123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649 |
- -- prevents luacheck from making lints for setting things on vim
- local vim = assert(vim)
- local a = vim.api
- local validate = vim.validate
- local SET_TYPES = setmetatable({
- SET = 0,
- LOCAL = 1,
- GLOBAL = 2,
- }, { __index = error })
- local options_info = {}
- for _, v in pairs(a.nvim_get_all_options_info()) do
- options_info[v.name] = v
- if v.shortname ~= "" then options_info[v.shortname] = v end
- end
- local get_scoped_options = function(scope)
- local result = {}
- for name, option_info in pairs(options_info) do
- if option_info.scope == scope then
- result[name] = true
- end
- end
- return result
- end
- local buf_options = get_scoped_options("buf")
- local glb_options = get_scoped_options("global")
- local win_options = get_scoped_options("win")
- local function make_meta_accessor(get, set, del, validator)
- validator = validator or function() return true end
- validate {
- get = {get, 'f'};
- set = {set, 'f'};
- del = {del, 'f', true};
- validator = {validator, 'f'};
- }
- local mt = {}
- function mt:__newindex(k, v)
- if not validator(k) then
- return
- end
- if del and v == nil then
- return del(k)
- end
- return set(k, v)
- end
- function mt:__index(k)
- if not validator(k) then
- return
- end
- return get(k)
- end
- return setmetatable({}, mt)
- end
- vim.env = make_meta_accessor(function(k)
- local v = vim.fn.getenv(k)
- if v == vim.NIL then
- return nil
- end
- return v
- end, vim.fn.setenv)
- do -- buffer option accessor
- local function new_buf_opt_accessor(bufnr)
- local function get(k)
- if bufnr == nil and type(k) == "number" then
- return new_buf_opt_accessor(k)
- end
- return a.nvim_buf_get_option(bufnr or 0, k)
- end
- local function set(k, v)
- return a.nvim_buf_set_option(bufnr or 0, k, v)
- end
- return make_meta_accessor(get, set, nil, function(k)
- if type(k) == 'string' then
- if win_options[k] then
- error(string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k))
- elseif glb_options[k] then
- error(string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k))
- end
- end
- return true
- end)
- end
- vim.bo = new_buf_opt_accessor(nil)
- end
- do -- window option accessor
- local function new_win_opt_accessor(winnr)
- local function get(k)
- if winnr == nil and type(k) == "number" then
- return new_win_opt_accessor(k)
- end
- return a.nvim_win_get_option(winnr or 0, k)
- end
- local function set(k, v)
- return a.nvim_win_set_option(winnr or 0, k, v)
- end
- return make_meta_accessor(get, set, nil, function(k)
- if type(k) == 'string' then
- if buf_options[k] then
- error(string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k))
- elseif glb_options[k] then
- error(string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k))
- end
- end
- return true
- end)
- end
- vim.wo = new_win_opt_accessor(nil)
- end
- -- vim global option
- -- this ONLY sets the global option. like `setglobal`
- vim.go = make_meta_accessor(
- function(k) return a.nvim_get_option_value(k, {scope = "global"}) end,
- function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end
- )
- -- vim `set` style options.
- -- it has no additional metamethod magic.
- vim.o = make_meta_accessor(
- function(k) return a.nvim_get_option_value(k, {}) end,
- function(k, v) return a.nvim_set_option_value(k, v, {}) end
- )
- ---@brief [[
- --- vim.opt, vim.opt_local and vim.opt_global implementation
- ---
- --- To be used as helpers for working with options within neovim.
- --- For information on how to use, see :help vim.opt
- ---
- ---@brief ]]
- --- Preserves the order and does not mutate the original list
- local remove_duplicate_values = function(t)
- local result, seen = {}, {}
- if type(t) == "function" then error(debug.traceback("asdf")) end
- for _, v in ipairs(t) do
- if not seen[v] then
- table.insert(result, v)
- end
- seen[v] = true
- end
- return result
- end
- -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
- -- Can be done in a separate PR.
- local key_value_options = {
- fillchars = true,
- listchars = true,
- winhl = true,
- }
- ---@class OptionTypes
- --- Option Type Enum
- local OptionTypes = setmetatable({
- BOOLEAN = 0,
- NUMBER = 1,
- STRING = 2,
- ARRAY = 3,
- MAP = 4,
- SET = 5,
- }, {
- __index = function(_, k) error("Not a valid OptionType: " .. k) end,
- __newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end,
- })
- --- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
- ---@return OptionType
- local get_option_type = function(name, info)
- if info.type == "boolean" then
- return OptionTypes.BOOLEAN
- elseif info.type == "number" then
- return OptionTypes.NUMBER
- elseif info.type == "string" then
- if not info.commalist and not info.flaglist then
- return OptionTypes.STRING
- end
- if key_value_options[name] then
- assert(info.commalist, "Must be a comma list to use key:value style")
- return OptionTypes.MAP
- end
- if info.flaglist then
- return OptionTypes.SET
- elseif info.commalist then
- return OptionTypes.ARRAY
- end
- error("Fallthrough in OptionTypes")
- else
- error("Not a known info.type:" .. info.type)
- end
- end
- -- Check whether the OptionTypes is allowed for vim.opt
- -- If it does not match, throw an error which indicates which option causes the error.
- local function assert_valid_value(name, value, types)
- local type_of_value = type(value)
- for _, valid_type in ipairs(types) do
- if valid_type == type_of_value then
- return
- end
- end
- error(string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, " or ")))
- end
- local valid_types = {
- [OptionTypes.BOOLEAN] = { "boolean" },
- [OptionTypes.NUMBER] = { "number" },
- [OptionTypes.STRING] = { "string" },
- [OptionTypes.SET] = { "string", "table" },
- [OptionTypes.ARRAY] = { "string", "table" },
- [OptionTypes.MAP] = { "string", "table" },
- }
- --- Convert a lua value to a vimoption_T value
- local convert_value_to_vim = (function()
- -- Map of functions to take a Lua style value and convert to vimoption_T style value.
- -- Each function takes (info, lua_value) -> vim_value
- local to_vim_value = {
- [OptionTypes.BOOLEAN] = function(_, value) return value end,
- [OptionTypes.NUMBER] = function(_, value) return value end,
- [OptionTypes.STRING] = function(_, value) return value end,
- [OptionTypes.SET] = function(info, value)
- if type(value) == "string" then return value end
- if info.flaglist and info.commalist then
- local keys = {}
- for k, v in pairs(value) do
- if v then
- table.insert(keys, k)
- end
- end
- table.sort(keys)
- return table.concat(keys, ",")
- else
- local result = ''
- for k, v in pairs(value) do
- if v then
- result = result .. k
- end
- end
- return result
- end
- end,
- [OptionTypes.ARRAY] = function(info, value)
- if type(value) == "string" then return value end
- if not info.allows_duplicates then
- value = remove_duplicate_values(value)
- end
- return table.concat(value, ",")
- end,
- [OptionTypes.MAP] = function(_, value)
- if type(value) == "string" then return value end
- local result = {}
- for opt_key, opt_value in pairs(value) do
- table.insert(result, string.format("%s:%s", opt_key, opt_value))
- end
- table.sort(result)
- return table.concat(result, ",")
- end,
- }
- return function(name, info, value)
- if value == nil then
- return vim.NIL
- end
- local option_type = get_option_type(name, info)
- assert_valid_value(name, value, valid_types[option_type])
- return to_vim_value[option_type](info, value)
- end
- end)()
- --- Converts a vimoption_T style value to a Lua value
- local convert_value_to_lua = (function()
- -- 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 = {
- [OptionTypes.BOOLEAN] = function(_, value) return value end,
- [OptionTypes.NUMBER] = function(_, value) return value end,
- [OptionTypes.STRING] = function(_, value) return value end,
- [OptionTypes.ARRAY] = function(info, value)
- if type(value) == "table" then
- if not info.allows_duplicates then
- value = remove_duplicate_values(value)
- end
- return value
- end
- -- 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 string.find(value, ",,,") then
- local comma_split = vim.split(value, ",,,")
- local left = comma_split[1]
- local right = comma_split[2]
- 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 string.find(value, ",^,,", 1, true) then
- local comma_split = vim.split(value, ",^,,", true)
- local left = comma_split[1]
- local right = comma_split[2]
- 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,
- [OptionTypes.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")
- if info.flaglist and info.commalist then
- local split_value = vim.split(value, ",")
- local result = {}
- for _, v in ipairs(split_value) do
- result[v] = true
- end
- return result
- else
- local result = {}
- for i = 1, #value do
- result[value:sub(i, i)] = true
- end
- return result
- end
- end,
- [OptionTypes.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 = {}
- local comma_split = vim.split(raw_value, ",")
- for _, key_value_str in ipairs(comma_split) do
- local key, value = unpack(vim.split(key_value_str, ":"))
- key = vim.trim(key)
- result[key] = value
- end
- return result
- end,
- }
- return function(name, info, option_value)
- return to_lua_value[get_option_type(name, info)](info, option_value)
- end
- end)()
- --- Handles the mutation of various different values.
- local value_mutator = function(name, info, current, new, mutator)
- return mutator[get_option_type(name, info)](current, new)
- end
- --- Handles the '^' operator
- local prepend_value = (function()
- local methods = {
- [OptionTypes.NUMBER] = function()
- error("The '^' operator is not currently supported for")
- end,
- [OptionTypes.STRING] = function(left, right)
- return right .. left
- end,
- [OptionTypes.ARRAY] = function(left, right)
- for i = #right, 1, -1 do
- table.insert(left, 1, right[i])
- end
- return left
- end,
- [OptionTypes.MAP] = function(left, right)
- return vim.tbl_extend("force", left, right)
- end,
- [OptionTypes.SET] = function(left, right)
- return vim.tbl_extend("force", left, right)
- end,
- }
- return function(name, info, current, new)
- return value_mutator(
- name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
- )
- end
- end)()
- --- Handles the '+' operator
- local add_value = (function()
- local methods = {
- [OptionTypes.NUMBER] = function(left, right)
- return left + right
- end,
- [OptionTypes.STRING] = function(left, right)
- return left .. right
- end,
- [OptionTypes.ARRAY] = function(left, right)
- for _, v in ipairs(right) do
- table.insert(left, v)
- end
- return left
- end,
- [OptionTypes.MAP] = function(left, right)
- return vim.tbl_extend("force", left, right)
- end,
- [OptionTypes.SET] = function(left, right)
- return vim.tbl_extend("force", left, right)
- end,
- }
- return function(name, info, current, new)
- return value_mutator(
- name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
- )
- end
- end)()
- --- Handles the '-' operator
- local remove_value = (function()
- local remove_one_item = function(t, val)
- if vim.tbl_islist(t) then
- local remove_index = nil
- for i, v in ipairs(t) do
- if v == val then
- remove_index = i
- end
- end
- if remove_index then
- table.remove(t, remove_index)
- end
- else
- t[val] = nil
- end
- end
- local methods = {
- [OptionTypes.NUMBER] = function(left, right)
- return left - right
- end,
- [OptionTypes.STRING] = function()
- error("Subtraction not supported for strings.")
- end,
- [OptionTypes.ARRAY] = function(left, right)
- if type(right) == "string" then
- remove_one_item(left, right)
- else
- for _, v in ipairs(right) do
- remove_one_item(left, v)
- end
- end
- return left
- end,
- [OptionTypes.MAP] = function(left, right)
- if type(right) == "string" then
- left[right] = nil
- else
- for _, v in ipairs(right) do
- left[v] = nil
- end
- end
- return left
- end,
- [OptionTypes.SET] = function(left, right)
- if type(right) == "string" then
- left[right] = nil
- else
- for _, v in ipairs(right) do
- left[v] = nil
- end
- end
- return left
- end,
- }
- return function(name, info, current, new)
- return value_mutator(name, info, convert_value_to_lua(name, info, current), new, methods)
- end
- end)()
- local create_option_metatable = function(set_type)
- local set_mt, option_mt
- local make_option = function(name, value)
- local info = assert(options_info[name], "Not a valid option name: " .. name)
- if type(value) == "table" and getmetatable(value) == option_mt then
- assert(name == value._name, "must be the same value, otherwise that's weird.")
- value = value._value
- end
- return setmetatable({
- _name = name,
- _value = value,
- _info = info,
- }, option_mt)
- end
- local scope
- if set_type == SET_TYPES.GLOBAL then
- scope = "global"
- elseif set_type == SET_TYPES.LOCAL then
- scope = "local"
- end
- option_mt = {
- -- To set a value, instead use:
- -- opt[my_option] = value
- _set = function(self)
- local value = convert_value_to_vim(self._name, self._info, self._value)
- a.nvim_set_option_value(self._name, value, {scope = scope})
- return self
- end,
- get = function(self)
- return convert_value_to_lua(self._name, self._info, self._value)
- end,
- append = function(self, right)
- return self:__add(right):_set()
- end,
- __add = function(self, right)
- return make_option(self._name, add_value(self._name, self._info, self._value, right))
- end,
- prepend = function(self, right)
- return self:__pow(right):_set()
- end,
- __pow = function(self, right)
- return make_option(self._name, prepend_value(self._name, self._info, self._value, right))
- end,
- remove = function(self, right)
- return self:__sub(right):_set()
- end,
- __sub = function(self, right)
- return make_option(self._name, remove_value(self._name, self._info, self._value, right))
- end
- }
- option_mt.__index = option_mt
- set_mt = {
- __index = function(_, k)
- return make_option(k, a.nvim_get_option_value(k, {scope = scope}))
- end,
- __newindex = function(_, k, v)
- local opt = make_option(k, v)
- opt:_set()
- end,
- }
- return set_mt
- end
- vim.opt = setmetatable({}, create_option_metatable(SET_TYPES.SET))
- vim.opt_local = setmetatable({}, create_option_metatable(SET_TYPES.LOCAL))
- vim.opt_global = setmetatable({}, create_option_metatable(SET_TYPES.GLOBAL))
|