feat(options): schema, "dict" options, messages

Problem:
Options parsing is still painful for dict-style options.

Solution:
schema-maxxing => better `opt:get()` (will be the basis for `vim.o()`),
unified (and more-detailed) err msgs.

- Drop bespoke structure-builder in `_core/options.lua`.
- Define `schema` for all non-primitive options (except 'guicursor' and
  statusline-style options); generate reified keysets `OptKeyDict`).
  - Generate 'fillchars' => `fcs_tab`, 'listchars' => `lcs_tab`.
- `nvim_set_option_value`:
  - Return the improved structures. Also from `vim.opt.x:get()`.
  - Eliminate api <=> lua roundtrip, centralize option structure
    handling.
- Improve/unify errors.
  - Bump ERR_BUFLEN 80 → 256 so the "one of" list isn't truncated.
- Eliminate old 'diffopt' order-dependence (`iwhiteall` before `iwhite`)

Error samples:

    Typed-key path (opt_strings_check → diffopt/mousescroll/breakindentopt):
    E474: Unknown item 'foo'
    E474: 'context' requires a number
    E474: 'ver' number is out of range
    E474: 'algorithm' must be one of: myers, minimal, patience, histogram
    E474: 'filler' does not take a value

Related:

- #31084
- #34661
- #31820
- #14739
- #20107
- fix #18875
  - :get() returns `{ sbr = true, shift = '3' }` (reified-keyset) instead of `{'sbr', 'shift:3'}`
  - Setting via table now works too. `object_as_optval_for` `is_map` now recognizes struct options.
- fix #30296
  - instead of `E474: Invalid argument`, errors now look like:
    ```
    E474: Invalid value 'x', expected one of: single, double: ambiwidth=x
    E474: Unknown item 'foo': diffopt=foo
    E474: 'context' requires a number: diffopt=context:x
    ```

simplify `win_float_parse_option` from #26799.
This commit is contained in:
Justin M. Keyes committed 2026-07-24 13:05:20 +02:00
1 parent 5d76b9836a
commit f8cfd7f06a
63 files changed
+2142 -1230

No files matched your search

+6
View File
@@ -38,6 +38,12 @@ LUA
• pos.to_cursor() returns a (`row,` `col)` tuple
instead of returning them as separate values.
• Renamed current_level param of vim.log.new().
• `vim.opt.{option}:get()` has improved structure for dict-like options.
Some shapes changed:
- "key:value" list options (e.g. 'diffopt', 'mousescroll') return a map
`{ key = value }` instead of an array of `"key:value"` strings.
- The `,,` literal-comma convention (e.g. 'isfname') is not reconstructed in
the structured view; the raw string value is unchanged.
DIAGNOSTICS
+6 -148
View File
@@ -95,46 +95,6 @@
local M = {}
local api = vim.api
-- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
local key_value_options = {
fillchars = true,
fcs = true,
listchars = true,
lcs = true,
winhighlight = true,
winhl = true,
}
--- @nodoc
--- @class vim._option.Info : vim.api.keyset.get_option_info
--- @field metatype 'boolean'|'string'|'number'|'map'|'array'|'set'
--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
---@return string
local function get_option_metatype(name, info)
if info.type == 'string' then
if info.flaglist then
return 'set'
elseif info.commalist then
if key_value_options[name] then
return 'map'
end
return 'array'
end
return 'string'
end
return info.type
end
--- @param name string
--- @return vim._option.Info
local function get_options_info(name)
local info = api.nvim_get_option_info2(name)
--- @cast info vim._option.Info
info.metatype = get_option_metatype(name, info)
return info
end
--- Gets or sets environment variables in the current editor process. See |expand-env| and
--- |:let-environment| for the Vimscript behavior. Invalid or unset key returns `nil`.
---
@@ -305,113 +265,6 @@ vim.bo = new_buf_opt_accessor()
--- ```
vim.wo = new_win_opt_accessor()
local function passthrough(_, x)
return x
end
-- Map of OptionType to functions that take vimoption_T values and convert to Lua values.
-- Each function takes (info, vim_value) -> lua_value
local to_lua_value = {
boolean = passthrough,
number = passthrough,
string = passthrough,
array = function(_, value)
-- Empty strings mean that there is nothing there,
-- so empty table should be returned.
if value == '' then
return {}
end
-- Handles unescaped commas in a list.
if value:find(',,,') then
--- @type string, string
local left, right = unpack(vim.split(value, ',,,'))
local result = {}
vim.list_extend(result, vim.split(left, ','))
table.insert(result, ',')
vim.list_extend(result, vim.split(right, ','))
table.sort(result)
return result
end
if value:find(',^,,', 1, true) then
--- @type string, string
local left, right = unpack(vim.split(value, ',^,,', { plain = true }))
local result = {}
vim.list_extend(result, vim.split(left, ','))
table.insert(result, '^,')
vim.list_extend(result, vim.split(right, ','))
table.sort(result)
return result
end
return vim.split(value, ',')
end,
set = function(info, value)
if type(value) == 'table' then
return value
end
-- Empty strings mean that there is nothing there,
-- so empty table should be returned.
if value == '' then
return {}
end
assert(info.flaglist, 'That is the only one I know how to handle')
local result = {} --- @type table<string,true>
if info.flaglist and info.commalist then
local split_value = vim.split(value, ',')
for _, v in ipairs(split_value) do
result[v] = true
end
else
for i = 1, #value do
result[value:sub(i, i)] = true
end
end
return result
end,
map = function(info, raw_value)
if type(raw_value) == 'table' then
return raw_value
end
assert(info.commalist, 'Only commas are supported currently')
local result = {} --- @type table<string,string>
local comma_split = vim.split(raw_value, ',')
for _, key_value_str in ipairs(comma_split) do
--- @type string, string
local key, value = unpack(vim.split(key_value_str, ':'))
key = vim.trim(key)
result[key] = value
end
return result
end,
}
--- Converts a vimoption_T style value to a Lua value
function M.convert_value_to_lua(name, option_value)
local info = get_options_info(name) or error('Not a valid option name: ' .. name)
return to_lua_value[info.metatype](info, option_value)
end
local function create_option_accessor(scope)
--- @diagnostic disable-next-line: no-unknown
local option_mt
@@ -433,7 +286,12 @@ local function create_option_accessor(scope)
option_mt = {
get = function(self)
return M.convert_value_to_lua(self._name, self._value)
-- `nvim_set_option_value(dry_run)` returns the value in its structured form.
return api.nvim_set_option_value(
self._name,
self._value,
{ operation = 'set', scope = scope, dry_run = true }
)
end,
append = function(self, right)
+1 -1
View File
@@ -33,7 +33,7 @@ local dup_allowed = {
E317 = 4,
E319 = 2,
E423 = 3,
E474 = 52,
E474 = 59,
E475 = 6,
E482 = 3,
E484 = 2,
+35 -48
View File
@@ -4,6 +4,7 @@
-- to obtain how the script is invoked, look in build/build.ninja and grep for
-- "gen_api_dispatch.lua"
local hashy = require 'gen.hashy'
local keyset = require('gen.keyset')
local c_grammar = require('gen.c_grammar')
-- output h file with generated dispatch functions (dispatch_wrappers.generated.h)
@@ -356,63 +357,49 @@ local keysets_defs = assert(io.open(keysets_outputf, 'wb'))
keysets_defs:write('// IWYU pragma: private, include "nvim/api/private/dispatch.h"\n\n')
for _, k in ipairs(keysets) do
local neworder, hashfun = hashy.hashy_hash(k.name, k.keys, function(idx)
return k.name .. '_table[' .. idx .. '].str'
end)
keysets_defs:write('extern KeySetLink ' .. k.name .. '_table[' .. (1 + #neworder) .. '];\n')
local function typename(type)
if type == 'HLGroupID' then
return 'kObjectTypeInteger'
elseif not type or startswith(type, 'Union') then
return 'kObjectTypeNil'
elseif type == 'StringArray' then
return 'kUnpackTypeStringArray'
end
return 'kObjectType' .. real_type(type)
local function typename(type)
if type == 'HLGroupID' then
return 'kObjectTypeInteger'
elseif not type or startswith(type, 'Union') then
return 'kObjectTypeNil'
elseif type == 'StringArray' then
return 'kUnpackTypeStringArray'
end
return 'kObjectType' .. real_type(type)
end
output:write('KeySetLink ' .. k.name .. '_table[] = {\n')
for i, key in ipairs(neworder) do
for _, k in ipairs(keysets) do
local order, hashfun = keyset.hash(k.name, k.keys)
keysets_defs:write('extern KeySetLink ' .. k.name .. '_table[' .. (1 + #order) .. '];\n')
local entry = {} --- @type table<string, gen.keyset.entry>
for i, key in ipairs(order) do
-- Only keysets with optional keys carry a per-key HAS_KEY index (and its `KEYSET_OPTIDX` define).
local ind = -1
if k.has_optional then
ind = i
keysets_defs:write('#define KEYSET_OPTIDX_' .. k.name .. '__' .. key .. ' ' .. ind .. '\n')
end
output:write(
' {"'
.. key
.. '", offsetof(KeyDict_'
.. k.name
.. ', '
.. (k.c_names[key] or key)
.. '), '
.. typename(k.types[key])
.. ', '
.. ind
.. ', '
.. (k.types[key] == 'HLGroupID' and 'true' or 'false')
.. '},\n'
)
entry[key] = {
field = k.c_names[key] or key,
type = typename(k.types[key]),
opt_index = ind,
is_hlgroup = k.types[key] == 'HLGroupID',
}
end
output:write(' {NULL, 0, kObjectTypeNil, -1, false},\n')
output:write('};\n\n')
output:write(hashfun)
output:write([[
KeySetLink *KeyDict_]] .. k.name .. [[_get_field(const char *str, size_t len)
{
int hash = ]] .. k.name .. [[_hash(str, len);
if (hash == -1) {
return NULL;
}
return &]] .. k.name .. [[_table[hash];
}
]])
keyset.emit(function(s)
output:write(s .. '\n')
end, {
name = k.name,
get_field = 'KeyDict_' .. k.name .. '_get_field',
struct = 'KeyDict_' .. k.name,
order = order,
hashfun = hashfun,
entry = entry,
static = false,
})
output:write('\n')
end
keysets_defs:close()
+8 -4
View File
@@ -657,11 +657,15 @@ local function render_option_meta(_f, opt, write)
write('--- ' .. l)
end
if opt.type == 'string' and not opt.list and opt.values then
local values = {} --- @type string[]
for _, e in ipairs(opt.values) do
values[#values + 1] = fmt("'%s'", e)
-- A non-list string option with a fixed value set (e.g. 'ambiwidth', 'tagcase') documents its
-- exact value union; everything else uses its Lua type.
local values = {} --- @type string[]
if opt.type == 'string' and not opt.list and opt.schema then
for _, v in ipairs(require('nvim.options').schema_values(opt.schema)) do
values[#values + 1] = fmt("'%s'", v)
end
end
if #values > 0 then
write('--- @type ' .. table.concat(values, '|'))
else
write('--- @type ' .. OPTION_TYPES[opt.type])
+319 -76
View File
@@ -1,11 +1,27 @@
---@diagnostic disable: no-unknown
local options_input_file = arg[5]
local options_input_file = arg[7]
--- @module 'nvim.options'
local options = loadfile(options_input_file)()
local options_meta = options.options
local cstr = options.cstr
local valid_scopes = options.valid_scopes
local schema_values = options.schema_values
-- Object type of a `dict` key's value, for the generated KeySetLink table.
local keyset_ctype = {
num = 'kObjectTypeInteger',
snum = 'kObjectTypeInteger',
enum = 'kObjectTypeString',
str = 'kObjectTypeString',
}
-- C field type (an API type) for each `dict` key kind.
local keyset_ftype = {
num = 'Integer',
snum = 'Integer',
enum = 'String',
str = 'String',
}
--- @param o vim.option_meta
--- @return string
@@ -13,6 +29,14 @@ local function get_values_var(o)
return ('opt_%s_values'):format(o.abbreviation or o.full_name)
end
--- True if an option is stored as a reified keyset (kOptValTypeDict): a `dict` schema (typed
--- key:value map, e.g. 'diffopt'), as opposed to a flag/enum/char category.
--- @param o vim.option_meta
--- @return boolean
local function is_dict_option(o)
return o.schema ~= nil and o.schema.dict ~= nil
end
--- @param s string
--- @return string
local function lowercase_to_titlecase(s)
@@ -191,12 +215,20 @@ local function dump_option(i, o, write)
if o.abbreviation then
write(' .shortname=', cstr(o.abbreviation))
end
write(' .type=', opt_type_enum(o.type))
write(' .type=', is_dict_option(o) and 'kOptValTypeDict' or opt_type_enum(o.type))
write(' .flags=', get_flags(o))
write(' .scope_flags=', get_scope_flags(o))
write(' .scope_idx=', get_scope_idx(o))
write(' .values=', (o.values and get_values_var(o) or 'NULL'))
write(' .values_len=', (o.values and #o.values or '0'))
-- `chars` (dispatch tables) and `flagchars` don't generate completion values (they self-expand).
local values = o.schema and schema_values(o.schema) or {}
write(' .values=', (#values > 0 and get_values_var(o) or 'NULL'))
write(' .values_len=', (#values > 0 and #values or '0'))
-- A `dict` schema also emits an OptSchemaItem[]; expose it so the structured value conversion can
-- read typed sub-values.
write(
' .schema=',
(is_dict_option(o) and ('opt_%s_schema'):format(o.abbreviation or o.full_name) or 'NULL')
)
write(' .flags_var=', (o.flags_varname and ('&%s'):format(o.flags_varname) or 'NULL'))
if o.enable_if then
write(('#if defined(%s)'):format(o.enable_if))
@@ -245,87 +277,66 @@ local function dump_option(i, o, write)
write(' },')
end
--- @param prefix string
--- @param values vim.option_valid_values
local function preorder_traversal(prefix, values)
local out = {} --- @type string[]
local function add(s)
table.insert(out, s)
end
add('')
add(('EXTERN const char *(%s_values[%s]) INIT( = {'):format(prefix, #vim.tbl_keys(values) + 1))
--- @type [string,vim.option_valid_values][]
local children = {}
for _, value in ipairs(values) do
if type(value) == 'string' then
add((' "%s",'):format(value))
else
assert(type(value) == 'table' and type(value[1]) == 'string' and type(value[2]) == 'table')
add((' "%s",'):format(value[1]))
table.insert(children, value)
end
end
add(' NULL')
add('});')
for _, value in pairs(children) do
-- Remove trailing colon from the added prefix to prevent syntax errors.
add(preorder_traversal(prefix .. '_' .. value[1]:gsub(':$', ''), value[2]))
end
return table.concat(out, '\n')
end
--- Emit an option's flag-set enum from its `flagchars` or `flags` schema:
--- * `flagchars` (e.g. 'formatoptions'): a name->char map -> `enum { kFoWrap = 't', … }`
--- * `flags` (e.g. 'foldopen', 'virtualedit'): an ordered list of names (bit = 2^i) or of
--- {name, bit} pairs -> `typedef enum { kOptFdoFlagHor = 0x…, … } OptFdoFlags;`. A pair's
--- optional 3rd element overrides the C constant's token (e.g. 'NONE' -> `NoneU`).
--- @param o vim.option_meta
--- @return string
local function gen_opt_enum(o)
local function gen_token_enum(o)
local out = {} --- @type string[]
local function add(s)
table.insert(out, s)
end
local opt_name = lowercase_to_titlecase(o.abbreviation or o.full_name)
if o.schema.flagchars then
-- Char flags complete as raw chars, so order is irrelevant; sort by name for a stable enum.
local names = vim.tbl_keys(o.schema.flagchars) --[[ @as string[] ]]
table.sort(names)
add('')
add('enum {')
for _, name in ipairs(names) do
add(
(" k%s%s = '%s',"):format(
opt_name,
lowercase_to_titlecase(name),
(o.schema.flagchars[name]:gsub("[\\']", '\\%0'))
)
)
end
add('};')
return table.concat(out, '\n')
end
-- Bitmask flags: collect (C-token, bit) and emit in bit order.
local flags = {} --- @type {name:string,bit:integer}[]
for i, entry in ipairs(o.schema.flags) do
if type(entry) == 'string' then
flags[#flags + 1] = { name = entry, bit = math.pow(2, i - 1) }
else
local e = entry --[[ @as {[1]:string, [2]:integer, [3]:string?} ]]
flags[#flags + 1] = { name = e[3] or e[1], bit = e[2] }
end
end
table.sort(flags, function(a, b)
return a.bit < b.bit
end)
add('')
add('typedef enum {')
local opt_name = lowercase_to_titlecase(o.abbreviation or o.full_name)
--- @type table<string,integer>
local enum_values
if type(o.flags) == 'table' then
enum_values = o.flags --[[ @as table<string,integer> ]]
else
enum_values = {}
for i, flag_name in ipairs(o.values) do
assert(type(flag_name) == 'string')
enum_values[flag_name] = math.pow(2, i - 1)
end
end
-- Sort the keys by the flag value so that the enum can be generated in order.
--- @type string[]
local flag_names = vim.tbl_keys(enum_values)
table.sort(flag_names, function(a, b)
return enum_values[a] < enum_values[b]
end)
for _, flag_name in pairs(flag_names) do
for _, f in ipairs(flags) do
-- A "key:" token (e.g. 'messagesopt' "wait:") names its flag without the trailing colon.
add(
(' kOpt%sFlag%s = 0x%02x,'):format(
opt_name,
lowercase_to_titlecase(flag_name:gsub(':$', '')),
enum_values[flag_name]
lowercase_to_titlecase((f.name:gsub(':$', ''))),
f.bit
)
)
end
add(('} Opt%sFlags;'):format(opt_name))
return table.concat(out, '\n')
end
@@ -452,6 +463,87 @@ local function gen_map(output_file, option_index)
fd:close()
end
-- Maps a `dict` key's value-kind to its OptSchemaKind enum. Enum keys carry a values array instead.
local schema_kinds = {
num = 'kOptSchemaNum',
snum = 'kOptSchemaSNum',
str = 'kOptSchemaStr',
}
--- Emit the OptSchemaItem[] that opt_strings_check() validates against, from a `dict` schema.
--- @param prefix string e.g. "opt_dip"
--- @param schema vim.option_schema
--- @return string
local function gen_opt_schema(prefix, schema)
local out = {} --- @type string[]
local function add(s)
table.insert(out, s)
end
add('')
add(('EXTERN const OptSchemaItem %s_schema[] INIT( = {'):format(prefix))
for _, item in ipairs(schema.dict) do
if type(item) == 'string' then
add((' { "%s", kOptSchemaFlag, NULL },'):format(item))
elseif item[2] == 'enum' then
add((' { "%s", kOptSchemaEnum, %s_%s_values },'):format(item[1], prefix, item[1]))
else
local kind = schema_kinds[item[2]] or error(('%s: bad kind %q'):format(prefix, item[2]))
add((' { "%s", %s, NULL },'):format(item[1], kind))
end
end
add(' { NULL, kOptSchemaFlag, NULL },')
add('});')
return table.concat(out, '\n')
end
--- Emit the completion values arrays (`opt_<name>_values`, plus `opt_<name>_<key>_values` for each
--- `dict` enum key). Enum/set/flag tokens complete as-is; `dict` keys complete as "key:".
--- @param prefix string e.g. "opt_dip"
--- @param schema vim.option_schema
--- @return string
local function gen_schema_values(prefix, schema)
local out = {} --- @type string[]
local function add(s)
table.insert(out, s)
end
local values = schema_values(schema)
add('')
add(('EXTERN const char *(%s_values[%s]) INIT( = {'):format(prefix, #values + 1))
for _, v in ipairs(values) do
add((' "%s",'):format(v))
end
add(' NULL')
add('});')
for _, item in ipairs(schema.dict or {}) do
if type(item) == 'table' and item[2] == 'enum' then
local vals = item[3].values --[[ @as string[] ]]
add('')
add(('EXTERN const char *(%s_%s_values[%s]) INIT( = {'):format(prefix, item[1], #vals + 1))
for _, v in ipairs(vals) do
add((' "%s",'):format(v))
end
add(' NULL')
add('});')
end
end
return table.concat(out, '\n')
end
local c_keywords = { inline = true, default = true, auto = true, register = true }
--- Sanitize a schema key into a C struct field name. C keywords get a trailing '_'.
--- @param name string
--- @return string
local function c_field_name(name)
local f = (name:gsub('%-', '_'))
return c_keywords[f] and (f .. '_') or f
end
--- @param output_file string
local function gen_vars(output_file)
local fd = assert(io.open(output_file, 'w'))
@@ -466,23 +558,170 @@ local function gen_vars(output_file)
-- Generate enums for option flags.
for _, o in ipairs(options_meta) do
if o.flags and (type(o.flags) == 'table' or o.values) then
write(gen_opt_enum(o))
if o.schema and (o.schema.flags or o.schema.flagchars) then
write(gen_token_enum(o))
end
end
-- Generate valid values for each option.
-- Generate valid values (for completion) and, for `dict` schemas, the validation schema.
for _, option in ipairs(options_meta) do
-- Since option values can be nested, we need to do preorder traversal to generate the values.
if option.values then
local values_var = ('opt_%s'):format(option.abbreviation or option.full_name)
write(preorder_traversal(values_var, option.values))
local values_var = ('opt_%s'):format(option.abbreviation or option.full_name)
-- `chars` dispatch tables (gen_chartab) and `flagchars` (gen_token_enum) have no completion
-- values; every other schema does.
if option.schema and #schema_values(option.schema) > 0 then
write(gen_schema_values(values_var, option.schema))
if is_dict_option(option) then
write(gen_opt_schema(values_var, option.schema))
end
end
end
fd:close()
end
--- Emit the chars_tab[] dispatch tables (fcs_tab/lcs_tab) from options with a `char`/`chars`
--- schema.
---
--- #include "options_chartab.generated.h"
---
--- @param output_file string
local function gen_chartab(output_file)
local fd = assert(io.open(output_file, 'w'))
--- @param s string
local function write(s)
fd:write(s)
fd:write('\n')
end
write('// IWYU pragma: private, include "nvim/optionstr.c"')
for _, o in ipairs(options_meta) do
if o.schema and o.schema.chars then
local abbr = o.abbreviation or o.full_name
write('')
write(('static const struct chars_tab %s_tab[] = {'):format(abbr))
for _, item in ipairs(o.schema.chars) do
--- @cast item vim.option_schema.char
local name = item[1]
local opts = item[3] or {}
-- opts.field == false means no storage (NULL cp), e.g. 'multispace'; nil defaults to name.
local cp = 'NULL'
if opts.field ~= false then
cp = ('&%s_chars.%s'):format(abbr, opts.field or name)
end
local def = opts.def and ('"%s"'):format(opts.def) or 'NULL'
local fallback = opts.fallback and ('"%s"'):format(opts.fallback) or 'NULL'
write((' CHARSTAB_ENTRY(%s, "%s", %s, %s),'):format(cp, name, def, fallback))
end
write('};')
end
end
fd:close()
end
--- Emit an option keyset: a `KeyDict_…` struct + `KeySetLink` + perfect-hash get_field, generated
--- from a `dict` schema. Consumed by `api_dict_to_keydict()`.
--- @param output_file string
local function gen_keysets(output_file)
local keyset = require('gen.keyset')
local fd = assert(io.open(output_file, 'w'))
local function write(s)
fd:write(s)
fd:write('\n')
end
write('#pragma once')
write('')
write('#include <stddef.h>')
write('#include "nvim/api/private/defs.h"')
write('#include "nvim/option_defs.h"')
local struct_opts = {} --- @type {enum:string,abbr:string,kd:string}[]
for _, o in ipairs(options_meta) do
-- A `dict` schema reifies to a keyset; generate it here.
if is_dict_option(o) then
-- Mirrors the API's KeyDict codegen with an "Opt" prefix: struct OptKeyDict_<abbr>, and
-- <abbr> as the keyset name for the table/hash/HAS_KEY (e.g. HAS_KEY(v, dip, filler)).
local abbr = o.abbreviation or o.full_name
local kd = 'OptKeyDict_' .. abbr
struct_opts[#struct_opts + 1] =
{ enum = 'kOpt' .. lowercase_to_titlecase(o.full_name), abbr = abbr, kd = kd }
local keys = {} --- @type string[]
local info = {} --- @type table<string,{field:string,ctype:string}>
for _, item in ipairs(o.schema.dict) do
local key = type(item) == 'string' and item or item[1]
local kind = type(item) == 'string' and 'flag' or item[2]
keys[#keys + 1] = key
info[key] = {
field = c_field_name(key),
ctype = keyset_ctype[kind] or 'kObjectTypeBoolean',
}
end
-- The schema array (validation grammar) lives in option_vars.generated.h; forward-declare it
-- so this header stays self-contained.
write('')
write(('extern const OptSchemaItem opt_%s_schema[];'):format(abbr))
write('')
write(('/// %s'):format(o.full_name))
write(('typedef struct %s {'):format(kd))
write((' OptionalKeys is_set__%s_;'):format(abbr))
for _, item in ipairs(o.schema.dict) do
local key = type(item) == 'string' and item or item[1]
local kind = type(item) == 'string' and 'flag' or item[2]
write((' %s %s;'):format(keyset_ftype[kind] or 'Boolean', c_field_name(key)))
end
write(('} %s;'):format(kd))
write('')
local order, hashfun = keyset.hash(abbr, keys)
local entry = {} --- @type table<string, gen.keyset.entry>
for i, key in ipairs(order) do
write(('#define KEYSET_OPTIDX_%s__%s %d'):format(abbr, c_field_name(key), i))
entry[key] =
{ field = info[key].field, type = info[key].ctype, opt_index = i, is_hlgroup = false }
end
-- `static` (inline): diff.c includes this header for the struct type alone, so a plain `static`
-- table/hash/get_field would warn as unused.
keyset.emit(write, {
name = abbr,
get_field = kd .. '_get_field',
struct = kd,
order = order,
hashfun = hashfun,
entry = entry,
static = true,
})
end
end
-- Dispatch from option index to its keyset handle (opt_dict_info); NULL for non-dict options.
write('')
write('static inline const OptDictInfo *opt_dict_info(OptIndex opt_idx)')
write('{')
write(' switch (opt_idx) {')
for _, s in ipairs(struct_opts) do
write((' case %s: {'):format(s.enum))
write(
(' static const OptDictInfo info = { %s_get_field, %s_table, opt_%s_schema,'):format(
s.kd,
s.abbr,
s.abbr
)
)
write((' sizeof(%s) };'):format(s.kd))
write(' return &info;')
write(' }')
end
write(' default:')
write(' return NULL;')
write(' }')
write('}')
fd:close()
end
--- @param output_file string
local function gen_options(output_file)
local fd = assert(io.open(output_file, 'w'))
@@ -527,10 +766,14 @@ local function main()
local options_enum_file = arg[2]
local options_map_file = arg[3]
local option_vars_file = arg[4]
local options_chartab_file = arg[5]
local options_keysets_file = arg[6]
local option_index = gen_enums(options_enum_file)
gen_map(options_map_file, option_index)
gen_vars(option_vars_file)
gen_chartab(options_chartab_file)
gen_keysets(options_keysets_file)
gen_options(options_file)
end
+2
View File
@@ -41,6 +41,8 @@ pub fn nvim_gen_sources(
_ = gen_header(b, gen_step, "options_enum.generated.h", gen_headers);
_ = gen_header(b, gen_step, "options_map.generated.h", gen_headers);
_ = gen_header(b, gen_step, "option_vars.generated.h", gen_headers);
_ = gen_header(b, gen_step, "options_chartab.generated.h", gen_headers);
_ = gen_header(b, gen_step, "options_keysets.generated.h", gen_headers);
gen_step.addFileArg(b.path("src/nvim/options.lua"));
const test_gen_step = b.step("wipopt", "debug one nlua0 (options)");
+55
View File
@@ -0,0 +1,55 @@
-- Shared codegen for a `KeySetLink` perfect-hash table and its `get_field()` lookup.
local hashy = require('gen.hashy')
local M = {}
--- Perfect-hash `keys`, using the shared `<name>_table[idx].str` probe convention.
--- @param name string
--- @param keys string[]
--- @return string[] order keys in perfect-hash order
--- @return string hashfun source of the `<name>_hash()` function
function M.hash(name, keys)
return hashy.hashy_hash(name, keys, function(idx)
return name .. '_table[' .. idx .. '].str'
end)
end
--- @class gen.keyset.entry
--- @field field string C struct field name, for offsetof()
--- @field type string ObjectType/UnpackType enum, e.g. 'kObjectTypeInteger'
--- @field opt_index integer index used by HAS_KEY (or -1 when the keyset has no optional keys)
--- @field is_hlgroup boolean
--- Emit `<name>_table[]` (a `KeySetLink[]` in perfect-hash `order`) followed by `<get_field>()`.
--- @param write fun(s: string) writer that appends a newline
--- @param p { name: string, get_field: string, struct: string, order: string[], hashfun: string, entry: table<string, gen.keyset.entry>, static: boolean }
--- `static` emits a `static const` table + `static inline` funcs (a self-contained header, e.g. the
--- option keysets); otherwise external linkage (the API keydicts, declared in a separate header).
function M.emit(write, p)
write(('%sKeySetLink %s_table[] = {'):format(p.static and 'static const ' or '', p.name))
for _, key in ipairs(p.order) do
local e = p.entry[key]
write(
(' { "%s", offsetof(%s, %s), %s, %d, %s },'):format(
key,
p.struct,
e.field,
e.type,
e.opt_index,
e.is_hlgroup and 'true' or 'false'
)
)
end
write(' { NULL, 0, kObjectTypeNil, -1, false },')
write('};')
write('')
local inl = p.static and 'static inline ' or ''
write(inl .. p.hashfun)
write(inl .. ('KeySetLink *%s(const char *str, size_t len)'):format(p.get_field))
write('{')
write((' int hash = %s_hash(str, len);'):format(p.name))
write((' return hash == -1 ? NULL : (KeySetLink *)&%s_table[hash];'):format(p.name))
write('}')
end
return M
+3
View File
@@ -13,6 +13,7 @@ const embedded_data = @import("embedded_data");
// these are common dependencies used by many generators
const hashy = @embedFile("gen/hashy.lua");
const keyset = @embedFile("gen/keyset.lua");
const c_grammar = @embedFile("gen/c_grammar.lua");
const Lua = ziglua.Lua;
@@ -43,6 +44,8 @@ fn init_lua() !*Lua {
_ = lua.getField(-1, "preload");
try lua.loadBuffer(hashy, "hashy.lua"); // [package, preload, hashy]
lua.setField(-2, "gen.hashy");
try lua.loadBuffer(keyset, "keyset.lua"); // [package, preload, keyset]
lua.setField(-2, "gen.keyset");
try lua.loadBuffer(c_grammar, "c_grammar.lua"); // [package, preload, c_grammar]
lua.setField(-2, "gen.c_grammar");
lua.pop(2);
+6 -2
View File
@@ -336,6 +336,8 @@ set(GENERATED_OPTIONS ${GENERATED_DIR}/options.generated.h)
set(GENERATED_OPTIONS_ENUM ${GENERATED_DIR}/options_enum.generated.h)
set(GENERATED_OPTIONS_MAP ${GENERATED_DIR}/options_map.generated.h)
set(GENERATED_OPTION_VARS ${GENERATED_DIR}/option_vars.generated.h)
set(GENERATED_OPTIONS_CHARTAB ${GENERATED_DIR}/options_chartab.generated.h)
set(GENERATED_OPTIONS_KEYSETS ${GENERATED_DIR}/options_keysets.generated.h)
set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h)
set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h)
set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h)
@@ -711,8 +713,8 @@ add_custom_command(OUTPUT ${GENERATED_KEYCODE_NAMES}
DEPENDS ${LUA_GEN_DEPS} ${KEYCODES_GENERATOR} ${GENERATOR_HASHY} ${CMAKE_CURRENT_LIST_DIR}/keycodes.lua
)
add_custom_command(OUTPUT ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS}
COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} ${CMAKE_CURRENT_LIST_DIR}/options.lua
add_custom_command(OUTPUT ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} ${GENERATED_OPTIONS_CHARTAB} ${GENERATED_OPTIONS_KEYSETS}
COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS} ${GENERATED_OPTIONS_ENUM} ${GENERATED_OPTIONS_MAP} ${GENERATED_OPTION_VARS} ${GENERATED_OPTIONS_CHARTAB} ${GENERATED_OPTIONS_KEYSETS} ${CMAKE_CURRENT_LIST_DIR}/options.lua
DEPENDS ${LUA_GEN_DEPS} ${OPTIONS_GENERATOR} ${GENERATOR_HASHY} ${CMAKE_CURRENT_LIST_DIR}/options.lua
)
@@ -732,6 +734,8 @@ list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_KEYCODE_NAMES}"
"${GENERATED_OPTIONS}"
"${GENERATED_OPTIONS_MAP}"
"${GENERATED_OPTIONS_CHARTAB}"
"${GENERATED_OPTIONS_KEYSETS}"
"${VIM_MODULE_FILE}"
"${GENERATED_API_METADATA}"
"${PROJECT_BINARY_DIR}/cmake.config/auto/pathdef.h"
+6 -1
View File
@@ -757,7 +757,12 @@ static Object get_option_from(void *from, OptScope scope, String name, Error *er
return (Object)OBJECT_INIT;
});
return optval_as_object(value);
Object rv = optval_as_object(value);
// A struct value serializes into a fresh Object string, so its keyset must be freed here.
if (value.type == kOptValTypeDict) {
optval_free(value);
}
return rv;
}
/// Sets the value of a global or local (buffer, window) option.
+15 -22
View File
@@ -305,7 +305,13 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
goto err;
});
return optval_as_object(value);
Object rv = optval_as_object(value);
// kOptValTypeDict serializes into a fresh Object string (not aliased like a plain string), so
// its keyset must be freed here.
if (value.type == kOptValTypeDict) {
optval_free(value);
}
return rv;
err:
optval_free(value);
return (Object)OBJECT_INIT;
@@ -397,6 +403,10 @@ Object nvim_set_option_value(uint64_t channel_id, String name, Object value, Dic
case kOptValTypeBoolean:
merged_val = optval_right;
break;
case kOptValTypeDict:
// Unreachable: object_as_optval_for() yields the string form for dict options, which is
// merged as a string (below) and reified by set_option().
break;
}
optval_free(optval_right);
@@ -416,27 +426,10 @@ Object nvim_set_option_value(uint64_t channel_id, String name, Object value, Dic
});
}
if (merged_val.type == kOptValTypeString) {
// Convert string/list/map style option to a (Lua) structure.
Error lua_err = ERROR_INIT;
MAXSIZE_TEMP_ARRAY(lua_args, 2);
ADD_C(lua_args, STRING_OBJ(name));
ADD_C(lua_args, STRING_OBJ(merged_val.data.string));
Object lua_val =
NLUA_EXEC_STATIC("return require('vim._core.options').convert_value_to_lua(...)",
lua_args, kRetObject, arena, &lua_err);
optval_free(merged_val);
VALIDATE(!ERROR_SET(&lua_err), "%s", lua_err.msg, {
api_clear_error(&lua_err);
return NIL;
});
return lua_val;
}
return optval_as_object(merged_val);
// Return the value in its structured (list/map/set) form.
Object rv = optval_to_struct(opt_idx, merged_val, arena);
optval_free(merged_val);
return rv;
}
/// Gets the option information for all options.
+8 -8
View File
@@ -177,7 +177,7 @@ int get_highest_fnum(void)
static int read_buffer(bool read_stdin, exarg_T *eap, int flags)
{
int retval = OK;
bool silent = shortmess(SHM_FILEINFO);
bool silent = shortmess(kShmFileinfo);
// Read from the buffer which the text is already filled in and append at
// the end. This makes it possible to retry when 'fileformat' or
@@ -250,7 +250,7 @@ int open_buffer(bool read_stdin, exarg_T *eap, int flags_arg)
bufref_T old_curbuf;
OptInt old_tw = curbuf->b_p_tw;
bool read_fifo = false;
bool silent = shortmess(SHM_FILEINFO);
bool silent = shortmess(kShmFileinfo);
// The 'readonly' flag is only set when BF_NEVERLOADED is being reset.
// When re-entering the same buffer, it should not change, because the
@@ -384,9 +384,9 @@ int open_buffer(bool read_stdin, exarg_T *eap, int flags_arg)
// When reading stdin, the buffer contents always needs writing, so set
// the changed flag. Unless in readonly mode: "ls | nvim -R -".
// When interrupted and 'cpoptions' contains 'i' set changed flag.
if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
if ((got_int && vim_strchr(p_cpo, kCpoIntmod) != NULL)
|| curbuf->b_modified_was_set // autocmd did ":set modified"
|| (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)) {
|| (aborting() && vim_strchr(p_cpo, kCpoIntmod) != NULL)) {
changed(curbuf);
} else if (retval != FAIL && !read_stdin && !read_fifo) {
unchanged(curbuf, false, true);
@@ -1287,7 +1287,7 @@ static int empty_curbuf(bool close_others, int forceit, int action)
if (!close_others) {
need_fileinfo = false;
} else if (retval == OK && !shortmess(SHM_FILEINFO)) {
} else if (retval == OK && !shortmess(kShmFileinfo)) {
// do_ecmd() does not display file info for a new empty buffer.
need_fileinfo = true;
}
@@ -1834,7 +1834,7 @@ static void enter_buffer(buf_T *buf)
open_buffer(false, NULL, 0);
} else {
if (!msg_silent && !shortmess(SHM_FILEINFO)) {
if (!msg_silent && !shortmess(kShmFileinfo)) {
need_fileinfo = true; // display file info after redraw
}
// check if file changed
@@ -3349,7 +3349,7 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate)
IOSIZE - bufferlen,
"\"%s%s%s%s%s%s",
curbufIsChanged()
? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]"))
? (shortmess(kShmMod) ? " [+]" : _(" [Modified]"))
: " ",
(curbuf->b_flags & BF_NOTEDITED) && !dontwrite
? _("[Not edited]") : "",
@@ -3358,7 +3358,7 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate)
(curbuf->b_flags & BF_READERR)
? _("[Read errors]") : "",
curbuf->b_p_ro
? (shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"))
? (shortmess(kShmRo) ? _("[RO]") : _("[readonly]"))
: "",
(curbufIsChanged()
|| (curbuf->b_flags & BF_WRITE_MASK)
+1 -1
View File
@@ -92,7 +92,7 @@ typedef struct {
#define w_p_arab w_onebuf_opt.wo_arab // 'arabic'
int wo_bri;
#define w_p_bri w_onebuf_opt.wo_bri // 'breakindent'
char *wo_briopt;
struct OptKeyDict_briopt *wo_briopt; // 'breakindentopt'
#define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt'
int wo_diff;
#define w_p_diff w_onebuf_opt.wo_diff // 'diff'
+14 -14
View File
@@ -467,7 +467,7 @@ static int buf_write_do_autocmds(buf_T *buf, char **fnamep, char **sfnamep, char
}
}
if (reset_changed && buf->b_changed && !append
&& (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
&& (overwriting || vim_strchr(p_cpo, kCpoPlus) != NULL)) {
// Buffer still changed, the autocommands didn't work properly.
return FAIL;
}
@@ -664,7 +664,7 @@ static int get_fileinfo(buf_T *buf, char *fname, bool overwriting, bool forceit,
*readonly = !os_file_is_writable(fname);
if (!forceit && *readonly) {
if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
if (vim_strchr(p_cpo, kCpoFwrite) != NULL) {
*err = set_err_num("E504", _(err_readonly));
} else {
*err = set_err_num("E505", _("is read-only (add ! to override)"));
@@ -903,7 +903,7 @@ nobackup:
// 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) {
if (file_readonly && vim_strchr(p_cpo, kCpoFwrite) != NULL) {
*err = set_err_num("E504", _(err_readonly));
return FAIL;
}
@@ -1028,8 +1028,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
&& buf == curbuf
&& !bt_nofilename(buf)
&& !filtering
&& (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL)
&& vim_strchr(p_cpo, CPO_FNAMEW) != NULL) {
&& (!append || vim_strchr(p_cpo, kCpoFnameapp) != NULL)
&& vim_strchr(p_cpo, kCpoFnamew) != NULL) {
if (set_rw_fname(fname, sfname) == FAIL) {
return FAIL;
}
@@ -1076,7 +1076,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
buf->b_op_end = orig_end;
}
if (shortmess(SHM_OVER) && !exiting) {
if (shortmess(kShmOver) && !exiting) {
msg_scroll = false; // overwrite previous file message
} else {
msg_scroll = true; // don't overwrite previous file message
@@ -1164,7 +1164,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
// When using ":w!" and the file was read-only: make it writable
if (forceit && perm >= 0 && !(perm & 0200)
&& file_info_old.stat.st_uid == getuid()
&& vim_strchr(p_cpo, CPO_FWRITE) == NULL) {
&& vim_strchr(p_cpo, kCpoFwrite) == NULL) {
perm |= 0200;
os_setperm(fname, perm);
made_writable = true;
@@ -1173,7 +1173,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
// When using ":w!" and writing to the current file, 'readonly' makes no
// sense, reset it, unless 'Z' appears in 'cpoptions'.
if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) {
if (forceit && overwriting && vim_strchr(p_cpo, kCpoKeepro) == NULL) {
buf->b_p_ro = false;
need_maketitle = true; // set window title later
status_redraw_all(); // redraw status lines later
@@ -1328,7 +1328,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
err = set_err(_("E166: Can't open linked file for writing"));
} else {
err = set_err_arg(_("E212: Can't open file for writing: %s"), fd);
if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL && perm >= 0) {
if (forceit && vim_strchr(p_cpo, kCpoFwrite) == NULL && perm >= 0) {
// we write to the file, thus it should be marked
// writable after all
if (!(perm & 0200)) {
@@ -1347,7 +1347,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
}
#else
err = set_err_arg(_("E212: Can't open file for writing: %s"), fd);
if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL && perm >= 0) {
if (forceit && vim_strchr(p_cpo, kCpoFwrite) == NULL && perm >= 0) {
if (!append) { // don't remove when appending
os_remove(wfname);
}
@@ -1704,11 +1704,11 @@ restore_backup:
insert_space = true;
}
msg_add_lines(insert_space, lnum, nchars); // add line/char count
if (!shortmess(SHM_WRITE)) {
if (!shortmess(kShmWrite)) {
if (append) {
xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"), IOSIZE);
xstrlcat(IObuff, shortmess(kShmWri) ? _(" [a]") : _(" appended"), IOSIZE);
} else {
xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"), IOSIZE);
xstrlcat(IObuff, shortmess(kShmWri) ? _(" [w]") : _(" written"), IOSIZE);
}
}
// Hide cursor while emitting "written" message, so cursor doesn't flicker in cmdline. #25974
@@ -1721,7 +1721,7 @@ restore_backup:
// 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)) {
&& (overwriting || vim_strchr(p_cpo, kCpoPlus) != NULL)) {
unchanged(buf, true, false);
const varnumber_T changedtick = buf_get_changedtick(buf);
if (buf->b_last_changedtick + 1 == changedtick) {
+3 -3
View File
@@ -440,7 +440,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
// Don't do this when displaying '$' at the end of changed text.
if (spell_check_window(curwin)
&& lnum < curbuf->b_ml.ml_line_count
&& vim_strchr(p_cpo, CPO_DOLLAR) == NULL) {
&& vim_strchr(p_cpo, kCpoDollar) == NULL) {
redrawWinline(curwin, lnum + 1);
}
// notify any channels that are watching
@@ -747,7 +747,7 @@ void ins_char_bytes(char *buf, size_t charlen)
// Returns the old value of list, so when finished,
// curwin->w_p_list should be set back to this.
int old_list = curwin->w_p_list;
if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
if (old_list && vim_strchr(p_cpo, kCpoListwm) == NULL) {
curwin->w_p_list = false;
}
// In virtual replace mode each character may replace one or more
@@ -1237,7 +1237,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
if (flags & OPENLINE_DO_COM) {
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))) {
&& (!has_format_option(kFoNoOpenComs) || (flags & OPENLINE_FORMAT))) {
// Check for a line comment after code.
comment_start = check_linecomment(saved_line);
if (comment_start != MAXCOL) {
+54 -127
View File
@@ -16,6 +16,7 @@
#include <string.h>
#include "auto/config.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
@@ -71,6 +72,9 @@
#include "nvim/window.h"
#include "xdiff/xdiff.h"
// KeyDict_dip + KeyDict_dip_get_field, generated from the 'diffopt' schema (reusing hashy.lua).
#include "options_keysets.generated.h"
static bool diff_busy = false; // using diff structs, don't change them
static bool diff_need_update = false; // ex_diffupdate needs to be called
@@ -2650,147 +2654,63 @@ int diffanchors_changed(bool buflocal)
return result;
}
/// This is called when 'diffopt' is changed.
/// Map the 'diffopt' keyset onto diff.c's globals. `v` is the stored value (`p_dip`), reified from
/// the ":set" string by `opt_fill()` when the option is set.
///
/// @return
int diffopt_changed(void)
/// @return FAIL only for the cross-part "horizontal" + "vertical" conflict.
static int diffopt_apply(OptKeyDict_dip *v)
{
int diff_context_new = 6;
int linematch_lines_new = 0;
int diff_flags_new = 0;
int diff_foldcolumn_new = 2;
int diff_algorithm_new = 0;
int diff_indent_heuristic = 0;
char *p = p_dip;
while (*p != NUL) {
// Note: Keep this in sync with opt_dip_values.
if (strncmp(p, "filler", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_FILLER;
} else if (strncmp(p, "anchor", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_ANCHOR;
} 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) {
p += 6;
diff_flags_new |= DIFF_IBLANK;
} else if (strncmp(p, "icase", 5) == 0) {
p += 5;
diff_flags_new |= DIFF_ICASE;
} else if (strncmp(p, "iwhiteall", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_IWHITEALL;
} else if (strncmp(p, "iwhiteeol", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_IWHITEEOL;
} else if (strncmp(p, "iwhite", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_IWHITE;
} else if (strncmp(p, "horizontal", 10) == 0) {
p += 10;
diff_flags_new |= DIFF_HORIZONTAL;
} 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])) {
p += 11;
diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new);
} else if (strncmp(p, "hiddenoff", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
} else if (strncmp(p, "closeoff", 8) == 0) {
p += 8;
diff_flags_new |= DIFF_CLOSE_OFF;
} else if (strncmp(p, "followwrap", 10) == 0) {
p += 10;
diff_flags_new |= DIFF_FOLLOWWRAP;
} else if (strncmp(p, "indent-heuristic", 16) == 0) {
p += 16;
diff_indent_heuristic = XDF_INDENT_HEURISTIC;
} else if (strncmp(p, "internal", 8) == 0) {
p += 8;
diff_flags_new |= DIFF_INTERNAL;
} else if (strncmp(p, "algorithm:", 10) == 0) {
// Note: Keep this in sync with opt_dip_algorithm_values.
p += 10;
if (strncmp(p, "myers", 5) == 0) {
p += 5;
diff_algorithm_new = 0;
} else if (strncmp(p, "minimal", 7) == 0) {
p += 7;
diff_algorithm_new = XDF_NEED_MINIMAL;
} else if (strncmp(p, "patience", 8) == 0) {
p += 8;
diff_algorithm_new = XDF_PATIENCE_DIFF;
} else if (strncmp(p, "histogram", 9) == 0) {
p += 9;
diff_algorithm_new = XDF_HISTOGRAM_DIFF;
} else {
return FAIL;
}
} else if (strncmp(p, "inline:", 7) == 0) {
// Note: Keep this in sync with opt_dip_inline_values.
p += 7;
if (strncmp(p, "none", 4) == 0) {
p += 4;
diff_flags_new &= ~(ALL_INLINE);
diff_flags_new |= DIFF_INLINE_NONE;
} else if (strncmp(p, "simple", 6) == 0) {
p += 6;
diff_flags_new &= ~(ALL_INLINE);
diff_flags_new |= DIFF_INLINE_SIMPLE;
} else if (strncmp(p, "char", 4) == 0) {
p += 4;
diff_flags_new &= ~(ALL_INLINE);
diff_flags_new |= DIFF_INLINE_CHAR;
} else if (strncmp(p, "word", 4) == 0) {
p += 4;
diff_flags_new &= ~(ALL_INLINE);
diff_flags_new |= DIFF_INLINE_WORD;
} 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;
// linematch does not make sense without filler set
diff_flags_new |= DIFF_FILLER;
}
if ((*p != ',') && (*p != NUL)) {
return FAIL;
}
if (*p == ',') {
p++;
int flags = (v->filler ? DIFF_FILLER : 0) | (v->anchor ? DIFF_ANCHOR : 0)
| (v->iblank ? DIFF_IBLANK : 0) | (v->icase ? DIFF_ICASE : 0)
| (v->iwhiteall ? DIFF_IWHITEALL : 0) | (v->iwhiteeol ? DIFF_IWHITEEOL : 0)
| (v->iwhite ? DIFF_IWHITE : 0) | (v->horizontal ? DIFF_HORIZONTAL : 0)
| (v->vertical ? DIFF_VERTICAL : 0) | (v->closeoff ? DIFF_CLOSE_OFF : 0)
| (v->hiddenoff ? DIFF_HIDDEN_OFF : 0) | (v->followwrap ? DIFF_FOLLOWWRAP : 0)
| (v->internal ? DIFF_INTERNAL : 0);
if (HAS_KEY(v, dip, linematch)) {
flags |= DIFF_LINEMATCH | DIFF_FILLER; // linematch needs filler
}
if (HAS_KEY(v, dip, inline_)) {
flags &= ~ALL_INLINE;
if (option_slice_eq(v->inline_.data, v->inline_.size, "simple")) {
flags |= DIFF_INLINE_SIMPLE;
} else if (option_slice_eq(v->inline_.data, v->inline_.size, "char")) {
flags |= DIFF_INLINE_CHAR;
} else if (option_slice_eq(v->inline_.data, v->inline_.size, "word")) {
flags |= DIFF_INLINE_WORD;
} else {
flags |= DIFF_INLINE_NONE;
}
}
diff_algorithm_new |= diff_indent_heuristic;
int algorithm = v->indent_heuristic ? XDF_INDENT_HEURISTIC : 0;
if (HAS_KEY(v, dip, algorithm)) {
if (option_slice_eq(v->algorithm.data, v->algorithm.size, "minimal")) {
algorithm |= XDF_NEED_MINIMAL;
} else if (option_slice_eq(v->algorithm.data, v->algorithm.size, "patience")) {
algorithm |= XDF_PATIENCE_DIFF;
} else if (option_slice_eq(v->algorithm.data, v->algorithm.size, "histogram")) {
algorithm |= XDF_HISTOGRAM_DIFF;
} // else "myers" -> 0
}
// Can't have both "horizontal" and "vertical".
if ((diff_flags_new & DIFF_HORIZONTAL) && (diff_flags_new & DIFF_VERTICAL)) {
if ((flags & DIFF_HORIZONTAL) && (flags & DIFF_VERTICAL)) {
return FAIL;
}
// If flags were added or removed, or the algorithm was changed, need to
// update the diff.
if (diff_flags != diff_flags_new || diff_algorithm != diff_algorithm_new) {
// If flags were added or removed, or the algorithm was changed, update the diff.
if (diff_flags != flags || diff_algorithm != algorithm) {
FOR_ALL_TABS(tp) {
tp->tp_diff_invalid = true;
}
}
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;
int context = HAS_KEY(v, dip, context) ? (int)v->context : 6;
diff_flags = flags;
diff_context = context == 0 ? 1 : context;
linematch_lines = HAS_KEY(v, dip, linematch) ? (int)v->linematch : 0;
diff_foldcolumn = HAS_KEY(v, dip, foldcolumn) ? (int)v->foldcolumn : 2;
diff_algorithm = algorithm;
diff_redraw(true);
@@ -2800,6 +2720,13 @@ int diffopt_changed(void)
return OK;
}
/// Apply the current 'diffopt'. Its value is stored as the reified keyset `p_dip` (see
/// `opt_dict_info()`), so this just hands the stored struct to `diffopt_apply()`.
int diffopt_changed(void)
{
return p_dip == NULL ? OK : diffopt_apply(p_dip);
}
/// Check that "diffopt" contains "horizontal".
bool diffopt_horizontal(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+2 -2
View File
@@ -668,7 +668,7 @@ static int get_line_number_attr(win_T *wp, winlinevars_T *wlv)
/// blanks when the 'n' flag isn't in 'cpo'.
static void draw_lnum_col(win_T *wp, winlinevars_T *wlv)
{
bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL;
bool has_cpo_n = vim_strchr(p_cpo, kCpoNumcol) != NULL;
if ((wp->w_p_nu || wp->w_p_rnu)
&& (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n)
@@ -3268,7 +3268,7 @@ end_check:
if (wlv.filler_todo <= 0) {
wlv.need_showbreak = true;
}
if (statuscol.draw && vim_strchr(p_cpo, CPO_NUMCOL)
if (statuscol.draw && vim_strchr(p_cpo, kCpoNumcol)
&& wlv.row > startrow + wlv.filler_lines) {
statuscol.draw = false; // don't draw status column if "n" is in 'cpo'
}
+3 -3
View File
@@ -944,7 +944,7 @@ int showmode(void)
if (do_mode) {
msg_puts_hl("--", hl_id, false);
// CTRL-X in Insert mode
if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
if (edit_submode != NULL && !shortmess(kShmCompletionmenu)) {
// These messages can get long, avoid a wrap in a narrow window.
// Prefer showing edit_submode_extra. With external messages there
// is no imposed limit.
@@ -1116,7 +1116,7 @@ void clearmode(void)
static void recording_mode(int hl_id)
{
if (shortmess(SHM_RECORDING)) {
if (shortmess(kShmRecording)) {
return;
}
@@ -2553,7 +2553,7 @@ void win_draw_end(win_T *wp, schar_T c1, bool draw_margin, int startrow, int end
}
// draw the number column
if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) {
if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, kCpoNumcol) == NULL) {
int width = number_width(wp) + 1;
n = grid_line_fill(n, MIN(view_width, n + width),
schar_from_ascii(' '), win_hl_attr(wp, HLF_N));
+4
View File
@@ -3409,6 +3409,10 @@ int eval_option(const char **const arg, typval_T *const rettv, const bool evalua
assert(value.type != kOptValTypeNil);
*rettv = optval_as_tv(value, true);
// A dict option serializes into a fresh typval string that rettv now owns, so free its keyset.
if (value.type == kOptValTypeDict) {
optval_free(value);
}
} else if (working && !is_tty_opt && is_option_hidden(opt_idx)) {
ret = FAIL;
}
+30 -1
View File
@@ -41,6 +41,7 @@
#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/optionstr.h"
#include "nvim/os/os.h"
#include "nvim/register.h"
#include "nvim/runtime.h"
@@ -1377,6 +1378,14 @@ static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
semsg(_(e_unknown_option2), arg);
goto theend;
}
// `:let &opt` operates on the string form; a dict option is handled as its serialization
// (set_option() reifies it back). This also lets `.=` concatenate onto it like any string option.
if (curval.type == kOptValTypeDict) {
OptVal strval = CSTR_AS_OPTVAL(opt_serialize(curval.data.dictval.ptr,
curval.data.dictval.table));
optval_free(curval);
curval = strval;
}
if (op != NULL && *op != '='
&& ((curval.type != kOptValTypeString && *op == '.')
|| (curval.type == kOptValTypeString && *op != '.'))) {
@@ -3202,7 +3211,9 @@ static OptVal tv_to_optval(typval_T *tv, OptIndex opt_idx, const char *option, b
const bool is_tty_opt = is_tty_option(option);
const bool option_has_bool = !is_tty_opt && option_has_type(opt_idx, kOptValTypeBoolean);
const bool option_has_num = !is_tty_opt && option_has_type(opt_idx, kOptValTypeNumber);
const bool option_has_str = is_tty_opt || option_has_type(opt_idx, kOptValTypeString);
// Struct-stored options (e.g. 'diffopt') take their ":set" string here; set_option() reifies it.
const bool option_has_str = is_tty_opt || option_has_type(opt_idx, kOptValTypeString)
|| option_has_type(opt_idx, kOptValTypeDict);
if (!is_tty_opt && (get_option(opt_idx)->flags & kOptFlagFunc) && tv_is_func(*tv)) {
// If the option can be set to a function reference or a lambda
@@ -3249,6 +3260,10 @@ static OptVal tv_to_optval(typval_T *tv, OptIndex opt_idx, const char *option, b
/// Convert an option value to typval.
///
/// A "schema.dict" option has no stored string to alias, so it serializes to an allocated string
/// owned by the returned; every other type borrows from `value`. Either transfer that string to
/// a longer-lived owner or release it with `optval_as_tv_free()`.
///
/// @param[in] value Option value to convert.
/// @param numbool Whether to convert boolean values to number.
/// Used for backwards compatibility.
@@ -3278,11 +3293,25 @@ typval_T optval_as_tv(OptVal value, bool numbool)
rettv.v_type = VAR_STRING;
rettv.vval.v_string = value.data.string.data;
break;
case kOptValTypeDict:
// Surfaced to Vimscript as its ":set" string, allocated.
rettv.v_type = VAR_STRING;
rettv.vval.v_string = opt_serialize(value.data.dictval.ptr, value.data.dictval.table);
break;
}
return rettv;
}
/// Release `optval_as_tv()` result. Only for "schema.dict" options; no-op for other types (they
/// alias the source value).
void optval_as_tv_free(OptVal value, typval_T tv)
{
if (value.type == kOptValTypeDict) {
xfree(tv.vval.v_string);
}
}
/// Set option "varname" to the value of "varp" for the current buffer/window.
static void set_option_from_tv(const char *varname, typval_T *varp)
{
+8 -8
View File
@@ -1395,7 +1395,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
if (do_in) {
if ((cmdmod.cmod_flags & CMOD_KEEPMARKS)
|| vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
|| vim_strchr(p_cpo, kCpoRemmark) == NULL) {
// TODO(bfredl): Currently not active for extmarks. What would we
// do if columns don't match, assume added/deleted bytes at the
// end of each line?
@@ -1786,7 +1786,7 @@ void ex_file(exarg_T *eap)
}
// print file name if no argument or 'F' is not in 'shortmess'
if (*eap->arg == NUL || !shortmess(SHM_FILEINFO)) {
if (*eap->arg == NUL || !shortmess(kShmFileinfo)) {
fileinfo(false, false, eap->forceit);
}
}
@@ -1875,7 +1875,7 @@ int do_write(exarg_T *eap)
// 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
if (vim_strchr(p_cpo, kCpoAltwrite) != NULL
|| eap->cmdidx == CMD_saveas) {
alt_buf = setaltfname(ffname, fname, 1);
} else {
@@ -2024,7 +2024,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, bool ot
|| (!bt_nofilename(buf)
&& ((buf->b_flags & BF_NOTEDITED)
|| ((buf->b_flags & BF_NEW)
&& vim_strchr(p_cpo, CPO_OVERNEW) == NULL)
&& vim_strchr(p_cpo, kCpoOvernew) == NULL)
|| (buf->b_flags & BF_READERR))))
&& !p_wa
&& os_path_exists(ffname)) {
@@ -2900,7 +2900,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) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
if (shortmess(kShmOverall) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
msg_scroll = false;
}
if (!msg_scroll) { // wait a bit when overwriting an error msg
@@ -2910,7 +2910,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
msg_scroll = msg_scroll_save;
msg_scrolled_ign = true;
if (!shortmess(SHM_FILEINFO)) {
if (!shortmess(kShmFileinfo)) {
fileinfo(false, true, false);
}
@@ -3940,7 +3940,7 @@ static int do_sub(exarg_T *eap, proftime_T tm, const int cmdpreview_ns,
// When 'cpoptions' contains "u" don't sync undo when
// asking for confirmation.
if (vim_strchr(p_cpo, CPO_UNDO) != NULL) {
if (vim_strchr(p_cpo, kCpoUndo) != NULL) {
no_u_sync++;
}
@@ -4082,7 +4082,7 @@ static int do_sub(exarg_T *eap, proftime_T tm, const int cmdpreview_ns,
}
State = save_State;
setmouse();
if (vim_strchr(p_cpo, CPO_UNDO) != NULL) {
if (vim_strchr(p_cpo, kCpoUndo) != NULL) {
no_u_sync--;
}
+1 -1
View File
@@ -490,7 +490,7 @@ void ex_listdo(exarg_T *eap)
char *save_ei = NULL;
// Temporarily override SHM_OVER and SHM_OVERALL to avoid that file
// Temporarily override kShmOver and kShmOverall to avoid that file
// message overwrites output from the command.
msg_listdo_overwrite++;
+4 -4
View File
@@ -4251,7 +4251,7 @@ void separate_nextcmd(exarg_T *eap)
|| *p == '\n') {
// We remove the '\' before the '|', unless EX_CTRLV is used
// AND 'b' is present in 'cpoptions'.
if ((vim_strchr(p_cpo, CPO_BAR) == NULL
if ((vim_strchr(p_cpo, kCpoBar) == NULL
|| !(eap->argt & EX_CTRLV)) && *(p - 1) == '\\') {
STRMOVE(p - 1, p); // remove the '\'
p--;
@@ -6353,7 +6353,7 @@ static void ex_read(exarg_T *eap)
i = readfile(curbuf->b_ffname, curbuf->b_fname,
eap->line2, 0, (linenr_T)MAXLNUM, eap, 0, false);
} else {
if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) {
if (vim_strchr(p_cpo, kCpoAltread) != NULL) {
setaltfname(eap->arg, eap->arg, 1);
}
i = readfile(eap->arg, NULL,
@@ -6453,7 +6453,7 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged)
}
last_chdir_reason = NULL;
shorten_fnames(vim_strchr(p_cpo, CPO_NOSYMLINKS) == NULL);
shorten_fnames(vim_strchr(p_cpo, kCpoNosymlinks) == NULL);
if (trigger_dirchanged) {
do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false);
@@ -6863,7 +6863,7 @@ 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) {
if (do_execreg(c, true, vim_strchr(p_cpo, kCpoExecbuf) != NULL, true) == FAIL) {
beep_flush();
return;
}
+1 -1
View File
@@ -1480,7 +1480,7 @@ static int command_line_execute(VimState *state, int key)
|| s->c == '\r'
|| s->c == K_KENTER
|| (s->c == ESC
&& (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL))) {
&& (!KeyTyped || vim_strchr(p_cpo, kCpoEsc) != NULL))) {
// In Ex mode a backslash escapes a newline.
if (exmode_active
&& s->c != ESC
+1 -1
View File
@@ -295,7 +295,7 @@ void *vim_findfile_init(char *path, char *filename, size_t filenamelen, char *st
// 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)
&& (!tagfile || vim_strchr(p_cpo, kCpoDottag) == NULL)
&& rel_fname != NULL) {
size_t len = (size_t)(path_tail(rel_fname) - rel_fname);
+6 -6
View File
@@ -124,7 +124,7 @@ void filemess(buf_T *buf, char *name, char *s)
// For further ones overwrite the previous one, reset msg_scroll before
// calling filemess().
int msg_scroll_save = msg_scroll;
if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
if (shortmess(kShmOverall) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
msg_scroll = false;
}
if (!msg_scroll) { // wait a bit when overwriting an error msg
@@ -260,7 +260,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (curbuf->b_ffname == NULL
&& !filtering
&& fname != NULL
&& vim_strchr(p_cpo, CPO_FNAMER) != NULL
&& vim_strchr(p_cpo, kCpoFnamer) != NULL
&& !(flags & READ_DUMMY)) {
if (set_rw_fname(fname, sfname) == FAIL) {
goto theend;
@@ -344,7 +344,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
}
}
if (((shortmess(SHM_OVER) && !msg_listdo_overwrite) || curbuf->b_help) && p_verbose == 0) {
if (((shortmess(kShmOver) && !msg_listdo_overwrite) || curbuf->b_help) && p_verbose == 0) {
msg_scroll = false; // overwrite previous file message
} else {
msg_scroll = true; // don't overwrite previous file message
@@ -1783,7 +1783,7 @@ failed:
#endif
if (curbuf->b_p_ro) {
buflen += snprintf(IObuff + buflen, (size_t)(IOSIZE - buflen), "%s",
shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"));
shortmess(kShmRo) ? _("[RO]") : _("[readonly]"));
c = true;
}
if (read_no_eol_lnum) {
@@ -2210,7 +2210,7 @@ void msg_add_lines(int insert_space, linenr_T lnum, off_T nchars)
{
size_t len = strlen(IObuff);
if (shortmess(SHM_LINES)) {
if (shortmess(kShmLines)) {
snprintf(IObuff + len, IOSIZE - len,
_("%s%" PRId64 "L, %" PRId64 "B"), // l10n: L as in line, B as in byte
insert_space ? " " : "", (int64_t)lnum, (int64_t)nchars);
@@ -3205,7 +3205,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
curbuf->b_flags |= BF_CHECK_RO; // check for RO again
curbuf->b_keep_filetype = true; // don't detect 'filetype'
if (readfile(buf->b_ffname, buf->b_fname, 0, 0,
(linenr_T)MAXLNUM, &ea, flags, shortmess(SHM_FILEINFO)) != OK) {
(linenr_T)MAXLNUM, &ea, flags, shortmess(kShmFileinfo)) != OK) {
if (!aborting()) {
semsg(_("E321: Could not reload \"%s\""), buf->b_fname);
}
+13 -51
View File
@@ -52,6 +52,8 @@
#include "indent.c.generated.h"
#include "options_keysets.generated.h"
/// Set the integer values corresponding to the string setting of 'vartabstop'.
/// "array" will be set, caller must free it if needed.
///
@@ -747,7 +749,7 @@ int get_number_indent(linenr_T lnum)
pos.lnum = 0;
// In format_lines() (i.e. not insert mode), fo+=q is needed too...
if ((State & MODE_INSERT) || has_format_option(FO_Q_COMS)) {
if ((State & MODE_INSERT) || has_format_option(kFoQComs)) {
lead_len = get_leader_len(ml_get(lnum), NULL, false, true);
}
regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC);
@@ -780,59 +782,19 @@ int get_number_indent(linenr_T lnum)
/// @param wp when NULL: only check "briopt"
///
/// @return FAIL for failure, OK otherwise.
bool briopt_check(char *briopt, win_T *wp)
void 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 = empty_string_option;
if (briopt != NULL) {
p = briopt;
} else if (wp != NULL) {
p = wp->w_p_briopt;
}
while (*p != NUL) {
// Note: Keep this in sync with opt_briopt_values.
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++;
}
}
if (wp == NULL) {
return OK;
return; // Setting the global value: nothing to apply to a window.
}
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;
// 'breakindentopt' is stored as its reified keyset (validated when set); just map it onto the
// applied per-window fields. A NULL keyset (before the option is set) means all-default.
OptKeyDict_briopt *v = wp->w_p_briopt;
wp->w_briopt_shift = (v != NULL && HAS_KEY(v, briopt, shift)) ? (int)v->shift : 0;
wp->w_briopt_min = (v != NULL && HAS_KEY(v, briopt, min)) ? (int)v->min : 20;
wp->w_briopt_sbr = (v != NULL && HAS_KEY(v, briopt, sbr));
wp->w_briopt_list = (v != NULL && HAS_KEY(v, briopt, list)) ? (int)v->list : 0;
wp->w_briopt_vcol = (v != NULL && HAS_KEY(v, briopt, column)) ? (int)v->column : 0;
}
// Return appropriate space number for breakindent, taking influencing
+11 -11
View File
@@ -1949,7 +1949,7 @@ void insertchar(int c, int flags, int second_indent)
int force_format = flags & INSCHAR_FORMAT;
const int textwidth = comp_textwidth(force_format);
const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
const bool fo_ins_blank = has_format_option(kFoInsBlank);
// Try to break the line in two or more pieces when:
// - Always do this if we have been called to do formatting only.
@@ -1971,7 +1971,7 @@ void insertchar(int c, int flags, int second_indent)
&& !(State & VREPLACE_FLAG)
&& *get_cursor_pos_ptr() != NUL)
&& (curwin->w_cursor.lnum != Ins.start.lnum
|| ((!has_format_option(FO_INS_LONG)
|| ((!has_format_option(kFoInsLong)
|| Ins.start_textlen <= (colnr_T)textwidth)
&& (!fo_ins_blank
|| Ins.start_blank_vcol <= (colnr_T)textwidth)))))) {
@@ -2265,7 +2265,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
// insertion (or moving the cursor), but it's required when appending
// a line and having it end in a space. But only do it when something
// was actually inserted, otherwise undo won't work.
if (!Ins.need_undo && has_format_option(FO_AUTO)) {
if (!Ins.need_undo && has_format_option(kFoAuto)) {
pos_T tpos = curwin->w_cursor;
// When the cursor is at the end of the line after a space the
@@ -2304,7 +2304,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
// Do this when ESC was used or moving the cursor up/down.
// Check for the old position still being valid, just in case the text
// got changed unexpectedly.
if (!nomove && Ins.did_ai && (esc || (vim_strchr(p_cpo, CPO_INDENT) == NULL
if (!nomove && Ins.did_ai && (esc || (vim_strchr(p_cpo, kCpoIndent) == NULL
&& curwin->w_cursor.lnum !=
end_insert_pos->lnum))
&& end_insert_pos->lnum <= curbuf->b_ml.ml_line_count) {
@@ -3097,7 +3097,7 @@ static bool ins_esc(int *count, int cmdchar, bool nomove)
if (--*count > 0) { // repeat what was typed
// Vi repeats the insert without replacing characters.
if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) {
if (vim_strchr(p_cpo, kCpoReplcnt) != NULL) {
State &= ~REPLACE_FLAG;
}
@@ -3435,8 +3435,8 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// When "aw" is in 'formatoptions' we must delete the space at
// the end of the line, otherwise the line will be broken
// again when auto-formatting.
if (has_format_option(FO_AUTO)
&& has_format_option(FO_WHITE_PAR)) {
if (has_format_option(kFoAuto)
&& has_format_option(kFoWhitePar)) {
const char *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum);
int len = get_cursor_line_len();
if (len > 0 && ptr[len - 1] == ' ') {
@@ -3675,7 +3675,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// We can emulate the vi behaviour by pretending there is a dollar
// displayed even when there isn't.
// --pkv Sun Jan 19 01:56:40 EST 2003
if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) {
if (vim_strchr(p_cpo, kCpoBackspace) != NULL && dollar_vcol == -1) {
dollar_vcol = curwin->w_virtcol;
}
@@ -4023,7 +4023,7 @@ static bool ins_tab(void)
}
// When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces.
if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
if (vim_strchr(p_cpo, kCpoListwm) == NULL) {
curwin->w_p_list = false;
}
@@ -4182,7 +4182,7 @@ bool ins_eol(int c)
AppendToRedobuff(NL_STR);
bool i = open_line(FORWARD,
has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0,
has_format_option(kFoRetComs) ? OPENLINE_DO_COM : 0,
old_indent, NULL);
old_indent = 0;
Ins.can_cindent = true;
@@ -4338,7 +4338,7 @@ colnr_T get_nolist_virtcol(void)
|| curwin->w_cursor.lnum > curwin->w_buffer->b_ml.ml_line_count) {
return 0;
}
if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
if (curwin->w_p_list && vim_strchr(p_cpo, kCpoListwm) == NULL) {
return getvcol_nolist(&curwin->w_cursor);
}
validate_virtcol(curwin);
+10 -10
View File
@@ -2005,7 +2005,7 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags,
for (int i = 0; i < count && !got_int && !ins_compl_interrupted(); i++) {
FILE *fp = os_fopen(files[i], "r"); // open dictionary file
if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN) && !compl_autocomplete) {
if (flags != DICT_EXACT && !shortmess(kShmCompletionscan) && !compl_autocomplete) {
vim_snprintf(IObuff, IOSIZE, _("Scanning dictionary: %s"), files[i]);
msg_progress(IObuff, "nvim.completion", "running", HLF_R, false, true);
}
@@ -2768,7 +2768,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval)
ins_compl_free();
compl_started = false;
compl_matches = 0;
if (!shortmess(SHM_COMPLETIONMENU)) {
if (!shortmess(kShmCompletionmenu)) {
msg_clr_cmdline(); // necessary for "noshowmode"
}
ctrl_x_mode = CTRL_X_NORMAL;
@@ -3898,7 +3898,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
st->dict = st->ins_buf->b_fname;
st->dict_f = DICT_EXACT;
}
if (!shortmess(SHM_COMPLETIONSCAN) && !compl_autocomplete) {
if (!shortmess(kShmCompletionscan) && !compl_autocomplete) {
vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"),
st->ins_buf->b_fname == NULL
? buf_spname(st->ins_buf)
@@ -3937,7 +3937,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
compl_type = CTRL_X_BUFNAMES;
} else if (*st->e_cpt == ']' || *st->e_cpt == 't') {
compl_type = CTRL_X_TAGS;
if (!shortmess(SHM_COMPLETIONSCAN) && !compl_autocomplete) {
if (!shortmess(kShmCompletionscan) && !compl_autocomplete) {
vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags."));
msg_progress(IObuff, "nvim.completion", "running", HLF_R, false, true);
}
@@ -5961,7 +5961,7 @@ static int get_userdefined_compl_info(colnr_T curs_col, Callback *cb, int *start
}
ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL;
if (!shortmess(SHM_COMPLETIONMENU)) {
if (!shortmess(kShmCompletionmenu)) {
msg_clr_cmdline();
}
return FAIL;
@@ -6158,7 +6158,7 @@ static int ins_compl_start(void)
}
if (compl_status_adding()) {
if (!shortmess(SHM_COMPLETIONMENU)) {
if (!shortmess(kShmCompletionmenu)) {
edit_submode_pre = _(" Adding");
}
if (ctrl_x_mode_line_or_eval()) {
@@ -6179,7 +6179,7 @@ static int ins_compl_start(void)
compl_startpos.col = compl_col;
}
if (!shortmess(SHM_COMPLETIONMENU) && !compl_autocomplete) {
if (!shortmess(kShmCompletionmenu) && !compl_autocomplete) {
if (compl_cont_status & CONT_LOCAL) {
edit_submode = _(ctrl_x_msgs[CTRL_X_LOCAL_MSG]);
} else {
@@ -6213,7 +6213,7 @@ static int ins_compl_start(void)
// 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.
if (!shortmess(SHM_COMPLETIONMENU) && !compl_autocomplete) {
if (!shortmess(kShmCompletionmenu) && !compl_autocomplete) {
edit_submode_extra = _("-- Searching...");
edit_submode_highl = HLF_COUNT;
showmode();
@@ -6278,7 +6278,7 @@ static void ins_compl_show_statusmsg(void)
// Show a message about what (completion) mode we're in.
redraw_mode = true;
if (!shortmess(SHM_COMPLETIONMENU)) {
if (!shortmess(kShmCompletionmenu)) {
if (edit_submode_extra != NULL) {
if (!p_smd) {
msg_hist_off = true;
@@ -6360,7 +6360,7 @@ int ins_complete(int c, bool enable_pum)
compl_cont_status &= ~CONT_S_IPOS;
}
if (!shortmess(SHM_COMPLETIONMENU) && !compl_autocomplete) {
if (!shortmess(kShmCompletionmenu) && !compl_autocomplete) {
ins_compl_show_statusmsg();
}
+3 -3
View File
@@ -694,7 +694,7 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag)
/// `<C-H>` => 0x08.
///
/// When "flags" has REPTERM_FROM_PART, trailing <C-v> is included, otherwise it is removed (to make
/// ":map xx ^V" map xx to nothing). When cpo_val contains CPO_BSLASH, a backslash can be used in
/// ":map xx ^V" map xx to nothing). When cpo_val contains kCpoBslash, a backslash can be used in
/// place of <C-v>. All other <C-v> characters are removed.
///
/// @param[in] from What characters to replace.
@@ -709,7 +709,7 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag)
/// REPTERM_NO_SPECIAL do not accept <key> notation
/// REPTERM_NO_SIMPLIFY do not simplify <C-H> into 0x08, etc.
/// @param[out] did_simplify set when some <C-H> code was simplified, unless it is NULL.
/// @param[in] cpo_val The value of 'cpoptions' to use. Only CPO_BSLASH matters.
/// @param[in] cpo_val The value of 'cpoptions' to use. Only kCpoBslash matters.
///
/// @return The same as what `*bufp` is set to.
char *replace_termcodes(const char *const from, const size_t from_len, char **const bufp,
@@ -721,7 +721,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
const char *const end = from + from_len - 1;
// backslash is a special character
const bool do_backslash = (vim_strchr(cpo_val, CPO_BSLASH) == NULL);
const bool do_backslash = (vim_strchr(cpo_val, kCpoBslash) == NULL);
const bool do_special = !(flags & REPTERM_NO_SPECIAL);
bool allocated = (*bufp == NULL);
+2 -2
View File
@@ -461,7 +461,7 @@ static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapa
// With :unmap, literal white space is included in the {lhs}; there is no
// separate {rhs}.
const char *lhs_end = to_parse;
bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
bool do_backslash = (vim_strchr(p_cpo, kCpoBslash) == NULL);
while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) {
if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\'))
&& lhs_end[1] != NUL) {
@@ -1202,7 +1202,7 @@ static char *translate_mapping(const char *const str_in, const char *const cpo_v
garray_T ga;
ga_init(&ga, 1, 40);
const bool cpo_bslash = (vim_strchr(cpo_val, CPO_BSLASH) != NULL);
const bool cpo_bslash = (vim_strchr(cpo_val, kCpoBslash) != NULL);
for (; *str; str++) {
int c = *str;
+1 -1
View File
@@ -423,7 +423,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
// 3. Vi compatible searching: continue at end of previous match.
if (shl->lnum == 0) {
matchcol = 0;
} else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
} else if (vim_strchr(p_cpo, kCpoSearch) == NULL
|| (shl->rm.endpos[0].lnum == 0
&& shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
matchcol = shl->rm.startpos[0].col;
+1 -1
View File
@@ -3533,7 +3533,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
// - there is an old swapfile for the current file
// - the buffer was not recovered
if (!differ && !(curbuf->b_flags & BF_RECOVERED)
&& vim_strchr(p_shm, SHM_ATTENTION) == NULL) {
&& vim_strchr(p_shm, kShmAttention) == NULL) {
sea_choice_T choice = SEA_CHOICE_NONE;
// It's safe to delete the swapfile if all these are true:
+2 -2
View File
@@ -501,7 +501,7 @@ char *msg_strtrunc(const char *s, int force)
char *buf = NULL;
// May truncate message to avoid a hit-return prompt
if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL)
if ((!msg_scroll && !need_wait_return && shortmess(kShmTruncall)
&& !exmode_active && msg_silent == 0 && !ui_has(kUIMessages))
|| force) {
int room;
@@ -1072,7 +1072,7 @@ char *msg_may_trunc(bool force, char *s)
// just in case.
int room = (Rows - cmdline_row - 1) * Columns + sc_col - 1;
if (room > 0
&& (force || (shortmess(SHM_TRUNC) && !exmode_active))
&& (force || (shortmess(kShmTrunc) && !exmode_active))
&& (int)strlen(s) - room > 0) {
int size = vim_strsize(s);
+1 -1
View File
@@ -612,7 +612,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
pos_T end_visual = { 0 };
pos_T start_visual = { 0 };
bool mouse_can_visual = ui_mouse_has(MOUSE_VISUAL);
bool mouse_can_visual = ui_mouse_has(kMouseVisual);
if ((State & (MODE_NORMAL | MODE_INSERT))
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) {
if (which_button == MOUSE_LEFT && mouse_can_visual) {
+1 -1
View File
@@ -823,7 +823,7 @@ int win_col_off(win_T *wp)
int win_col_off2(win_T *wp)
{
if ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
&& vim_strchr(p_cpo, CPO_NUMCOL) != NULL) {
&& vim_strchr(p_cpo, kCpoNumcol) != NULL) {
return number_width(wp) + (*wp->w_p_stc == NUL);
}
return 0;
+6 -6
View File
@@ -781,7 +781,7 @@ static void normal_get_additional_char(NormalState *s)
// Typing CTRL-K gets a digraph.
if (*cp == Ctrl_K && ((nv_cmds[s->idx].cmd_flags & NV_LANG)
|| cp == &s->ca.extra_char)
&& vim_strchr(p_cpo, CPO_DIGRAPH) == NULL) {
&& vim_strchr(p_cpo, kCpoDigraph) == NULL) {
s->c = get_digraph(false);
if (s->c > 0) {
*cp = s->c;
@@ -1379,7 +1379,7 @@ static void normal_redraw(NormalState *s)
}
// show fileinfo after redraw
if (need_fileinfo && !shortmess(SHM_FILEINFO)) {
if (need_fileinfo && !shortmess(kShmFileinfo)) {
fileinfo(false, true, false);
need_fileinfo = false;
}
@@ -2302,7 +2302,7 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock)
foldOpenCursor();
}
// clear any search statistics
if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) {
if (messaging() && !msg_silent && !shortmess(kShmSearchcount)) {
clear_cmdline = true;
}
}
@@ -4394,7 +4394,7 @@ static void nv_percent(cmdarg_T *cap)
// Skip matching parens inside C-style comments, like the "=" operator
// does, but not when "%" is in 'cpoptions' (Vi-compatible) or the
// cursor sits in a line comment (so a match there can still be found).
if (vim_strchr(p_cpo, CPO_MATCH) == NULL && buf_has_cstyle_comments()) {
if (vim_strchr(p_cpo, kCpoMatch) == NULL && buf_has_cstyle_comments()) {
int comment_col = check_linecomment(get_cursor_line_ptr());
if (comment_col == MAXCOL || curwin->w_cursor.col < (colnr_T)comment_col) {
flags = FM_SKIPCOMM;
@@ -5765,7 +5765,7 @@ static void n_opencmd(cmdarg_T *cap)
if (u_save(curwin->w_cursor.lnum - (cap->cmdchar == 'O' ? 1 : 0),
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,
has_format_option(kFoOpenComs) ? OPENLINE_DO_COM : 0,
0, NULL)) {
if (win_cursorline_standout(curwin)) {
// force redraw of cursorline
@@ -5991,7 +5991,7 @@ static void nv_wordcmd(cmdarg_T *cap)
// Another strangeness: When standing on the end of a word "ce" will
// change until the end of the next word, but "cw" will change only one
// character! This is done by setting "flag".
if (vim_strchr(p_cpo, CPO_CHANGEW) != NULL) {
if (vim_strchr(p_cpo, kCpoChangew) != NULL) {
cap->oap->inclusive = true;
word_end = true;
}
+11 -11
View File
@@ -769,7 +769,7 @@ int op_delete(oparg_T *oap)
// marks as if it happened.
goto setmarks;
}
if (vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL) {
if (vim_strchr(p_cpo, kCpoEmptyregion) != NULL) {
beep_flush();
}
return OK;
@@ -942,7 +942,7 @@ int op_delete(oparg_T *oap)
}
// if 'cpoptions' contains '$', display '$' at end of change
if (vim_strchr(p_cpo, CPO_DOLLAR) != NULL
if (vim_strchr(p_cpo, kCpoDollar) != NULL
&& oap->op_type == OP_CHANGE
&& oap->end.lnum == curwin->w_cursor.lnum
&& !oap->is_VIsual) {
@@ -1894,7 +1894,7 @@ int do_join(size_t count, bool insert_space, bool save_undo, bool use_formatopti
int sumsize = 0; // size of the long new line
int ret = OK;
int *comments = NULL;
bool remove_comments = use_formatoptions && has_format_option(FO_REMOVE_COMS);
bool remove_comments = use_formatoptions && has_format_option(kFoRemoveComs);
bool prev_was_comment = false;
assert(count >= 1);
@@ -1939,9 +1939,9 @@ int do_join(size_t count, bool insert_space, bool save_undo, bool use_formatopti
&& *curr != ')'
&& sumsize != 0
&& endcurr1 != TAB
&& (!has_format_option(FO_MBYTE_JOIN)
&& (!has_format_option(kFoMbyteJoin)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
&& (!has_format_option(kFoMbyteJoin2)
|| (utf_ptr2char(curr) < 0x100 && !utf_eat_space(endcurr1))
|| (endcurr1 < 0x100
&& !utf_eat_space(utf_ptr2char(curr))))) {
@@ -2062,7 +2062,7 @@ int do_join(size_t count, bool insert_space, bool save_undo, bool use_formatopti
// 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);
(vim_strchr(p_cpo, kCpoJoincol) != NULL ? currsize : col);
check_cursor_col(curwin);
curwin->w_cursor.coladd = 0;
@@ -3285,7 +3285,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
bool include_line_break = false;
// Yank can be redone when 'y' is in 'cpoptions', but not when yanking
// for the clipboard.
const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank;
const bool redo_yank = vim_strchr(p_cpo, kCpoYank) != NULL && !gui_yank;
// Avoid a problem with unwanted linebreaks in block mode
reset_lbr();
@@ -3334,7 +3334,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (cap->cmdchar == '/' || cap->cmdchar == '?') { // was a search
// If 'cpoptions' does not contain 'r', insert the search
// pattern to really repeat the same command.
if (vim_strchr(p_cpo, CPO_REDO) == NULL) {
if (vim_strchr(p_cpo, kCpoRedo) == NULL) {
AppendToRedobuffLit(cap->searchbuf, -1);
}
AppendToRedobuff(NL_STR);
@@ -3602,7 +3602,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
// For delete, change and yank, it's an error to operate on an
// empty region, when 'E' included in 'cpoptions' (Vi compatible).
empty_region_error = (oap->empty
&& vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL);
&& vim_strchr(p_cpo, kCpoEmptyregion) != NULL);
// Force a redraw when operating on an empty Visual region, when
// 'modifiable is off or creating a fold.
@@ -3668,7 +3668,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
op_delete(oap);
// save cursor line for undo if it wasn't saved yet
if (oap->motion_type == kMTLineWise
&& has_format_option(FO_AUTO)
&& has_format_option(kFoAuto)
&& u_save_cursor() == OK) {
auto_format(false, true);
}
@@ -3724,7 +3724,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
break;
case OP_FILTER:
if (vim_strchr(p_cpo, CPO_FILTER) != NULL) {
if (vim_strchr(p_cpo, kCpoFilter) != NULL) {
AppendToRedobuff("!\r"); // Use any last used !cmd.
} else {
bangredo = true; // do_bang() will put cmd in redo buffer.
+375 -20
View File
@@ -159,6 +159,7 @@ static char *p_vsts_nopaste;
// ignored and they are not printed.
#include "options.generated.h"
#include "options_keysets.generated.h"
#include "options_map.generated.h"
static int p_bin_dep_opts[] = {
@@ -471,6 +472,19 @@ static void alloc_options_default(void)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
options[opt_idx].def_val = optval_copy(options[opt_idx].def_val);
// A dict option declares its default as a ":set" string (options.lua). Reify it into a
// keyset once here, so the default is a keyset, for reset and ":set opt&".
if (option_has_type(opt_idx, kOptValTypeDict)
&& options[opt_idx].def_val.type == kOptValTypeString) {
const char *emsg = NULL;
OptVal reified = opt_dict_from_string(opt_idx, options[opt_idx].def_val.data.string.data,
NULL, 0, &emsg);
assert(emsg == NULL); // A built-in default must be valid.
(void)emsg;
optval_free(options[opt_idx].def_val);
options[opt_idx].def_val = reified;
}
}
}
@@ -1446,6 +1460,15 @@ OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T prefix, c
newval = CSTR_AS_OPTVAL(newval_str);
break;
}
case kOptValTypeDict: {
// Merge at the string level (serialize the old keyset, apply =/+=/-=); set_option() reifies and
// validates the result. stropt_get_newval() doesn't dereference varp except for 'keywordprg'.
char *oldval_str = opt_serialize(oldval.data.dictval.ptr, oldval.data.dictval.table);
char *newval_str = stropt_get_newval(opt_idx, argp, varp, oldval_str, &op);
xfree(oldval_str);
newval = CSTR_AS_OPTVAL(newval_str);
break;
}
}
return newval;
@@ -2009,20 +2032,17 @@ bool parse_winhl_opt(const char *winhl, win_T *wp)
}
}
while (*p) {
const char *colon = strchr(p, ':');
if (!colon) {
return false;
const char *key, *val;
size_t keylen, vallen;
while (option_next_keyval(&p, &key, &keylen, &val, &vallen)) {
if (val == NULL) {
return false; // every part must be "group:hl"
}
size_t nlen = (size_t)(colon - p);
const char *hi = colon + 1;
const char *commap = xstrchrnul(hi, ',');
size_t len = (size_t)(commap - hi);
int hl_id = len ? syn_check_group(hi, len) : -1;
int hl_id = vallen ? syn_check_group(val, vallen) : -1;
if (hl_id == 0) {
return false;
}
int hl_id_link = nlen ? syn_check_group(p, nlen) : 0;
int hl_id_link = keylen ? syn_check_group(key, keylen) : 0;
if (hl_id_link == 0) {
return false;
}
@@ -2032,8 +2052,6 @@ bool parse_winhl_opt(const char *winhl, win_T *wp)
attrs.rgb_ae_attr |= HL_GLOBAL;
ns_hl_def(ns_hl, hl_id_link, attrs, hl_id, NULL);
}
p = *commap ? commap + 1 : "";
}
if (wp != NULL) {
@@ -2118,6 +2136,12 @@ void apply_optionset_autocmd_now(OptIndex opt_idx, int opt_flags, OptVal oldval,
}
apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
reset_v_option_vars();
// set_vim_var_tv() copied each typval, so release the strings optval_as_tv() freshly allocated.
optval_as_tv_free(oldval, oldval_tv);
optval_as_tv_free(oldval_g, oldval_g_tv);
optval_as_tv_free(oldval_l, oldval_l_tv);
optval_as_tv_free(newval, newval_tv);
}
/// For 'modified', the event is deferred.
@@ -3338,6 +3362,86 @@ OptIndex find_option(const char *const name)
return find_option_len(name, strlen(name));
}
/// Free the owned resources of a dict option value: its `String` fields and the keyset.
static void opt_dict_free(OptDict s)
{
if (s.ptr == NULL) {
return;
}
for (const KeySetLink *f = s.table; f->str != NULL; f++) {
if (f->type == kObjectTypeString) {
api_free_string(*(String *)((char *)s.ptr + f->ptr_off));
}
}
xfree(s.ptr);
}
/// Deep-copy a dict option value (keyset + its `String` fields).
static OptDict opt_dict_dup(OptDict s)
{
if (s.ptr == NULL) {
return s;
}
void *ptr = xmemdup(s.ptr, s.size);
for (const KeySetLink *f = s.table; f->str != NULL; f++) {
if (f->type == kObjectTypeString) {
String *field = (String *)((char *)ptr + f->ptr_off);
*field = copy_string(*field, NULL);
}
}
return (OptDict){ ptr, s.table, s.size };
}
/// Compare two dict option values: same keys present (`is_set_`) and same field values.
static bool opt_dict_equal(OptDict a, OptDict b)
{
if (a.ptr == NULL || b.ptr == NULL) {
return a.ptr == b.ptr;
}
for (const KeySetLink *f = a.table; f->str != NULL; f++) {
const void *fa = (const char *)a.ptr + f->ptr_off;
const void *fb = (const char *)b.ptr + f->ptr_off;
switch (f->type) {
case kObjectTypeInteger:
if (*(const Integer *)fa != *(const Integer *)fb) {
return false;
}
break;
case kObjectTypeString: {
const String *sa = fa;
const String *sb = fb;
if (sa->size != sb->size || (sa->size != 0 && memcmp(sa->data, sb->data, sa->size) != 0)) {
return false;
}
break;
}
default: // Boolean and other scalars
if (*(const Boolean *)fa != *(const Boolean *)fb) {
return false;
}
break;
}
}
// is_set__<abbr>_ (OptionalKeys) is the first member of every keyset.
return *(const OptionalKeys *)a.ptr == *(const OptionalKeys *)b.ptr;
}
/// Validate a ":set" string against a dict option's grammar and reify it into a fresh
/// heap keyset. On success returns an owned struct OptVal; on failure sets `errmsg` and returns Nil.
static OptVal opt_dict_from_string(OptIndex opt_idx, const char *str, char *errbuf,
size_t errbuflen, const char **errmsg)
{
const OptDictInfo *si = opt_dict_info(opt_idx);
const char *err = opt_strings_check(str, si->schema, errbuf, errbuflen);
if (err != NULL) {
*errmsg = err;
return NIL_OPTVAL;
}
void *ptr = xcalloc(1, si->size);
opt_fill(str, si->get_field, ptr);
return (OptVal){ .type = kOptValTypeDict, .data.dictval = { ptr, si->table, si->size } };
}
/// Free an allocated OptVal.
void optval_free(OptVal o)
{
@@ -3352,6 +3456,9 @@ void optval_free(OptVal o)
api_free_string(o.data.string);
}
break;
case kOptValTypeDict:
opt_dict_free(o.data.dictval);
break;
}
}
@@ -3365,6 +3472,9 @@ OptVal optval_copy(OptVal o)
return o;
case kOptValTypeString:
return STRING_OPTVAL(copy_string(o.data.string, NULL));
case kOptValTypeDict:
return (OptVal){ .type = kOptValTypeDict,
.data.dictval = opt_dict_dup(o.data.dictval) };
}
UNREACHABLE;
}
@@ -3387,6 +3497,8 @@ bool optval_equal(OptVal o1, OptVal o2)
return o1.data.string.size == o2.data.string.size
&& (o1.data.string.data == o2.data.string.data
|| strnequal(o1.data.string.data, o2.data.string.data, o1.data.string.size));
case kOptValTypeDict:
return opt_dict_equal(o1.data.dictval, o2.data.dictval);
}
UNREACHABLE;
}
@@ -3423,6 +3535,13 @@ OptVal optval_from_varp(OptIndex opt_idx, void *varp)
return NUMBER_OPTVAL(*(OptInt *)varp);
case kOptValTypeString:
return STRING_OPTVAL(cstr_as_string(*(char **)varp));
case kOptValTypeDict: {
// Alias the stored keyset (like the string case aliases the stored char*): free frees it, copy
// dupes it. The table/size travel with the value so free/copy need no option index.
const OptDictInfo *si = opt_dict_info(opt_idx);
return (OptVal){ .type = kOptValTypeDict,
.data.dictval = { *(void **)varp, si->table, si->size } };
}
}
UNREACHABLE;
}
@@ -3454,6 +3573,11 @@ static void set_option_varp(OptIndex opt_idx, void *varp, OptVal value, bool fre
case kOptValTypeString:
*(char **)varp = value.data.string.data;
return;
case kOptValTypeDict:
// Move the keyset pointer in (ownership transfers, like the string case). The old value was
// already freed above when free_oldval is set.
*(void **)varp = value.data.dictval.ptr;
return;
}
UNREACHABLE;
}
@@ -3476,6 +3600,14 @@ static char *optval_to_cstr(OptVal o)
snprintf(buf, o.data.string.size + 3, "\"%s\"", o.data.string.data);
return buf;
}
case kOptValTypeDict: {
char *s = opt_serialize(o.data.dictval.ptr, o.data.dictval.table);
size_t len = strlen(s);
char *buf = xmalloc(len + 3);
snprintf(buf, len + 3, "\"%s\"", s);
xfree(s);
return buf;
}
}
UNREACHABLE;
}
@@ -3499,10 +3631,96 @@ Object optval_as_object(OptVal o)
return INTEGER_OBJ(o.data.number);
case kOptValTypeString:
return STRING_OBJ(o.data.string);
case kOptValTypeDict:
// The API surfaces the string form; the returned Object owns a fresh serialization.
return STRING_OBJ(cstr_as_string(opt_serialize(o.data.dictval.ptr, o.data.dictval.table)));
}
UNREACHABLE;
}
/// Converts an option value to its structured form.
///
/// @return Object allocated in `arena`.
Object optval_to_struct(OptIndex opt_idx, OptVal value, Arena *arena)
{
if (value.type != kOptValTypeString && value.type != kOptValTypeDict) {
return optval_as_object(value); // boolean/number/nil scalar
}
const uint32_t flags = options[opt_idx].flags;
const OptSchemaItem *schema = options[opt_idx].schema;
bool owned = false;
char *str;
if (value.type == kOptValTypeDict) {
str = opt_serialize(value.data.dictval.ptr, value.data.dictval.table);
owned = true;
} else {
str = value.data.string.data != NULL ? value.data.string.data : "";
}
Object rv;
// Plain string option: no list/map structure.
if (!(flags & (kOptFlagComma | kOptFlagFlagList))) {
rv = STRING_OBJ(CSTR_TO_ARENA_STR(arena, str));
} else if ((flags & kOptFlagFlagList) && !(flags & kOptFlagComma)) {
// Single-char flag list, e.g. 'shortmess': each character is a flag.
size_t n = strlen(str);
Dict d = arena_dict(arena, n);
for (size_t i = 0; i < n; i++) {
PUT_C(d, arena_memdupz(arena, &str[i], 1), BOOLEAN_OBJ(true));
}
rv = DICT_OBJ(d);
} else {
// Comma-separated list/map/set. Upper-bound the item count for arena sizing.
size_t nparts = 1;
for (char *p = str; *p != NUL; p++) {
nparts += (*p == ',');
}
const bool as_map = (flags & kOptFlagColon) || (flags & kOptFlagFlagList) || schema != NULL;
Dict d = as_map ? arena_dict(arena, nparts) : (Dict)ARRAY_DICT_INIT;
Array a = as_map ? (Array)ARRAY_DICT_INIT : arena_array(arena, nparts);
char *item = xmalloc(strlen(str) + 1);
for (char *p = str; *p != NUL;) {
copy_option_part(&p, item, strlen(str) + 1, ",");
if (*item == NUL) {
continue; // skip empty parts, e.g. "a,,b"
}
if (!as_map) {
// Note: the raw string preserves the ",," literal-comma convention (e.g. 'isfname'); the
// structured view splits on every comma and does not reconstruct literal commas.
ADD_C(a, STRING_OBJ(CSTR_TO_ARENA_STR(arena, item)));
continue;
}
char *colon = strchr(item, ':');
char *val = NULL;
if (colon != NULL) {
*colon = NUL;
val = colon + 1;
}
String key = CSTR_TO_ARENA_STR(arena, item);
Object v;
if (flags & kOptFlagFlagList) {
v = BOOLEAN_OBJ(true); // comma flag list ('whichwrap'): each item is a flag
} else if (val == NULL) {
v = BOOLEAN_OBJ(true); // bare flag in a typed/colon map
} else {
// A key's value stays a string, even for a num/enum key: option sub-value types are not a
// stable contract (Vim options are "stringly typed"), so we expose structure, not types.
v = STRING_OBJ(CSTR_TO_ARENA_STR(arena, val));
}
PUT_C(d, key.data, v);
}
xfree(item);
rv = as_map ? DICT_OBJ(d) : ARRAY_OBJ(a);
}
if (owned) {
xfree(str);
}
return rv;
}
/// Convert an API Object to an OptVal.
OptVal object_as_optval(Object o, bool *error)
{
@@ -3537,7 +3755,9 @@ OptVal object_as_optval_for(OptIndex opt_idx, Object o, set_op_T op, bool *error
const uint32_t flags = options[opt_idx].flags;
const bool is_list = flags & (kOptFlagComma | kOptFlagFlagList);
const bool is_map = flags & kOptFlagColon; // "key:value" list, e.g. 'listchars'.
// "key:value" list, e.g. 'listchars', or a dict option, e.g. 'breakindentopt' (which
// accepts a Dict even without kOptFlagColon, mirroring optval_to_struct()'s `as_map`).
const bool is_map = (flags & kOptFlagColon) || option_has_type(opt_idx, kOptValTypeDict);
const bool is_flaglist = flags & kOptFlagFlagList; // single-char flag list, e.g. 'shortmess'.
const bool is_comma = flags & kOptFlagComma;
const bool allow_dup = !(flags & kOptFlagNoDup);
@@ -3552,7 +3772,9 @@ OptVal object_as_optval_for(OptIndex opt_idx, Object o, set_op_T op, bool *error
type_ok = option_has_type(opt_idx, kOptValTypeNumber);
break;
case kObjectTypeString:
// Struct-stored options take their ":set" string here; set_option() reifies it.
type_ok = option_has_type(opt_idx, kOptValTypeString)
|| option_has_type(opt_idx, kOptValTypeDict)
|| opt_idx == kOptWildchar || opt_idx == kOptWildcharm;
break;
case kObjectTypeArray:
@@ -3620,6 +3842,17 @@ OptVal object_as_optval_for(OptIndex opt_idx, Object o, set_op_T op, bool *error
char *kv = concat_str(k.data, ":");
GA_APPEND(char *, &ga, concat_str(kv, v.data.string.data));
xfree(kv);
} else if (v.type == kObjectTypeInteger) {
// Typed map value, e.g. 'diffopt' `{ context = 4 }` -> "context:4".
char buf[NUMBUFLEN];
snprintf(buf, sizeof(buf), ":%" PRId64, (int64_t)v.data.integer);
char *kv = concat_str(k.data, buf);
GA_APPEND(char *, &ga, kv);
} else if (v.type == kObjectTypeBoolean || v.type == kObjectTypeNil) {
// Bare flag in a typed map, e.g. 'diffopt' `{ internal = true }` -> "internal".
if (v.type == kObjectTypeBoolean && v.data.boolean) {
GA_APPEND(char *, &ga, xstrdup(k.data));
}
} else {
*error = true;
GA_DEEP_CLEAR_PTR(&ga);
@@ -4048,6 +4281,19 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
const char *errmsg = NULL;
// Struct-stored options keep a reified keyset as the stored value. Every set path (":set", the
// API, Vimscript, a merge) funnels through here as a ":set" string; validate and reify it once,
// the single choke point. Reset-to-default/global (":set opt&/<") already produces a keyset.
if (value.type == kOptValTypeString && option_has_type(opt_idx, kOptValTypeDict)) {
OptVal reified = opt_dict_from_string(opt_idx, value.data.string.data, errbuf, errbuflen,
&errmsg);
optval_free(value);
if (errmsg != NULL) {
return errmsg;
}
value = reified;
}
if (!direct) {
errmsg = validate_option_value(opt_idx, &value, opt_flags, errbuf, errbuflen);
@@ -4862,6 +5108,16 @@ static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
xfree(part);
return FAIL;
}
case kOptValTypeDict: {
// Written back as its ":set" string, e.g. `set diffopt=internal,filler,...`.
char *value_str = opt_serialize(value.data.dictval.ptr, value.data.dictval.table);
bool ok = fprintf(fd, "%s %s=", cmd, name) >= 0 && put_escstr(fd, value_str, 2) == OK;
xfree(value_str);
if (!ok) {
return FAIL;
}
break;
}
}
if (put_eol(fd) < 0) {
@@ -5335,6 +5591,21 @@ static char *copy_option_val(const char *val)
return xstrdup(val);
}
/// Deep-copy a dict (keyset) window/buffer option value, or NULL.
static void *copy_opt_dict(void *keyset, OptIndex opt_idx)
{
const OptDictInfo *si = opt_dict_info(opt_idx);
return opt_dict_dup((OptDict){ keyset, si->table, si->size }).ptr;
}
/// Free a dict option value and clear the pointer.
static void clear_opt_dict(void **keyset, OptIndex opt_idx)
{
const OptDictInfo *si = opt_dict_info(opt_idx);
opt_dict_free((OptDict){ *keyset, si->table, si->size });
*keyset = NULL;
}
/// 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.
@@ -5359,7 +5630,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_wrap_save = from->wo_wrap_save;
to->wo_lbr = from->wo_lbr;
to->wo_bri = from->wo_bri;
to->wo_briopt = copy_option_val(from->wo_briopt);
to->wo_briopt = copy_opt_dict(from->wo_briopt, kOptBreakindentopt);
to->wo_scb = from->wo_scb;
to->wo_scb_save = from->wo_scb_save;
to->wo_sms = from->wo_sms;
@@ -5435,7 +5706,7 @@ static void check_winopt(winopt_T *wop)
check_string_option(&wop->wo_culopt);
check_string_option(&wop->wo_cc);
check_string_option(&wop->wo_cocu);
check_string_option(&wop->wo_briopt);
// wo_briopt: keyset (may be NULL until set); briopt_check() handles NULL.
check_string_option(&wop->wo_winhl);
check_string_option(&wop->wo_lcs);
check_string_option(&wop->wo_fcs);
@@ -5463,7 +5734,7 @@ void clear_winopt(winopt_T *wop)
clear_string_option(&wop->wo_culopt);
clear_string_option(&wop->wo_cc);
clear_string_option(&wop->wo_cocu);
clear_string_option(&wop->wo_briopt);
clear_opt_dict((void **)&wop->wo_briopt, kOptBreakindentopt);
clear_string_option(&wop->wo_winhl);
clear_string_option(&wop->wo_lcs);
clear_string_option(&wop->wo_fcs);
@@ -5481,7 +5752,7 @@ void didset_window_options(win_T *wp, bool valid_cursor)
wp->w_skipcol = 0;
}
check_colorcolumn(NULL, wp);
briopt_check(NULL, wp);
briopt_check(wp);
fill_culopt_flags(NULL, wp);
set_chars_option(wp, wp->w_p_fcs, kFillchars, true, NULL, 0);
set_chars_option(wp, wp->w_p_lcs, kListchars, true, NULL, 0);
@@ -5522,10 +5793,10 @@ void buf_copy_options(buf_T *buf, int flags)
// X no no no true
// no yes no X true
///
if ((vim_strchr(p_cpo, CPO_BUFOPTGLOB) == NULL || !(flags & BCO_ENTER))
if ((vim_strchr(p_cpo, kCpoBufoptglob) == NULL || !(flags & BCO_ENTER))
&& (buf->b_p_initialized
|| (!(flags & BCO_ENTER)
&& vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) {
&& vim_strchr(p_cpo, kCpoBufopt) != NULL))) {
should_copy = false;
}
@@ -6264,6 +6535,14 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c
expand_option_flags,
curbuf, curwin);
// A dict option's varp holds a keyset; expand on its serialized string form instead.
char *option_serialized = NULL;
if (option_has_type(expand_option_idx, kOptValTypeDict)) {
option_serialized = opt_serialize((void *)option_val,
opt_dict_info(expand_option_idx)->table);
option_val = option_serialized;
}
uint32_t option_flags = options[expand_option_idx].flags;
if (option_has_type(expand_option_idx, kOptValTypeNumber)) {
@@ -6275,11 +6554,13 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c
// kOptFlagComma and kOptFlagFlagList.
if (*option_val == NUL) {
xfree(option_serialized);
return FAIL;
}
// Make a copy as we need to inject null characters destructively.
char *option_copy = xstrdup(option_val);
xfree(option_serialized); // struct form copied into option_copy; no longer needed
char *next_val = option_copy;
garray_T ga;
@@ -6381,6 +6662,10 @@ static void option_value2string(vimoption_T *opt, int opt_flags)
"%" PRId64,
(int64_t)(*(OptInt *)varp));
}
} else if (option_has_type(get_opt_idx(opt), kOptValTypeDict)) {
char *str = opt_serialize(*(void **)varp, opt_dict_info(get_opt_idx(opt))->table);
xstrlcpy(NameBuff, str, MAXPATHL);
xfree(str);
} else { // string
varp = *(char **)varp;
@@ -6760,6 +7045,73 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars
return len;
}
/// Iterates comma-separated parts of option, splitting each part at its first ':' into key/value.
/// The returned pointers are slices into the original string: for simple "key:value" list options
/// like 'winhighlight' and 'previewpopup', but not 'listchars'/'fillchars' whose values need
/// decoding.
///
/// Slices are paired lengths; copy with xmemcpyz() if you need a NUL-terminated string.
///
/// A part with no ':' yields `*val == NULL`, i.e. a bare flag such as 'diffopt' "filler". An empty
/// part (from "a,,b" or a leading ',') yields "*keylen == 0" with "*val == NULL"; the caller
/// decides if that is an error (strict key:value options, but not 'diffopt').
///
/// Drive it with a loop, advancing `*p` each step:
///
/// const char *key, *val;
/// size_t keylen, vallen;
/// for (const char *p = value; option_next_keyval(&p, &key, &keylen, &val, &vallen);) {
/// ...
/// }
///
/// @param[in,out] p Cursor into the option string; advanced past the part.
/// @param[out] key Start of the key slice.
/// @param[out] keylen Length of the key slice.
/// @param[out] val Start of the value slice, or NULL if the part had no ':'.
/// @param[out] vallen Length of the value slice (0 when "*val" is NULL).
///
/// @return true if a part was returned, false at the end of the string.
bool option_next_keyval(const char **p, const char **key, size_t *keylen, const char **val,
size_t *vallen)
FUNC_ATTR_NONNULL_ALL
{
const char *s = *p;
if (*s == NUL) {
return false;
}
const char *comma = vim_strchr(s, ',');
const char *end = comma != NULL ? comma : s + strlen(s);
const char *colon = vim_strchr(s, ':');
*key = s;
if (colon != NULL && colon < end) {
*keylen = (size_t)(colon - s);
*val = colon + 1;
*vallen = (size_t)(end - (colon + 1));
} else {
*keylen = (size_t)(end - s);
*val = NULL;
*vallen = 0;
}
*p = comma != NULL ? comma + 1 : end;
return true;
}
/// Compare a (non-NUL-terminated) slice from option_next_keyval() against a name.
///
/// @param slice Start of the slice; may be NULL (e.g. a bare flag's value).
/// @param slicelen Length of the slice.
/// @param name NUL-terminated name to match.
///
/// @return true if "slice[0..slicelen)" equals "name".
bool option_slice_eq(const char *slice, size_t slicelen, const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return slice != NULL && strlen(name) == slicelen && strncmp(slice, name, slicelen) == 0;
}
/// Return true when 'shell' has "csh" in the tail.
int csh_like_shell(void)
{
@@ -6786,8 +7138,11 @@ dict_T *get_winbuf_options(const int bufopt)
void *varp = get_varp(opt);
if (varp != NULL) {
typval_T opt_tv = optval_as_tv(optval_from_varp(opt_idx, varp), true);
OptVal value = optval_from_varp(opt_idx, varp);
typval_T opt_tv = optval_as_tv(value, true);
tv_dict_add_tv(d, opt->fullname, strlen(opt->fullname), &opt_tv);
// tv_dict_add_tv() copied the typval, so release any string optval_as_tv() allocated.
optval_as_tv_free(value, opt_tv);
}
}
}
+2
View File
@@ -52,6 +52,8 @@ static inline const char *optval_type_get_name(const OptValType type)
return "number";
case kOptValTypeString:
return "string";
case kOptValTypeDict:
return "string"; // dict options present as strings at the API/error surface.
}
UNREACHABLE;
}
+40
View File
@@ -50,8 +50,18 @@ typedef enum {
kOptValTypeBoolean,
kOptValTypeNumber,
kOptValTypeString,
kOptValTypeDict, ///< Option stored as a reified keyset (`schema.dict` in options.lua).
} OptValType;
/// Storage for a dict option ("schema" in options.lua): a heap-allocated keyset
/// (`OptKeyDict_<name>`) plus the field table needed to free, copy and serialize it without an
/// option index. The string form is derived on-demand (`opt_serialize()`), never stored.
typedef struct {
void *ptr; ///< Heap keyset (owned), or NULL.
const KeySetLink *table; ///< Field layout (borrowed; points at generated static data).
size_t size; ///< sizeof the keyset, for (re)allocation.
} OptDict;
/// Scopes that an option can support.
typedef enum {
kOptScopeGlobal = 0, ///< Request global option value
@@ -68,6 +78,7 @@ typedef union {
TriState boolean;
OptInt number;
String string;
OptDict dictval;
} OptValData;
/// Option value
@@ -76,6 +87,32 @@ typedef struct {
OptValData data;
} OptVal;
/// Value kind of one key in a dict option (see "schema" in options.lua).
typedef enum {
kOptSchemaFlag, ///< bare flag, e.g. 'diffopt' "filler"
kOptSchemaNum, ///< "key:N" non-negative number, e.g. 'diffopt' "context:4"
kOptSchemaSNum, ///< "key:N" signed number, e.g. 'breakindentopt' "shift:-2"
kOptSchemaEnum, ///< "key:val" where val is one of "enum_values", e.g. "algorithm:patience"
kOptSchemaStr, ///< "key:val" where val is a free string, e.g. 'previewpopup' "highlight:Foo"
} OptSchemaKind;
/// One key of a dict option, generated from its `dict` schema in options.lua.
/// Consumed by opt_strings_check(). A NULL "name" terminates the array.
typedef struct {
const char *name; ///< key name, without a trailing ':'
OptSchemaKind kind;
const char **enum_values; ///< kOptSchemaEnum: NULL-terminated valid values; else NULL
} OptSchemaItem;
/// Per-option handle for a dict option ("schema" in options.lua), generated by
/// `opt_dict_info()`. NULL from `opt_dict_info()` means the option is not a dict option.
typedef struct {
FieldHashfn get_field; ///< Keyset perfect-hash lookup, for opt_fill().
const KeySetLink *table; ///< Field layout, for opt_serialize()/free/copy.
const OptSchemaItem *schema; ///< Grammar, for opt_strings_check() validation.
size_t size; ///< sizeof the keyset, for allocation.
} OptDictInfo;
/// :set operator types
typedef enum {
OP_NONE = 0,
@@ -180,6 +217,9 @@ typedef struct {
const char **values; ///< possible values for string options
const size_t values_len; ///< length of values array
/// Grammar of a dict option ("schema.dict" in options.lua); NULL otherwise.
const OptSchemaItem *schema;
/// callback function to invoke after an option is modified to validate and
/// apply the new value.
opt_did_set_cb_T opt_did_set_cb;
+15 -104
View File
@@ -1,6 +1,7 @@
#pragma once
#include "nvim/macros_defs.h"
#include "nvim/option_defs.h"
#include "nvim/os/os_defs.h"
#include "nvim/sign_defs.h"
#include "nvim/statusline_defs.h"
@@ -57,81 +58,16 @@
#define EOL_DOS 1 // CR NL
#define EOL_MAC 2 // CR
// Formatting options for p_fo 'formatoptions'
#define FO_WRAP 't'
#define FO_WRAP_COMS 'c'
#define FO_RET_COMS 'r'
#define FO_OPEN_COMS 'o'
#define FO_NO_OPEN_COMS '/'
#define FO_Q_COMS 'q'
#define FO_Q_NUMBER 'n'
#define FO_Q_SECOND '2'
#define FO_INS_VI 'v'
#define FO_INS_LONG 'l'
#define FO_INS_BLANK 'b'
#define FO_MBYTE_BREAK 'm' // break before/after multi-byte char
#define FO_MBYTE_JOIN 'M' // no space before/after multi-byte char
#define FO_MBYTE_JOIN2 'B' // no space between multi-byte chars
#define FO_ONE_LETTER '1'
#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
#define FO_AUTO 'a' // automatic formatting
#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
// Formatting options for 'formatoptions': the per-flag kFo* constants are generated from the
// option's `flagchars` schema (options.lua); FO_ALL is the concatenated set for do_set().
#define DFLT_FO_VI "vt"
#define DFLT_FO_VIM "tcqj"
#define FO_ALL "tcro/q2vlb1mMBn,aw]jp" // for do_set()
#define MAX_MCO 6 // fixed value for 'maxcombine'
// characters for the p_cpo option:
#define CPO_ALTREAD 'a' // ":read" sets alternate file name
#define CPO_ALTWRITE 'A' // ":write" sets alternate file name
#define CPO_BAR 'b' // "\|" ends a mapping
#define CPO_BSLASH 'B' // backslash in mapping is not special
#define CPO_SEARCH 'c'
#define CPO_CONCAT 'C' // Don't concatenate sourced lines
#define CPO_DOTTAG 'd' // "./tags" in 'tags' is in current dir
#define CPO_DIGRAPH 'D' // No digraph after "r", "f", etc.
#define CPO_EXECBUF 'e'
#define CPO_EMPTYREGION 'E' // operating on empty region is an error
#define CPO_FNAMER 'f' // set file name for ":r file"
#define CPO_FNAMEW 'F' // set file name for ":w file"
#define CPO_INTMOD 'i' // interrupt a read makes buffer modified
#define CPO_INDENT 'I' // remove auto-indent more often
#define CPO_ENDOFSENT 'J' // need two spaces to detect end of sentence
#define CPO_KOFFSET 'K' // don't wait for key code in mappings
#define CPO_LITERAL 'l' // take char after backslash in [] literal
#define CPO_LISTWM 'L' // 'list' changes wrapmargin
#define CPO_SHOWMATCH 'm'
#define CPO_MATCHBSL 'M' // "%" ignores use of backslashes
#define CPO_NUMCOL 'n' // 'number' column also used for text
#define CPO_LINEOFF 'o'
#define CPO_OVERNEW 'O' // silently overwrite new file
#define CPO_FNAMEAPP 'P' // set file name for ":w >>file"
#define CPO_JOINCOL 'q' // with "3J" use column after first join
#define CPO_REDO 'r'
#define CPO_REMMARK 'R' // remove marks when filtering
#define CPO_BUFOPT 's'
#define CPO_BUFOPTGLOB 'S'
#define CPO_TAGPAT 't' // tag pattern is used for "n"
#define CPO_UNDO 'u' // "u" undoes itself
#define CPO_BACKSPACE 'v' // "v" keep deleted text
#define CPO_FWRITE 'W' // "w!" doesn't overwrite readonly files
#define CPO_ESC 'x'
#define CPO_REPLCNT 'X' // "R" with a count only deletes chars once
#define CPO_YANK 'y'
#define CPO_KEEPRO 'Z' // don't reset 'readonly' on ":w!"
#define CPO_DOLLAR '$'
#define CPO_FILTER '!'
#define CPO_MATCH '%'
#define CPO_PLUS '+' // ":write file" resets 'modified'
#define CPO_REGAPPEND '>' // insert NL when appending to a register
#define CPO_SCOLON ';' // using "," and ";" will skip over char if
// cursor would not move
#define CPO_NOSYMLINKS '~' // don't resolve symlinks when changing directory
#define CPO_CHANGEW '_' // "cw" special-case
// characters for the p_cpo option: the per-flag kCpo* constants are generated from the
// option's `flagchars` schema (options.lua). CPO_VI/CPO_VIM are the Vi/Vim default sets.
// default values for Vim and Vi
#define CPO_VIM "aABceFs_"
#define CPO_VI "aAbBcCdDeEfFiIJKlLmMnoOpPqrRsStuvWxXyZ$!%+>;~_"
@@ -139,17 +75,10 @@
// characters for p_ww option:
#define WW_ALL "bshl<>[]~"
// characters for p_mouse option:
#define MOUSE_NORMAL 'n' // use mouse in Normal mode
#define MOUSE_VISUAL 'v' // use mouse in Visual/Select mode
#define MOUSE_INSERT 'i' // use mouse in Insert mode
#define MOUSE_COMMAND 'c' // use mouse in Command-line mode
#define MOUSE_HELP 'h' // use mouse in help buffers
#define MOUSE_RETURN 'r' // use mouse for hit-return message
// characters for p_mouse option: the per-flag kMouse* constants are generated from the option's
// `flagchars` schema (options.lua).
#define MOUSE_A "nvich" // used for 'a' flag
#define MOUSE_ALL "anvichr" // all possible characters
#define MOUSE_NONE ' ' // don't use Visual selection
#define MOUSE_NONEF 'x' // forced modeless selection
// default vertical and horizontal mouse scroll values.
// Note: This should be in sync with the default mousescroll option.
@@ -158,31 +87,11 @@
#define COCU_ALL "nvic" // flags for 'concealcursor'
/// characters for p_shm option:
enum {
SHM_RO = 'r', ///< Readonly.
SHM_MOD = 'm', ///< Modified.
SHM_LINES = 'l', ///< "L" instead of "lines".
SHM_WRI = 'w', ///< "[w]" instead of "written".
SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS.
SHM_WRITE = 'W', ///< Don't use "written" at all.
SHM_TRUNC = 't', ///< Truncate file messages.
SHM_TRUNCALL = 'T', ///< Truncate all messages.
SHM_OVER = 'o', ///< Overwrite file messages.
SHM_OVERALL = 'O', ///< Overwrite more messages.
SHM_SEARCH = 's', ///< No search hit bottom messages.
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', ///< No recording message.
SHM_FILEINFO = 'F', ///< No file info messages.
SHM_SEARCHCOUNT = 'S', ///< No search stats: '[1/10]'.
SHM_UNDO = 'u', ///< No undo messages.
};
// characters for the p_shm option: the per-flag kShm* constants are generated from the option's
// `flagchars` schema (options.lua).
/// Represented by 'a' flag.
#define SHM_ALL_ABBREVIATIONS ((char[]) { \
SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, \
kShmRo, kShmMod, kShmLines, kShmWri, \
0 })
// characters for p_go:
@@ -329,7 +238,7 @@ EXTERN char *p_debug; ///< 'debug'
EXTERN char *p_def; ///< 'define'
EXTERN char *p_inc;
EXTERN char *p_dia; ///< 'diffanchors'
EXTERN char *p_dip; ///< 'diffopt'
EXTERN struct OptKeyDict_dip *p_dip; ///< 'diffopt'
EXTERN char *p_dex; ///< 'diffexpr'
EXTERN char *p_dict; ///< 'dictionary'
EXTERN int p_dg; ///< 'digraph'
@@ -439,7 +348,7 @@ EXTERN char *p_mousem; ///< 'mousemodel'
EXTERN int p_mousemev; ///< 'mousemoveevent'
EXTERN int p_mousef; ///< 'mousefocus'
EXTERN int p_mh; ///< 'mousehide'
EXTERN char *p_mousescroll; ///< 'mousescroll'
EXTERN struct OptKeyDict_mousescroll *p_mousescroll; ///< 'mousescroll'
EXTERN OptInt p_mousescroll_vert INIT( = MOUSESCROLL_VERT_DFLT);
EXTERN OptInt p_mousescroll_hor INIT( = MOUSESCROLL_HOR_DFLT);
EXTERN OptInt p_mouset; ///< 'mousetime'
@@ -608,7 +517,9 @@ EXTERN int p_cdh; ///< 'cdhome'
// Value for b_p_ul indicating the global value must be used.
#define NO_LOCAL_UNDOLEVEL (-123456)
#define ERR_BUFLEN 80
// Buffer for an option-set error message. Large enough to list an option's valid values (see
// opt_invalid_value_err()); the value is appended separately into IObuff.
#define ERR_BUFLEN 256
#define SB_MAX 1000000 // Maximum 'scrollback' value.
+520 -230
View File
File diff suppressed because it is too large. Load diff
+309 -168
View File
@@ -22,6 +22,7 @@
#include "nvim/eval/vars.h"
#include "nvim/ex_getln.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
@@ -57,6 +58,7 @@
#include "nvim/window.h"
#include "nvim/winfloat.h"
#include "options_keysets.generated.h"
#include "optionstr.c.generated.h"
static const char e_illegal_character_after_chr[]
@@ -78,33 +80,33 @@ static const char e_wrong_character_width_for_field_str[]
/// All possible flags for 'shm'.
/// the literal chars before 0 are removed flags. these are safely ignored
static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES,
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, SHM_UNDO, 'n', 'f', 'x', 'i', 0, };
static char SHM_ALL[] = { kShmRo, kShmMod, kShmLines,
kShmWri, kShmAbbreviations, kShmWrite, kShmTrunc, kShmTruncall,
kShmOver, kShmOverall, kShmSearch, kShmAttention, kShmIntro,
kShmCompletionmenu, kShmCompletionscan, kShmRecording, kShmFileinfo,
kShmSearchcount, kShmUndo, 'n', 'f', 'x', 'i', 0, };
/// After setting various option values: recompute variables that depend on
/// option values.
void didset_string_options(void)
{
check_str_opt(kOptCasemap, NULL);
check_str_opt(kOptBackupcopy, NULL);
check_str_opt(kOptBelloff, NULL);
check_str_opt(kOptCompleteopt, NULL);
check_str_opt(kOptSessionoptions, NULL);
check_str_opt(kOptViewoptions, NULL);
check_str_opt(kOptFoldopen, NULL);
check_str_opt(kOptDisplay, NULL);
check_str_opt(kOptJumpoptions, NULL);
check_str_opt(kOptRedrawdebug, NULL);
check_str_opt(kOptTagcase, NULL);
check_str_opt(kOptTermpastefilter, NULL);
check_str_opt(kOptVirtualedit, NULL);
check_str_opt(kOptSwitchbuf, NULL);
check_str_opt(kOptTabclose, NULL);
check_str_opt(kOptWildoptions, NULL);
check_str_opt(kOptClipboard, NULL);
check_str_opt(kOptCasemap, NULL, NULL, 0);
check_str_opt(kOptBackupcopy, NULL, NULL, 0);
check_str_opt(kOptBelloff, NULL, NULL, 0);
check_str_opt(kOptCompleteopt, NULL, NULL, 0);
check_str_opt(kOptSessionoptions, NULL, NULL, 0);
check_str_opt(kOptViewoptions, NULL, NULL, 0);
check_str_opt(kOptFoldopen, NULL, NULL, 0);
check_str_opt(kOptDisplay, NULL, NULL, 0);
check_str_opt(kOptJumpoptions, NULL, NULL, 0);
check_str_opt(kOptRedrawdebug, NULL, NULL, 0);
check_str_opt(kOptTagcase, NULL, NULL, 0);
check_str_opt(kOptTermpastefilter, NULL, NULL, 0);
check_str_opt(kOptVirtualedit, NULL, NULL, 0);
check_str_opt(kOptSwitchbuf, NULL, NULL, 0);
check_str_opt(kOptTabclose, NULL, NULL, 0);
check_str_opt(kOptWildoptions, NULL, NULL, 0);
check_str_opt(kOptClipboard, NULL, NULL, 0);
}
char *illegal_char(char *errbuf, size_t errbuflen, int c)
@@ -241,7 +243,7 @@ int check_signcolumn(char *scl, win_T *wp)
return FAIL;
}
if (opt_strings_flags(val, opt_scl_values, NULL, false) == OK) {
if (opt_strings_flags(val, opt_scl_values, NULL, false, NULL, 0) == NULL) {
if (wp == NULL) {
return OK;
}
@@ -369,12 +371,10 @@ bool check_illegal_path_names(char *val, uint32_t flags)
/// An option that accepts a list of flags is changed.
/// e.g. 'viewoptions', 'switchbuf', 'casemap', etc.
static const char *did_set_opt_flags(char *val, const char **values, unsigned *flagp, bool list)
static const char *did_set_opt_flags(char *val, const char **values, unsigned *flagp, bool list,
char *errbuf, size_t errbuflen)
{
if (opt_strings_flags(val, values, flagp, list) != OK) {
return e_invarg;
}
return NULL;
return opt_strings_flags(val, values, flagp, list, errbuf, errbuflen);
}
static const char **opt_values(OptIndex idx, size_t *values_len)
@@ -390,7 +390,7 @@ static const char **opt_values(OptIndex idx, size_t *values_len)
return opt->values;
}
static int check_str_opt(OptIndex idx, char **varp)
static const char *check_str_opt(OptIndex idx, char **varp, char *errbuf, size_t errbuflen)
{
vimoption_T *opt = get_option(idx);
if (varp == NULL) {
@@ -398,7 +398,7 @@ static int check_str_opt(OptIndex idx, char **varp)
}
bool list = opt->flags & (kOptFlagComma | kOptFlagOneComma);
const char **values = opt_values(idx, NULL);
return opt_strings_flags(*varp, values, opt->flags_var, list);
return opt_strings_flags(*varp, values, opt->flags_var, list, errbuf, errbuflen);
}
int expand_set_str_generic(optexpand_T *args, int *numMatches, char ***matches)
@@ -410,7 +410,7 @@ int expand_set_str_generic(optexpand_T *args, int *numMatches, char ***matches)
const char *did_set_str_generic(optset_T *args)
{
return check_str_opt(args->os_idx, args->os_varp) != OK ? e_invarg : NULL;
return check_str_opt(args->os_idx, args->os_varp, args->os_errbuf, args->os_errbuflen);
}
/// An option which is a list of flags is set. Valid values are in "flags".
@@ -559,8 +559,9 @@ const char *did_set_ambiwidth(optset_T *args)
/// The 'emoji' option is changed.
const char *did_set_emoji(optset_T *args)
{
if (check_str_opt(kOptAmbiwidth, NULL) != OK) {
return e_invarg;
const char *errmsg = check_str_opt(kOptAmbiwidth, NULL, args->os_errbuf, args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
return check_chars_options();
}
@@ -637,15 +638,17 @@ const char *did_set_backupcopy(optset_T *args)
// make the local value empty: use the global value
*flags = 0;
} else {
if (opt_strings_flags(bkc, opt_bkc_values, flags, true) != OK) {
return e_invarg;
const char *errmsg = opt_strings_flags(bkc, opt_bkc_values, flags, true, args->os_errbuf,
args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
if (((*flags & kOptBkcFlagAuto) != 0)
+ ((*flags & kOptBkcFlagYes) != 0)
+ ((*flags & kOptBkcFlagNo) != 0) != 1) {
// Must have exactly one of "auto", "yes" and "no".
opt_strings_flags(oldval, opt_bkc_values, flags, true);
opt_strings_flags(oldval, opt_bkc_values, flags, true, NULL, 0);
return e_invarg;
}
}
@@ -684,14 +687,12 @@ const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED)
const char *did_set_breakindentopt(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
char **varp = (char **)args->os_varp;
if (briopt_check(*varp, varp == &win->w_p_briopt ? win : NULL) == FAIL) {
return e_invarg;
}
// Apply it to the window, or to nothing if setting the global value.
bool is_local = args->os_varp == (void *)&win->w_p_briopt;
briopt_check(is_local ? win : NULL);
// list setting requires a redraw
if (varp == &win->w_p_briopt && win->w_briopt_list) {
if (is_local && win->w_briopt_list) {
redraw_all_later(UPD_NOT_VALID);
}
@@ -702,7 +703,8 @@ const char *did_set_breakindentopt(optset_T *args)
const char *did_set_bufhidden(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
return did_set_opt_flags(buf->b_p_bh, opt_bh_values, NULL, false);
return did_set_opt_flags(buf->b_p_bh, opt_bh_values, NULL, false, args->os_errbuf,
args->os_errbuflen);
}
/// The 'buftype' option is changed.
@@ -711,9 +713,13 @@ const char *did_set_buftype(optset_T *args)
buf_T *buf = (buf_T *)args->os_buf;
win_T *win = (win_T *)args->os_win;
// When 'buftype' is set, check for valid value.
const char *errmsg = opt_strings_flags(buf->b_p_bt, opt_bt_values, NULL, false, args->os_errbuf,
args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
if ((buf->terminal && buf->b_p_bt[0] != 't')
|| (!buf->terminal && buf->b_p_bt[0] == 't')
|| opt_strings_flags(buf->b_p_bt, opt_bt_values, NULL, false) != OK) {
|| (!buf->terminal && buf->b_p_bt[0] == 't')) {
return e_invarg;
}
// buftype=prompt:
@@ -997,11 +1003,7 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
buf->b_cot_flags = 0;
}
if (opt_strings_flags(cot, opt_cot_values, flags, true) != OK) {
return e_invarg;
}
return NULL;
return opt_strings_flags(cot, opt_cot_values, flags, true, args->os_errbuf, args->os_errbuflen);
}
#ifdef BACKSLASH_IN_FILENAME
@@ -1009,11 +1011,13 @@ const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
const char *did_set_completeslash(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
if (opt_strings_flags(p_csl, opt_csl_values, NULL, false) != OK
|| opt_strings_flags(buf->b_p_csl, opt_csl_values, NULL, false) != OK) {
return e_invarg;
const char *errmsg = opt_strings_flags(p_csl, opt_csl_values, NULL, false, args->os_errbuf,
args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
return NULL;
return opt_strings_flags(buf->b_p_csl, opt_csl_values, NULL, false, args->os_errbuf,
args->os_errbuflen);
}
#endif
@@ -1556,64 +1560,13 @@ int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches)
/// @return error message, NULL if it's OK.
const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED)
{
OptInt vertical = -1;
OptInt horizontal = -1;
char *string = p_mousescroll;
while (true) {
char *end = vim_strchr(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.
if (length <= 4) {
return e_invarg;
}
OptInt *direction;
if (memcmp(string, "ver:", 4) == 0) {
direction = &vertical;
} else if (memcmp(string, "hor:", 4) == 0) {
direction = &horizontal;
} else {
return e_invarg;
}
// If the direction has already been set, this is a duplicate.
if (*direction != -1) {
return e_invarg;
}
// Verify that only digits follow the colon.
for (size_t i = 4; i < length; i++) {
if (!ascii_isdigit(string[i])) {
return N_("E5080: Digit expected");
}
}
string += 4;
*direction = getdigits_int(&string, false, -1);
// Num options are generally kept within the signed int range.
// We know this number won't be negative because we've already checked for
// a minus sign. We'll allow 0 as a means of disabling mouse scrolling.
if (*direction == -1) {
return e_invarg;
}
if (!end) {
break;
}
string = end + 1;
// An empty value sets no direction; reject it (mousescroll always needs at least one).
OptKeyDict_mousescroll *v = p_mousescroll;
if (!HAS_KEY(v, mousescroll, hor) && !HAS_KEY(v, mousescroll, ver)) {
return e_invarg;
}
// If a direction wasn't set, fallback to the default value.
p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical;
p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal;
p_mousescroll_hor = HAS_KEY(v, mousescroll, hor) ? (int)v->hor : MOUSESCROLL_HOR_DFLT;
p_mousescroll_vert = HAS_KEY(v, mousescroll, ver) ? (int)v->ver : MOUSESCROLL_VERT_DFLT;
return NULL;
}
@@ -1663,7 +1616,7 @@ const char *did_set_sessionoptions(optset_T *args)
if ((ssop_flags & kOptSsopFlagCurdir) && (ssop_flags & kOptSsopFlagSesdir)) {
// Don't allow both "sesdir" and "curdir".
const char *oldval = args->os_oldval.string.data;
opt_strings_flags(oldval, opt_ssop_values, &ssop_flags, true);
opt_strings_flags(oldval, opt_ssop_values, &ssop_flags, true, NULL, 0);
return e_invarg;
}
return NULL;
@@ -1845,13 +1798,19 @@ const char *did_set_spelloptions(optset_T *args)
int opt_flags = args->os_flags;
const char *val = args->os_newval.string.data;
if (!(opt_flags & OPT_LOCAL)
&& opt_strings_flags(val, opt_spo_values, &spo_flags, true) != OK) {
return e_invarg;
if (!(opt_flags & OPT_LOCAL)) {
const char *errmsg = opt_strings_flags(val, opt_spo_values, &spo_flags, true, args->os_errbuf,
args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
}
if (!(opt_flags & OPT_GLOBAL)
&& opt_strings_flags(val, opt_spo_values, &win->w_s->b_p_spo_flags, true) != OK) {
return e_invarg;
if (!(opt_flags & OPT_GLOBAL)) {
const char *errmsg = opt_strings_flags(val, opt_spo_values, &win->w_s->b_p_spo_flags, true,
args->os_errbuf, args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
}
return NULL;
}
@@ -1970,8 +1929,12 @@ const char *did_set_tagcase(optset_T *args)
if ((opt_flags & OPT_LOCAL) && *p == NUL) {
// make the local value empty: use the global value
*flags = 0;
} else if (opt_strings_flags(p, opt_tc_values, flags, false) != OK) {
return e_invarg;
} else {
const char *errmsg = opt_strings_flags(p, opt_tc_values, flags, false, args->os_errbuf,
args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
}
}
return NULL;
}
@@ -2089,8 +2052,10 @@ const char *did_set_virtualedit(optset_T *args)
// make the local value empty: use the global value
*flags = 0;
} else {
if (opt_strings_flags(ve, opt_ve_values, flags, true) != OK) {
return e_invarg;
const char *errmsg = opt_strings_flags(ve, opt_ve_values, flags, true, args->os_errbuf,
args->os_errbuflen);
if (errmsg != NULL) {
return errmsg;
} else if (strcmp(ve, args->os_oldval.string.data) != 0) {
// Recompute cursor position in case the new 've' setting
// changes something.
@@ -2176,15 +2141,38 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches)
return expand_set_opt_generic(args, get_highlight_name, numMatches, matches);
}
/// Format an "invalid value" error naming the offending item (up to the next comma) and listing the
/// valid values. Returns `errbuf`, or the generic `e_invarg` when no buffer is available.
static const char *opt_invalid_value_err(const char *val, const char **values, char *errbuf,
size_t errbuflen)
{
if (errbuf == NULL) {
return e_invarg;
}
size_t bad_len = 0;
while (val[bad_len] != NUL && val[bad_len] != ',') {
bad_len++;
}
char badbuf[64];
xmemcpyz(badbuf, val, MIN(bad_len, sizeof(badbuf) - 1));
int n = vim_snprintf(errbuf, errbuflen, _("E474: Invalid value '%s', expected one of:"), badbuf);
for (int j = 0; values[j] != NULL && n > 0 && (size_t)n < errbuflen; j++) {
n += vim_snprintf(errbuf + n, errbuflen - (size_t)n, "%s%s", j == 0 ? " " : ", ", values[j]);
}
return errbuf;
}
/// Handle an option that can be a range of string values.
/// Set a flag in "*flagp" for each string present.
///
/// @param val new value
/// @param values array of valid string values
/// @param list when true: accept a list of values
/// @param val new value
/// @param values array of valid string values
/// @param list when true: accept a list of values
/// @param errbuf buffer for the error message (may be NULL, then a generic error is returned)
///
/// @return OK for correct value, FAIL otherwise. Empty is always OK.
static int opt_strings_flags(const char *val, const char **values, unsigned *flagp, bool list)
/// @return NULL for a correct value, otherwise an error message. Empty is always OK.
static const char *opt_strings_flags(const char *val, const char **values, unsigned *flagp,
bool list, char *errbuf, size_t errbuflen)
{
unsigned new_flags = 0;
@@ -2194,7 +2182,7 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla
while (*val || iter_one) {
for (unsigned i = 0;; i++) {
if (values[i] == NULL) { // val not found in values[]
return FAIL;
return opt_invalid_value_err(val, values, errbuf, errbuflen);
}
size_t len = strlen(values[i]);
@@ -2214,13 +2202,199 @@ static int opt_strings_flags(const char *val, const char **values, unsigned *fla
*flagp = new_flags;
}
return OK;
return NULL;
}
/// Format a schema-validation error naming the offending key. Returns `errbuf`, or the generic
/// `e_invarg` when no buffer is available.
static const char *opt_schema_err(char *errbuf, size_t errbuflen, const char *fmt, const char *key,
size_t keylen)
{
if (errbuf == NULL) {
return e_invarg;
}
char keybuf[64];
xmemcpyz(keybuf, key, MIN(keylen, sizeof(keybuf) - 1));
vim_snprintf(errbuf, errbuflen, _(fmt), keybuf);
return errbuf;
}
/// Validates a dict option against its schema (options.lua). On failure, writes a msg to `errbuf`.
///
/// @param val The option value.
/// @param schema NULL-terminated schema array (opt_<name>_schema).
/// @param errbuf Error message (may be NULL, then a generic error is returned).
///
/// @return NULL when valid, otherwise an (untranslated) error message.
const char *opt_strings_check(const char *val, const OptSchemaItem *schema, char *errbuf,
size_t errbuflen)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
const char *key, *v;
size_t keylen, vlen;
for (const char *p = val; option_next_keyval(&p, &key, &keylen, &v, &vlen);) {
if (keylen == 0 && v == NULL) {
continue; // tolerate empty parts, e.g. "a,,b"
}
const OptSchemaItem *it = schema;
while (it->name != NULL && !option_slice_eq(key, keylen, it->name)) {
it++;
}
if (it->name == NULL) {
return opt_schema_err(errbuf, errbuflen, N_("E474: Unknown item '%s'"), key, keylen);
}
switch (it->kind) {
case kOptSchemaFlag:
if (v != NULL) {
return opt_schema_err(errbuf, errbuflen, N_("E474: '%s' does not take a value"), key,
keylen);
}
break;
case kOptSchemaNum:
case kOptSchemaSNum: {
// snum allows a leading '-'; both require at least one digit and nothing else.
size_t i = (v != NULL && it->kind == kOptSchemaSNum && *v == '-') ? 1 : 0;
bool ok = v != NULL && vlen > i;
for (; ok && i < vlen; i++) {
ok = ascii_isdigit((uint8_t)v[i]);
}
if (!ok) {
return opt_schema_err(errbuf, errbuflen, N_("E474: '%s' requires a number"), key, keylen);
}
// The value must fit the `int` it is parsed into (opt_fill() uses getdigits_int()).
char *end = (char *)v;
intmax_t n;
if (!try_getdigits(&end, &n) || n < INT_MIN || n > INT_MAX) {
return opt_schema_err(errbuf, errbuflen, N_("E474: '%s' number is out of range"), key,
keylen);
}
break;
}
case kOptSchemaEnum: {
const char **ev = it->enum_values;
while (v != NULL && *ev != NULL && !option_slice_eq(v, vlen, *ev)) {
ev++;
}
if (v != NULL && *ev != NULL) {
break; // matched an enum value
}
if (errbuf == NULL) {
return e_invarg;
}
char keybuf[64];
xmemcpyz(keybuf, key, MIN(keylen, sizeof(keybuf) - 1));
int n = vim_snprintf(errbuf, errbuflen, _("E474: '%s' must be one of:"), keybuf);
for (ev = it->enum_values; *ev != NULL && n > 0 && (size_t)n < errbuflen; ev++) {
n += vim_snprintf(errbuf + n, errbuflen - (size_t)n, "%s%s",
ev == it->enum_values ? " " : ", ", *ev);
}
return errbuf;
}
case kOptSchemaStr:
if (v == NULL) {
return opt_schema_err(errbuf, errbuflen, N_("E474: '%s' requires a value"), key, keylen);
}
break;
}
}
return NULL;
}
/// Parses a validated dict option ":set" string into its keyset (`OptKeyDict_…`), see also
/// `api_dict_to_keydict()`. The keyset owns its `String` fields, so free it with `opt_dict_free()`
/// (via `optval_free()`/`clear_opt_dict()`), not `xfree()`.
void opt_fill(const char *value, FieldHashfn get_field, void *out)
FUNC_ATTR_NONNULL_ALL
{
const char *key, *v;
size_t keylen, vlen;
for (const char *p = value; option_next_keyval(&p, &key, &keylen, &v, &vlen);) {
KeySetLink *f = get_field(key, keylen);
if (f == NULL) {
continue; // unknown key can't occur after validation
}
void *field = (char *)out + f->ptr_off;
switch (f->type) {
case kObjectTypeBoolean:
*(Boolean *)field = true; // a flag: present means true
break;
case kObjectTypeInteger: {
char *end = (char *)v;
*(Integer *)field = getdigits_int(&end, false, 0);
break;
}
case kObjectTypeString:
*(String *)field = (String){ .data = xmemdupz(v, vlen), .size = vlen }; // owned; keyset is stored
break;
default:
break;
}
((OptKeySet *)out)->is_set_ |= (1ULL << (unsigned)f->opt_index);
}
}
/// Serializes a keyset to ":set" string form (inverse of `opt_fill()`), for storage and `:set opt?`.
/// Emits set keys only: "key" for a true flag, "key:value" for a set num/enum. `table` is the
/// keyset's `KeySetLink` table.
///
/// XXX: Keys are emitted in a canonical (alphanum) order, so the string is deterministic regardless
/// of the order they were set in. The reified value is an unordered map; unlike Vim, the serialized
/// order is not insertion-order.
///
/// @return an owned string (caller frees).
char *opt_serialize(const void *keyset, const KeySetLink *table)
FUNC_ATTR_NONNULL_ALL
{
// Collect the set keys, then sort by name. `set[]` is bounded by the number of sub-options.
const KeySetLink *set[64];
int n = 0;
for (const KeySetLink *f = table; f->str != NULL; f++) {
if (!(((const OptKeySet *)keyset)->is_set_ & (1ULL << (unsigned)f->opt_index))) {
continue;
}
if (f->type == kObjectTypeBoolean && !*(const Boolean *)((const char *)keyset + f->ptr_off)) {
continue; // a false flag is simply absent
}
assert(n < (int)ARRAY_SIZE(set));
set[n++] = f;
}
for (int i = 1; i < n; i++) { // insertion sort (n is small)
const KeySetLink *cur = set[i];
int j = i - 1;
while (j >= 0 && strcmp(set[j]->str, cur->str) > 0) {
set[j + 1] = set[j];
j--;
}
set[j + 1] = cur;
}
garray_T ga;
ga_init(&ga, 1, 64);
for (int i = 0; i < n; i++) {
const KeySetLink *f = set[i];
const void *field = (const char *)keyset + f->ptr_off;
if (ga.ga_len > 0) {
ga_append(&ga, ',');
}
ga_concat(&ga, f->str);
if (f->type == kObjectTypeInteger) {
char buf[32];
snprintf(buf, sizeof(buf), ":%" PRId64, *(const Integer *)field);
ga_concat(&ga, buf);
} else if (f->type == kObjectTypeString) {
const String *s = field;
ga_append(&ga, ':');
ga_concat_len(&ga, s->data, s->size);
}
}
ga_append(&ga, NUL);
return ga.ga_data;
}
/// @return OK if "p" is a valid fileformat name, FAIL otherwise.
int check_ff_value(char *p)
{
return opt_strings_flags(p, opt_ff_values, NULL, false);
return opt_strings_flags(p, opt_ff_values, NULL, false, NULL, 0) == NULL ? OK : FAIL;
}
static const char e_conflicts_with_value_of_listchars[]
@@ -2268,45 +2442,12 @@ struct chars_tab {
{ (cp), STATIC_CSTR_STRING_INIT(name), def, fallback }
static fcs_chars_T fcs_chars;
static const struct chars_tab fcs_tab[] = {
CHARSTAB_ENTRY(&fcs_chars.stl, "stl", " ", NULL),
CHARSTAB_ENTRY(&fcs_chars.stlnc, "stlnc", " ", NULL),
CHARSTAB_ENTRY(&fcs_chars.wbr, "wbr", " ", NULL),
CHARSTAB_ENTRY(&fcs_chars.horiz, "horiz", "─", "-"),
CHARSTAB_ENTRY(&fcs_chars.horizup, "horizup", "┴", "-"),
CHARSTAB_ENTRY(&fcs_chars.horizdown, "horizdown", "┬", "-"),
CHARSTAB_ENTRY(&fcs_chars.vert, "vert", "│", "|"),
CHARSTAB_ENTRY(&fcs_chars.vertleft, "vertleft", "┤", "|"),
CHARSTAB_ENTRY(&fcs_chars.vertright, "vertright", "├", "|"),
CHARSTAB_ENTRY(&fcs_chars.verthoriz, "verthoriz", "┼", "+"),
CHARSTAB_ENTRY(&fcs_chars.fold, "fold", "·", "-"),
CHARSTAB_ENTRY(&fcs_chars.foldopen, "foldopen", "-", NULL),
CHARSTAB_ENTRY(&fcs_chars.foldclosed, "foldclose", "+", NULL),
CHARSTAB_ENTRY(&fcs_chars.foldsep, "foldsep", "│", "|"),
CHARSTAB_ENTRY(&fcs_chars.foldinner, "foldinner", NULL, NULL),
CHARSTAB_ENTRY(&fcs_chars.diff, "diff", "-", NULL),
CHARSTAB_ENTRY(&fcs_chars.msgsep, "msgsep", " ", NULL),
CHARSTAB_ENTRY(&fcs_chars.eob, "eob", "~", NULL),
CHARSTAB_ENTRY(&fcs_chars.lastline, "lastline", "@", NULL),
CHARSTAB_ENTRY(&fcs_chars.trunc, "trunc", ">", NULL),
CHARSTAB_ENTRY(&fcs_chars.truncrl, "truncrl", "<", NULL),
};
static lcs_chars_T lcs_chars;
static const struct chars_tab lcs_tab[] = {
CHARSTAB_ENTRY(&lcs_chars.eol, "eol", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.ext, "extends", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.nbsp, "nbsp", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.prec, "precedes", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.space, "space", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.tab2, "tab", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.leadtab2, "leadtab", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.lead, "lead", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.trail, "trail", NULL, NULL),
CHARSTAB_ENTRY(&lcs_chars.conceal, "conceal", NULL, NULL),
CHARSTAB_ENTRY(NULL, "multispace", NULL, NULL),
CHARSTAB_ENTRY(NULL, "leadmultispace", NULL, NULL),
};
// Generated from options.lua:
// - 'fillchars' => fcs_tab[]
// - 'listchars' => lcs_tab[]
#include "options_chartab.generated.h"
#undef CHARSTAB_ENTRY
+1 -1
View File
@@ -3129,7 +3129,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) {
msg_scroll = true;
} else if ((msg_scrolled == 0 || (p_ch == 0 && msg_scrolled == 1))
&& shortmess(SHM_OVERALL)) {
&& shortmess(kShmOverall)) {
msg_scroll = false;
}
msg_ext_set_kind("quickfix");
+1 -1
View File
@@ -707,7 +707,7 @@ static int reg_cpo_lit; // 'cpoptions' contains 'l' flag
static void get_cpo_flags(void)
{
reg_cpo_lit = vim_strchr(p_cpo, CPO_LITERAL) != NULL;
reg_cpo_lit = vim_strchr(p_cpo, kCpoLiteral) != NULL;
}
/// Skip over a "[]" range.
+1 -1
View File
@@ -1094,7 +1094,7 @@ void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
// Concatenate the last line of the old block with the first line of
// the new block, unless being Vi compatible.
if (curr->y_type == kMTCharWise
&& vim_strchr(p_cpo, CPO_REGAPPEND) == NULL) {
&& vim_strchr(p_cpo, kCpoRegappend) == NULL) {
char *pnew = xmalloc(curr->y_array[curr->y_size - 1].size
+ reg->y_array[0].size + 1);
j--;
+1 -1
View File
@@ -2774,7 +2774,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
// Only concatenate lines starting with a \ when 'cpoptions' doesn't
// contain the 'C' flag.
if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
if (line != NULL && do_concat && (vim_strchr(p_cpo, kCpoConcat) == NULL)) {
char *p;
// compensate for the one line read-ahead
sp->sourcing_lnum--;
+16 -16
View File
@@ -610,7 +610,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
return FAIL;
}
const bool search_from_match_end = vim_strchr(p_cpo, CPO_SEARCH) != NULL;
const bool search_from_match_end = vim_strchr(p_cpo, kCpoSearch) != NULL;
// find the string
do { // loop for count
@@ -951,8 +951,8 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
lnum = dir == BACKWARD // start second loop at the other end
? buf->b_ml.ml_line_count
: 1;
if (!shortmess(SHM_SEARCH)
&& shortmess(SHM_SEARCHCOUNT)
if (!shortmess(kShmSearch)
&& shortmess(kShmSearchcount)
&& (options & SEARCH_MSG)) {
give_warning(_(dir == BACKWARD ? top_bot_msg : bot_top_msg), true, false);
}
@@ -1151,7 +1151,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
Search.cmdlen = 0;
// A line offset is not remembered, this is vi compatible.
if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL) {
if (spats[0].off.line && vim_strchr(p_cpo, kCpoLineoff) != NULL) {
spats[0].off.line = false;
spats[0].off.off = 0;
}
@@ -1226,7 +1226,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
bool show_search_stats = false;
if ((options & SEARCH_ECHO) && messaging() && !msg_silent
&& (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) {
&& (!cmd_silent || !shortmess(kShmSearchcount))) {
char off_buf[40];
size_t off_len = 0;
@@ -1260,7 +1260,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
}
size_t msgbufsize;
if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent) {
if (!shortmess(kShmSearchcount) || cmd_silent) {
// Reserve enough space for the search pattern + offset +
// search stat. Use all the space available, so that the
// search state is right aligned. If there is not enough space
@@ -1343,7 +1343,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
msg_nowait = true; // don't wait for this message
}
if (!shortmess(SHM_SEARCHCOUNT)) {
if (!shortmess(kShmSearchcount)) {
show_search_stats = true;
}
}
@@ -1390,7 +1390,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
*dircp = (char)search_delim; // restore second '/' or '?' for normal_cmd()
}
if (!shortmess(SHM_SEARCH) && sia && sia->sa_wrapped) {
if (!shortmess(kShmSearch) && sia && sia->sa_wrapped) {
show_top_bot_msg = true;
}
@@ -1514,7 +1514,7 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
if (pos->lnum < 1) {
if (p_ws) {
pos->lnum = buf->b_ml.ml_line_count;
if (!shortmess(SHM_SEARCH)) {
if (!shortmess(kShmSearch)) {
give_warning(_(top_bot_msg), true, false);
}
} else {
@@ -1524,7 +1524,7 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
} else if (pos->lnum > buf->b_ml.ml_line_count) {
if (p_ws) {
pos->lnum = 1;
if (!shortmess(SHM_SEARCH)) {
if (!shortmess(kShmSearch)) {
give_warning(_(bot_top_msg), true, false);
}
} else {
@@ -1600,7 +1600,7 @@ int searchc(cmdarg_T *cap, bool t_cmd)
// Force a move of at least one char, so ";" and "," will move the
// cursor, even if the cursor is right in front of char we are looking
// at.
if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd) {
if (vim_strchr(p_cpo, kCpoScolon) == NULL && count == 1 && t_cmd) {
stop = false;
}
}
@@ -1798,9 +1798,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
char *linep = ml_get(pos.lnum); // pointer to current line
// vi compatible matching
bool cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
bool cpo_match = (vim_strchr(p_cpo, kCpoMatch) != NULL);
// don't recognize backslashes
bool cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
bool cpo_bsl = (vim_strchr(p_cpo, kCpoMatchbsl) != NULL);
// Direction to search when initc is '/', '*' or '#'
if (flags & FM_BACKWARD) {
@@ -2425,7 +2425,7 @@ void showmatch(int c)
// brief pause, unless 'm' is present in 'cpo' and a character is
// available.
if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) {
if (vim_strchr(p_cpo, kCpoShowmatch) != NULL) {
os_delay((uint64_t)p_mat * 100 + 8, true);
} else if (!char_avail()) {
os_delay((uint64_t)p_mat * 100 + 9, false);
@@ -2831,7 +2831,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_alloc_ret(rettv);
if (shortmess(SHM_SEARCHCOUNT)) { // 'shortmess' contains 'S' flag
if (shortmess(kShmSearchcount)) { // 'shortmess' contains 'S' flag
recompute = true;
}
@@ -3187,7 +3187,7 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
files[depth].name = curr_fname = new_fname;
files[depth].lnum = 0;
files[depth].matched = false;
if (action == ACTION_EXPAND && !shortmess(SHM_COMPLETIONSCAN) && !silent) {
if (action == ACTION_EXPAND && !shortmess(kShmCompletionscan) && !silent) {
msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE,
_("Scanning included file: %s"),
+2 -2
View File
@@ -1480,7 +1480,7 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a
// starting line again and accept the last match.
lnum = wp->w_buffer->b_ml.ml_line_count;
wrapped = true;
if (!shortmess(SHM_SEARCH)) {
if (!shortmess(kShmSearch)) {
give_warning(_(top_bot_msg), true, false);
}
}
@@ -1495,7 +1495,7 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a
// starting line again and accept the first match.
lnum = 1;
wrapped = true;
if (!shortmess(SHM_SEARCH)) {
if (!shortmess(kShmSearch)) {
give_warning(_(bot_top_msg), true, false);
}
}
+32 -21
View File
@@ -386,33 +386,44 @@ static int sps_limit = 9999; ///< max nr of suggestions given
/// Sets "sps_flags" and "sps_limit".
int spell_check_sps(void)
{
char buf[MAXPATHL];
sps_flags = 0;
sps_limit = 9999;
for (char *p = p_sps; *p != NUL;) {
copy_option_part(&p, buf, MAXPATHL, ",");
const char *key, *val;
size_t keylen, vallen;
for (const char *p = p_sps; option_next_keyval(&p, &key, &keylen, &val, &vallen);) {
int f = 0;
if (ascii_isdigit(*buf)) {
char *s = buf;
sps_limit = getdigits_int(&s, true, 0);
if (*s != NUL && !ascii_isdigit(*s)) {
if (val == NULL) {
// A bare number is the suggestion limit.
if (keylen > 0 && ascii_isdigit((uint8_t)(*key))) {
char *s = (char *)key;
sps_limit = getdigits_int(&s, true, 0);
if (s != key + keylen) { // trailing non-digits
f = -1;
}
} else if (option_slice_eq(key, keylen, "best")) {
f = SPS_BEST;
} else if (option_slice_eq(key, keylen, "fast")) {
f = SPS_FAST;
} else if (option_slice_eq(key, keylen, "double")) {
f = SPS_DOUBLE;
} else {
f = -1;
}
// Note: Keep this in sync with opt_sps_values.
} else if (strcmp(buf, "best") == 0) {
f = SPS_BEST;
} else if (strcmp(buf, "fast") == 0) {
f = SPS_FAST;
} 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
|| (!ascii_isdigit(buf[8])
&& !(buf[8] == '-' && ascii_isdigit(buf[9]))))) {
} else if (option_slice_eq(key, keylen, "expr") || option_slice_eq(key, keylen, "file")) {
// Value is an expression/filename, consumed later in spell_find_suggest().
} else if (option_slice_eq(key, keylen, "timeout")) {
// Optional leading '-', then at least one digit.
const char *v = val;
size_t vl = vallen;
if (vl > 0 && *v == '-') {
v++;
vl--;
}
if (vl == 0 || !ascii_isdigit((uint8_t)(*v))) {
f = -1;
}
} else {
f = -1;
}
+1 -1
View File
@@ -2770,7 +2770,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help)
// 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) {
if (vim_strchr(p_cpo, kCpoTagpat) != NULL) {
search_options = 0;
} else {
search_options = SEARCH_KEEP;
+22 -22
View File
@@ -72,10 +72,10 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
int cc;
char 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);
const bool fo_ins_blank = has_format_option(kFoInsBlank);
const bool fo_multibyte = has_format_option(kFoMbyteBreak);
const bool fo_rigor_tw = has_format_option(kFoRigorousTw);
const bool fo_white_par = has_format_option(kFoWhitePar);
bool first_line = true;
colnr_T leader_len;
bool no_leader = false;
@@ -114,7 +114,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
if (no_leader) {
do_comments = false;
} else if (!(flags & INSCHAR_FORMAT)
&& has_format_option(FO_WRAP_COMS)) {
&& has_format_option(kFoWrapComs)) {
do_comments = true;
}
@@ -145,7 +145,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
}
if (!(flags & INSCHAR_FORMAT)
&& leader_len == 0
&& !has_format_option(FO_WRAP)) {
&& !has_format_option(kFoWrap)) {
break;
}
if ((startcol = curwin->w_cursor.col) == 0) {
@@ -162,7 +162,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
// Find position to break at.
// Stop at first entered white when 'formatoptions' has 'v'
while ((!fo_ins_blank && !has_format_option(FO_INS_VI))
while ((!fo_ins_blank && !has_format_option(kFoInsVi))
|| (flags & INSCHAR_FORMAT)
|| curwin->w_cursor.lnum != Ins.start.lnum
|| curwin->w_cursor.col >= Ins.start.col) {
@@ -193,7 +193,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
// 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) {
if (has_format_option(kFoPeriodAbbr) && cc == '.' && wcc < 2) {
continue;
}
@@ -202,7 +202,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
break;
}
if (has_format_option(FO_ONE_LETTER)) {
if (has_format_option(kFoOneLetter)) {
// do not break after one-letter words
if (curwin->w_cursor.col == 0) {
break; // one-letter word at begin
@@ -396,7 +396,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
// 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)) {
if (second_indent < 0 && has_format_option(kFoQNumber)) {
second_indent = get_number_indent(curwin->w_cursor.lnum - 1);
}
if (second_indent >= 0) {
@@ -583,7 +583,7 @@ static bool paragraph_start(linenr_T lnum)
if (*p == NUL) {
return true; // after empty line
}
const bool do_comments = has_format_option(FO_Q_COMS); // format comments
const bool do_comments = has_format_option(kFoQComs); // format comments
if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) {
return true; // after non-paragraph line
}
@@ -592,10 +592,10 @@ static bool paragraph_start(linenr_T lnum)
return true; // "lnum" is not a paragraph line
}
if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) {
if (has_format_option(kFoWhitePar) && !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)) {
if (has_format_option(kFoQNumber) && (get_number_indent(lnum) > 0)) {
return true; // numbered item starts in "lnum".
}
if (!same_leader(lnum - 1, leader_len, leader_flags,
@@ -615,7 +615,7 @@ static bool paragraph_start(linenr_T lnum)
/// @param prev_line may start in previous line
void auto_format(bool trailblank, bool prev_line)
{
if (!has_format_option(FO_AUTO)) {
if (!has_format_option(kFoAuto)) {
return;
}
@@ -635,7 +635,7 @@ void auto_format(bool trailblank, bool prev_line)
dec_cursor();
int cc = gchar_cursor();
if (!WHITECHAR(cc) && curwin->w_cursor.col > 0
&& has_format_option(FO_ONE_LETTER)) {
&& has_format_option(kFoOneLetter)) {
dec_cursor();
}
cc = gchar_cursor();
@@ -664,7 +664,7 @@ void auto_format(bool trailblank, bool prev_line)
// With the 'c' flag in 'formatoptions' and 't' missing: only format
// comments.
if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP)
if (has_format_option(kFoWrapComs) && !has_format_option(kFoWrap)
&& get_leader_len(old, NULL, false, true) == 0) {
return;
}
@@ -698,7 +698,7 @@ void auto_format(bool trailblank, bool prev_line)
// 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)) {
if (!wasatend && has_format_option(kFoWhitePar)) {
char *linep = get_cursor_line_ptr();
colnr_T len = get_cursor_line_len();
if (curwin->w_cursor.col == len) {
@@ -927,11 +927,11 @@ void format_lines(linenr_T line_count, bool avoid_fex)
const int max_len = comp_textwidth(true) * 3;
// check for 'q', '2', 'n' and 'w' in 'formatoptions'
const bool do_comments = has_format_option(FO_Q_COMS); // format comments
const bool do_comments = has_format_option(kFoQComs); // 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);
const bool do_second_indent = has_format_option(kFoQSecond);
const bool do_number_indent = has_format_option(kFoQNumber);
const bool do_trail_white = has_format_option(kFoWhitePar);
// Get info about the previous and current line.
if (curwin->w_cursor.lnum > 1) {
@@ -1101,7 +1101,7 @@ void format_lines(linenr_T line_count, bool avoid_fex)
if (next_leader_len > 0) {
del_bytes(next_leader_len, false, false);
mark_col_adjust(curwin->w_cursor.lnum, 0, 0, -next_leader_len, 0);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
} else if (second_indent > 0) { // the "leader" for kFoQSecond
int indent = (int)getwhitecols_curline();
if (indent > 0) {
+2 -2
View File
@@ -98,7 +98,7 @@ int findsent(Direction dir, int count)
// remember the line where the search started
const int startlnum = pos.lnum;
const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL;
const bool cpo_J = vim_strchr(p_cpo, kCpoEndofsent) != NULL;
while (true) { // find end of sentence
c = gchar_pos(&pos);
@@ -989,7 +989,7 @@ int current_block(oparg_T *oap, int count, bool include, int what, int other)
// Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the
// user wants.
char *save_cpo = p_cpo;
p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%";
p_cpo = vim_strchr(p_cpo, kCpoMatchbsl) != NULL ? "%M" : "%";
if ((pos = findmatch(NULL, what)) != NULL) {
while (count-- > 0) {
if ((pos = findmatch(NULL, what)) == NULL) {
+7 -7
View File
@@ -617,15 +617,15 @@ void ui_check_mouse(void)
return;
}
int checkfor = MOUSE_NORMAL; // assume normal mode
int checkfor = kMouseNormal; // assume normal mode
if (Visual.active) {
checkfor = MOUSE_VISUAL;
checkfor = kMouseVisual;
} else if (State == MODE_HITRETURN || State == MODE_ASKMORE || State == MODE_SETWSIZE) {
checkfor = MOUSE_RETURN;
checkfor = kMouseReturn;
} else if (State & MODE_INSERT) {
checkfor = MOUSE_INSERT;
checkfor = kMouseInsert;
} else if (State & MODE_CMDLINE) {
checkfor = MOUSE_COMMAND;
checkfor = kMouseCommand;
} else if (State == MODE_EXTERNCMD) {
checkfor = ' '; // don't use mouse for ":!cmd"
}
@@ -652,8 +652,8 @@ bool ui_mouse_has(int mode)
}
break;
case MOUSE_HELP:
if (mode != MOUSE_RETURN && curbuf->b_help) {
case kMouseHelp:
if (mode != kMouseReturn && curbuf->b_help) {
return true;
}
+6 -6
View File
@@ -1790,7 +1790,7 @@ void u_undo(int count)
count = 1;
}
if (vim_strchr(p_cpo, CPO_UNDO) == NULL) {
if (vim_strchr(p_cpo, kCpoUndo) == NULL) {
undo_undoes = true;
} else {
undo_undoes = !undo_undoes;
@@ -1802,7 +1802,7 @@ void u_undo(int count)
/// If 'cpoptions' does not contain 'u': Always redo.
void u_redo(int count)
{
if (vim_strchr(p_cpo, CPO_UNDO) == NULL) {
if (vim_strchr(p_cpo, kCpoUndo) == NULL) {
undo_undoes = false;
}
@@ -1893,7 +1893,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
curbuf->b_u_curhead = curbuf->b_u_oldhead;
beep_flush();
if (count == startcount - 1) {
if (!shortmess(SHM_UNDO)) {
if (!shortmess(kShmUndo)) {
msg(_("Already at oldest change"), 0);
}
return;
@@ -1906,7 +1906,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
if (curbuf->b_u_curhead == NULL || get_undolevel(curbuf) <= 0) {
beep_flush(); // nothing to redo
if (count == startcount - 1) {
if (!shortmess(SHM_UNDO)) {
if (!shortmess(kShmUndo)) {
msg(_("Already at newest change"), 0);
}
return;
@@ -2129,7 +2129,7 @@ void undo_time(int step, bool sec, bool file, bool absolute)
}
if (closest == closest_start) {
if (!shortmess(SHM_UNDO)) {
if (!shortmess(kShmUndo)) {
if (step < 0) {
msg(_("Already at oldest change"), 0);
} else {
@@ -2565,7 +2565,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
if (quiet
|| global_busy // no messages until global is finished
|| !messaging() // 'lazyredraw' set, don't do messages now
|| shortmess(SHM_UNDO)) {
|| shortmess(kShmUndo)) {
return;
}
+1 -1
View File
@@ -4281,7 +4281,7 @@ bool may_show_intro(void)
&& (curbuf->handle == 1)
&& (curwin->handle == LOWEST_WIN_ID)
&& one_window(curwin, NULL)
&& (vim_strchr(p_shm, SHM_INTRO) == NULL));
&& (vim_strchr(p_shm, kShmIntro) == NULL));
}
/// Give an introductory message about Vim.
+6 -2
View File
@@ -52,8 +52,12 @@ describe("'tagcase' option", function()
-- does not. The first of these (setting the local value to <empty>) should
-- succeed; the other two should fail.
n.command('setl tc=')
eq('Vim(setglobal):E474: Invalid argument: tc=', pcall_err(n.command, 'setg tc='))
eq('Vim(set):E474: Invalid argument: tc=', pcall_err(n.command, 'set tc='))
local one_of = 'expected one of: followic, ignore, match, followscs, smart'
eq(
"Vim(setglobal):E474: Invalid value '', " .. one_of .. ': tc=',
pcall_err(n.command, 'setg tc=')
)
eq("Vim(set):E474: Invalid value '', " .. one_of .. ': tc=', pcall_err(n.command, 'set tc='))
end)
it("should work with 'ignorecase' correctly in all combinations", function()
+35 -4
View File
@@ -897,6 +897,37 @@ describe('lua stdlib', function()
end)
end)
it("returns structured values for a dict option ('diffopt')", function()
-- Bare flags -> true; sub-values stay strings (option sub-value types are not exposed).
eq_exec_lua({ internal = true, filler = true, context = '4' }, function()
vim.opt.diffopt = 'internal,filler,context:4'
return vim.opt.diffopt:get()
end)
end)
it('roundtrips a dict option through :set', function()
eq_exec_lua('context:4,filler,internal', function()
vim.opt.diffopt = 'internal,filler,context:4'
vim.opt.diffopt = vim.opt.diffopt:get()
return vim.go.diffopt
end)
end)
-- Dict option accepts a table and :get() returns a map. #18875
it("roundtrips a window-local dict option ('breakindentopt')", function()
eq_exec_lua({ sbr = true, shift = '3' }, function()
vim.opt.breakindentopt = { sbr = true, shift = 3 }
return vim.opt.breakindentopt:get()
end)
end)
it("roundtrips a table for 'mousescroll'", function()
eq_exec_lua({ hor = '6', ver = '3' }, function()
vim.opt.mousescroll = { hor = 6, ver = 3 }
return vim.opt.mousescroll:get()
end)
end)
it('works for key-value pair options', function()
eq_exec_lua({ tab = '> ', space = '_' }, function()
vim.opt.listchars = 'tab:> ,space:_'
@@ -1249,17 +1280,17 @@ describe('lua stdlib', function()
end)
end)
-- isfname=a,b,c,,,d,e,f
-- The raw string keeps the ",," literal-comma convention; the structured view splits on every
-- comma and does not reconstruct literal commas.
it('can handle isfname ,,,', function()
eq_exec_lua({ { ',', 'a', 'b', 'c' }, 'a,b,,,c' }, function()
eq_exec_lua({ { 'a', 'b', 'c' }, 'a,b,,,c' }, function()
vim.opt.isfname = 'a,b,,,c'
return { vim.opt.isfname:get(), vim.go.isfname }
end)
end)
-- isfname=a,b,c,^,,def
it('can handle isfname ,^,,', function()
eq_exec_lua({ { '^,', 'a', 'b', 'c' }, 'a,b,^,,c' }, function()
eq_exec_lua({ { 'a', 'b', '^', 'c' }, 'a,b,^,,c' }, function()
vim.opt.isfname = 'a,b,^,,c'
return { vim.opt.isfname:get(), vim.go.isfname }
end)
+17 -12
View File
@@ -22,11 +22,8 @@ local screencol = function()
end
describe("'mousescroll'", function()
local invalid_arg = 'Vim(set):E474: Invalid argument: mousescroll='
local digit_expected = 'Vim(set):E5080: Digit expected: mousescroll='
local function should_fail(val, errorstr)
eq(errorstr .. val, pcall_err(command, 'set mousescroll=' .. val))
local function should_fail(val, msg)
eq(msg, pcall_err(command, 'set mousescroll=' .. val))
end
local function should_succeed(val)
@@ -40,12 +37,19 @@ describe("'mousescroll'", function()
end)
it('handles invalid values', function()
should_fail('', invalid_arg) -- empty string
should_fail('foo:123', invalid_arg) -- unknown direction
should_fail('hor:1,hor:2', invalid_arg) -- duplicate direction
should_fail('ver:99999999999999999999', invalid_arg) -- integer overflow
should_fail('ver:bar', digit_expected) -- expected digit
should_fail('ver:-1', digit_expected) -- negative count
-- empty string (no direction set)
should_fail('', 'Vim(set):E474: Invalid argument: mousescroll=')
-- unknown direction
should_fail('foo:123', "Vim(set):E474: Unknown item 'foo': mousescroll=foo:123")
-- integer overflow
should_fail(
'ver:99999999999999999999',
"Vim(set):E474: 'ver' number is out of range: mousescroll=ver:99999999999999999999"
)
-- expected digit
should_fail('ver:bar', "Vim(set):E474: 'ver' requires a number: mousescroll=ver:bar")
-- negative count
should_fail('ver:-1', "Vim(set):E474: 'ver' requires a number: mousescroll=ver:-1")
end)
it('handles valid values', function()
@@ -54,10 +58,11 @@ describe("'mousescroll'", function()
should_succeed('ver:1') -- only vertical
should_succeed('hor:0,ver:0') -- zero
should_succeed('hor:2147483647') -- large count
should_succeed('hor:1,hor:2') -- duplicate direction: last wins
end)
it('default set correctly', function()
eq('ver:3,hor:6', eval('&mousescroll'))
eq('hor:6,ver:3', eval('&mousescroll'))
eq(10, screenrow())
scroll('up')
@@ -8,6 +8,7 @@ local command, clear = n.command, n.clear
local source, expect = n.source, n.expect
local matches = t.matches
local pcall_err = t.pcall_err
local eq = t.eq
describe('options', function()
setup(clear)
@@ -17,7 +18,7 @@ describe('options', function()
end)
end)
describe('set', function()
describe('options :set', function()
before_each(clear)
it("should keep two comma when 'path' is changed", function()
@@ -32,7 +33,7 @@ describe('set', function()
foo,,bar]])
end)
it('winminheight works', function()
it("'winminheight'", function()
local _ = Screen.new(20, 11)
source([[
set wmh=0 stal=2
@@ -44,7 +45,7 @@ describe('set', function()
matches('E36: Not enough room', pcall_err(command, 'set wmh=1'))
end)
it('winminheight works with tabline', function()
it("'winminheight' with tabline", function()
local _ = Screen.new(20, 11)
source([[
set wmh=0 stal=2
@@ -57,7 +58,7 @@ describe('set', function()
matches('E36: Not enough room', pcall_err(command, 'set wmh=1'))
end)
it('scroll works', function()
it("'scroll'", function()
local screen = Screen.new(42, 16)
source([[
set scroll=2
@@ -75,7 +76,46 @@ describe('set', function()
end)
it('foldcolumn and signcolumn to empty string is disallowed', function()
matches('E474: Invalid argument: fdc=', pcall_err(command, 'set fdc='))
matches("E474: Invalid value ''.*fdc=", pcall_err(command, 'set fdc='))
matches('E474: Invalid argument: scl=', pcall_err(command, 'set scl='))
end)
end)
describe('options validation', function()
before_each(clear)
-- Improved error messages for structured "key:value" options ("schema" in options.lua).
it('reports specific errors for structured (schema) options', function()
eq("Vim(set):E474: Unknown item 'foo': diffopt=foo", pcall_err(command, 'set diffopt=foo'))
eq(
"Vim(set):E474: 'context' requires a number: diffopt=context:x",
pcall_err(command, 'set diffopt=context:x')
)
eq(
'Vim(set):E474: '
.. "'algorithm' must be one of: myers, minimal, patience, histogram: diffopt=algorithm:bad",
pcall_err(command, 'set diffopt=algorithm:bad')
)
eq(
"Vim(set):E474: 'filler' does not take a value: diffopt=filler:1",
pcall_err(command, 'set diffopt=filler:1')
)
eq(
"Vim(set):E474: 'ver' number is out of range: mousescroll=ver:99999999999",
pcall_err(command, 'set mousescroll=ver:99999999999')
)
end)
-- Enum / flag-list options name the offending value and list the valid ones.
it('reports specific errors for enum and flag-list options', function()
eq(
"Vim(set):E474: Invalid value 'x', expected one of: single, double: ambiwidth=x",
pcall_err(command, 'set ambiwidth=x')
)
eq(
'Vim(set):E474: '
.. "Invalid value 'x', expected one of: yes, auto, no, breaksymlink, breakhardlink: backupcopy=x",
pcall_err(command, 'set backupcopy=x')
)
end)
end)
+21 -21
View File
@@ -2232,7 +2232,7 @@ func Test_opt_winminheight()
endfunc
func Test_opt_winminheight_term()
" See test/functional/legacy/options_spec.lua
" See test/functional/options/options_spec.lua
CheckRunVimInTerminal
" The tabline should be taken into account.
@@ -2253,7 +2253,7 @@ func Test_opt_winminheight_term()
endfunc
func Test_opt_winminheight_term_tabs()
" See test/functional/legacy/options_spec.lua
" See test/functional/options/options_spec.lua
CheckRunVimInTerminal
" The tabline should be taken into account.
@@ -2295,7 +2295,7 @@ endfunc
" Test that resetting laststatus does change scroll option
func Test_opt_reset_scroll()
" See test/functional/legacy/options_spec.lua
" See test/functional/options/options_spec.lua
CheckRunVimInTerminal
let vimrc =<< trim [CODE]
set scroll=2
@@ -2982,49 +2982,49 @@ func Test_comma_option_key_value()
" += replaces existing item with same key
set diffopt=internal,filler,algorithm:patience
set diffopt+=algorithm:histogram
call assert_equal('internal,filler,algorithm:histogram', &diffopt)
call assert_equal('algorithm:histogram,filler,internal', &diffopt)
" += with exact duplicate does nothing
set diffopt=internal,filler,algorithm:patience
set diffopt+=algorithm:patience
call assert_equal('internal,filler,algorithm:patience', &diffopt)
call assert_equal('algorithm:patience,filler,internal', &diffopt)
" += with multiple items, each processed individually
set diffopt=algorithm:patience,filler
set diffopt+=algorithm:histogram,filler
call assert_equal('filler,algorithm:histogram', &diffopt)
call assert_equal('algorithm:histogram,filler', &diffopt)
" += with non-colon item appends normally
set diffopt=internal,filler
set diffopt+=iwhite
call assert_equal('internal,filler,iwhite', &diffopt)
call assert_equal('filler,internal,iwhite', &diffopt)
" += repeated updates
set diffopt=internal,filler,algorithm:patience
set diffopt+=algorithm:histogram
set diffopt+=algorithm:minimal
set diffopt+=algorithm:myers
call assert_equal('internal,filler,algorithm:myers', &diffopt)
call assert_equal('algorithm:myers,filler,internal', &diffopt)
" += all exact duplicates does nothing
set diffopt=internal,filler,algorithm:patience
set diffopt+=algorithm:patience,filler
call assert_equal('internal,filler,algorithm:patience', &diffopt)
call assert_equal('algorithm:patience,filler,internal', &diffopt)
" -= with "key:" removes item regardless of value
set diffopt=internal,filler,algorithm:patience
set diffopt-=algorithm:
call assert_equal('internal,filler', &diffopt)
call assert_equal('filler,internal', &diffopt)
" -= with "key:value" also matches by key
set diffopt=internal,filler,algorithm:patience
set diffopt-=algorithm:histogram
call assert_equal('internal,filler', &diffopt)
call assert_equal('filler,internal', &diffopt)
" -= without colon does not match "key:value" items
set diffopt=internal,filler,algorithm:patience
set diffopt-=algorithm
call assert_equal('internal,filler,algorithm:patience', &diffopt)
call assert_equal('algorithm:patience,filler,internal', &diffopt)
" -= with multiple non-colon items (order independent)
set diffopt=internal,filler,closeoff
@@ -3039,22 +3039,22 @@ func Test_comma_option_key_value()
" -= with multiple items: non-colon and colon mixed
set diffopt& diffopt=internal,filler,closeoff,indent-heuristic,inline:char
set diffopt-=indent-heuristic,inline:char
call assert_equal('internal,filler,closeoff', &diffopt)
call assert_equal('closeoff,filler,internal', &diffopt)
" -= with multiple items: colon and non-colon mixed (reverse order)
set diffopt& diffopt=internal,filler,closeoff,indent-heuristic,inline:char
set diffopt-=inline:char,indent-heuristic
call assert_equal('internal,filler,closeoff', &diffopt)
call assert_equal('closeoff,filler,internal', &diffopt)
" += with multiple non-colon items
set diffopt=internal,filler
set diffopt+=closeoff,iwhite
call assert_equal('internal,filler,closeoff,iwhite', &diffopt)
call assert_equal('closeoff,filler,internal,iwhite', &diffopt)
" += with multiple non-colon items, some already exist
set diffopt=internal,filler,closeoff
set diffopt+=filler,iwhite
call assert_equal('internal,filler,closeoff,iwhite', &diffopt)
call assert_equal('closeoff,filler,internal,iwhite', &diffopt)
" -= with multiple items including key match
set diffopt=internal,filler,algorithm:patience
@@ -3064,12 +3064,12 @@ func Test_comma_option_key_value()
" -= key match when item is at the beginning
set diffopt=algorithm:patience,internal,filler
set diffopt-=algorithm:
call assert_equal('internal,filler', &diffopt)
call assert_equal('filler,internal', &diffopt)
" -= key match when item is at the end
set diffopt=internal,filler,algorithm:patience
set diffopt-=algorithm:
call assert_equal('internal,filler', &diffopt)
call assert_equal('filler,internal', &diffopt)
" -= key match when item is the only item
set diffopt=algorithm:patience
@@ -3079,17 +3079,17 @@ func Test_comma_option_key_value()
" ^= prepends new item
set diffopt=internal,filler
set diffopt^=algorithm:histogram
call assert_equal('algorithm:histogram,internal,filler', &diffopt)
call assert_equal('algorithm:histogram,filler,internal', &diffopt)
" ^= replaces item and prepends
set diffopt=internal,filler,algorithm:patience
set diffopt^=algorithm:histogram
call assert_equal('algorithm:histogram,internal,filler', &diffopt)
call assert_equal('algorithm:histogram,filler,internal', &diffopt)
" ^= with exact duplicate does nothing
set diffopt=internal,filler,algorithm:patience
set diffopt^=algorithm:patience
call assert_equal('internal,filler,algorithm:patience', &diffopt)
call assert_equal('algorithm:patience,filler,internal', &diffopt)
set diffopt&