_meta.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. -- prevents luacheck from making lints for setting things on vim
  2. local vim = assert(vim)
  3. local a = vim.api
  4. local validate = vim.validate
  5. local SET_TYPES = setmetatable({
  6. SET = 0,
  7. LOCAL = 1,
  8. GLOBAL = 2,
  9. }, { __index = error })
  10. local options_info = {}
  11. for _, v in pairs(a.nvim_get_all_options_info()) do
  12. options_info[v.name] = v
  13. if v.shortname ~= "" then options_info[v.shortname] = v end
  14. end
  15. local get_scoped_options = function(scope)
  16. local result = {}
  17. for name, option_info in pairs(options_info) do
  18. if option_info.scope == scope then
  19. result[name] = true
  20. end
  21. end
  22. return result
  23. end
  24. local buf_options = get_scoped_options("buf")
  25. local glb_options = get_scoped_options("global")
  26. local win_options = get_scoped_options("win")
  27. local function make_meta_accessor(get, set, del, validator)
  28. validator = validator or function() return true end
  29. validate {
  30. get = {get, 'f'};
  31. set = {set, 'f'};
  32. del = {del, 'f', true};
  33. validator = {validator, 'f'};
  34. }
  35. local mt = {}
  36. function mt:__newindex(k, v)
  37. if not validator(k) then
  38. return
  39. end
  40. if del and v == nil then
  41. return del(k)
  42. end
  43. return set(k, v)
  44. end
  45. function mt:__index(k)
  46. if not validator(k) then
  47. return
  48. end
  49. return get(k)
  50. end
  51. return setmetatable({}, mt)
  52. end
  53. vim.env = make_meta_accessor(function(k)
  54. local v = vim.fn.getenv(k)
  55. if v == vim.NIL then
  56. return nil
  57. end
  58. return v
  59. end, vim.fn.setenv)
  60. do -- buffer option accessor
  61. local function new_buf_opt_accessor(bufnr)
  62. local function get(k)
  63. if bufnr == nil and type(k) == "number" then
  64. return new_buf_opt_accessor(k)
  65. end
  66. return a.nvim_buf_get_option(bufnr or 0, k)
  67. end
  68. local function set(k, v)
  69. return a.nvim_buf_set_option(bufnr or 0, k, v)
  70. end
  71. return make_meta_accessor(get, set, nil, function(k)
  72. if type(k) == 'string' then
  73. if win_options[k] then
  74. error(string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k))
  75. elseif glb_options[k] then
  76. error(string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k))
  77. end
  78. end
  79. return true
  80. end)
  81. end
  82. vim.bo = new_buf_opt_accessor(nil)
  83. end
  84. do -- window option accessor
  85. local function new_win_opt_accessor(winnr)
  86. local function get(k)
  87. if winnr == nil and type(k) == "number" then
  88. return new_win_opt_accessor(k)
  89. end
  90. return a.nvim_win_get_option(winnr or 0, k)
  91. end
  92. local function set(k, v)
  93. return a.nvim_win_set_option(winnr or 0, k, v)
  94. end
  95. return make_meta_accessor(get, set, nil, function(k)
  96. if type(k) == 'string' then
  97. if buf_options[k] then
  98. error(string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k))
  99. elseif glb_options[k] then
  100. error(string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k))
  101. end
  102. end
  103. return true
  104. end)
  105. end
  106. vim.wo = new_win_opt_accessor(nil)
  107. end
  108. -- vim global option
  109. -- this ONLY sets the global option. like `setglobal`
  110. vim.go = make_meta_accessor(
  111. function(k) return a.nvim_get_option_value(k, {scope = "global"}) end,
  112. function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end
  113. )
  114. -- vim `set` style options.
  115. -- it has no additional metamethod magic.
  116. vim.o = make_meta_accessor(
  117. function(k) return a.nvim_get_option_value(k, {}) end,
  118. function(k, v) return a.nvim_set_option_value(k, v, {}) end
  119. )
  120. ---@brief [[
  121. --- vim.opt, vim.opt_local and vim.opt_global implementation
  122. ---
  123. --- To be used as helpers for working with options within neovim.
  124. --- For information on how to use, see :help vim.opt
  125. ---
  126. ---@brief ]]
  127. --- Preserves the order and does not mutate the original list
  128. local remove_duplicate_values = function(t)
  129. local result, seen = {}, {}
  130. if type(t) == "function" then error(debug.traceback("asdf")) end
  131. for _, v in ipairs(t) do
  132. if not seen[v] then
  133. table.insert(result, v)
  134. end
  135. seen[v] = true
  136. end
  137. return result
  138. end
  139. -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
  140. -- Can be done in a separate PR.
  141. local key_value_options = {
  142. fillchars = true,
  143. listchars = true,
  144. winhl = true,
  145. }
  146. ---@class OptionTypes
  147. --- Option Type Enum
  148. local OptionTypes = setmetatable({
  149. BOOLEAN = 0,
  150. NUMBER = 1,
  151. STRING = 2,
  152. ARRAY = 3,
  153. MAP = 4,
  154. SET = 5,
  155. }, {
  156. __index = function(_, k) error("Not a valid OptionType: " .. k) end,
  157. __newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end,
  158. })
  159. --- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
  160. ---@return OptionType
  161. local get_option_type = function(name, info)
  162. if info.type == "boolean" then
  163. return OptionTypes.BOOLEAN
  164. elseif info.type == "number" then
  165. return OptionTypes.NUMBER
  166. elseif info.type == "string" then
  167. if not info.commalist and not info.flaglist then
  168. return OptionTypes.STRING
  169. end
  170. if key_value_options[name] then
  171. assert(info.commalist, "Must be a comma list to use key:value style")
  172. return OptionTypes.MAP
  173. end
  174. if info.flaglist then
  175. return OptionTypes.SET
  176. elseif info.commalist then
  177. return OptionTypes.ARRAY
  178. end
  179. error("Fallthrough in OptionTypes")
  180. else
  181. error("Not a known info.type:" .. info.type)
  182. end
  183. end
  184. -- Check whether the OptionTypes is allowed for vim.opt
  185. -- If it does not match, throw an error which indicates which option causes the error.
  186. local function assert_valid_value(name, value, types)
  187. local type_of_value = type(value)
  188. for _, valid_type in ipairs(types) do
  189. if valid_type == type_of_value then
  190. return
  191. end
  192. end
  193. error(string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, " or ")))
  194. end
  195. local valid_types = {
  196. [OptionTypes.BOOLEAN] = { "boolean" },
  197. [OptionTypes.NUMBER] = { "number" },
  198. [OptionTypes.STRING] = { "string" },
  199. [OptionTypes.SET] = { "string", "table" },
  200. [OptionTypes.ARRAY] = { "string", "table" },
  201. [OptionTypes.MAP] = { "string", "table" },
  202. }
  203. --- Convert a lua value to a vimoption_T value
  204. local convert_value_to_vim = (function()
  205. -- Map of functions to take a Lua style value and convert to vimoption_T style value.
  206. -- Each function takes (info, lua_value) -> vim_value
  207. local to_vim_value = {
  208. [OptionTypes.BOOLEAN] = function(_, value) return value end,
  209. [OptionTypes.NUMBER] = function(_, value) return value end,
  210. [OptionTypes.STRING] = function(_, value) return value end,
  211. [OptionTypes.SET] = function(info, value)
  212. if type(value) == "string" then return value end
  213. if info.flaglist and info.commalist then
  214. local keys = {}
  215. for k, v in pairs(value) do
  216. if v then
  217. table.insert(keys, k)
  218. end
  219. end
  220. table.sort(keys)
  221. return table.concat(keys, ",")
  222. else
  223. local result = ''
  224. for k, v in pairs(value) do
  225. if v then
  226. result = result .. k
  227. end
  228. end
  229. return result
  230. end
  231. end,
  232. [OptionTypes.ARRAY] = function(info, value)
  233. if type(value) == "string" then return value end
  234. if not info.allows_duplicates then
  235. value = remove_duplicate_values(value)
  236. end
  237. return table.concat(value, ",")
  238. end,
  239. [OptionTypes.MAP] = function(_, value)
  240. if type(value) == "string" then return value end
  241. local result = {}
  242. for opt_key, opt_value in pairs(value) do
  243. table.insert(result, string.format("%s:%s", opt_key, opt_value))
  244. end
  245. table.sort(result)
  246. return table.concat(result, ",")
  247. end,
  248. }
  249. return function(name, info, value)
  250. if value == nil then
  251. return vim.NIL
  252. end
  253. local option_type = get_option_type(name, info)
  254. assert_valid_value(name, value, valid_types[option_type])
  255. return to_vim_value[option_type](info, value)
  256. end
  257. end)()
  258. --- Converts a vimoption_T style value to a Lua value
  259. local convert_value_to_lua = (function()
  260. -- Map of OptionType to functions that take vimoption_T values and convert to lua values.
  261. -- Each function takes (info, vim_value) -> lua_value
  262. local to_lua_value = {
  263. [OptionTypes.BOOLEAN] = function(_, value) return value end,
  264. [OptionTypes.NUMBER] = function(_, value) return value end,
  265. [OptionTypes.STRING] = function(_, value) return value end,
  266. [OptionTypes.ARRAY] = function(info, value)
  267. if type(value) == "table" then
  268. if not info.allows_duplicates then
  269. value = remove_duplicate_values(value)
  270. end
  271. return value
  272. end
  273. -- Empty strings mean that there is nothing there,
  274. -- so empty table should be returned.
  275. if value == '' then
  276. return {}
  277. end
  278. -- Handles unescaped commas in a list.
  279. if string.find(value, ",,,") then
  280. local comma_split = vim.split(value, ",,,")
  281. local left = comma_split[1]
  282. local right = comma_split[2]
  283. local result = {}
  284. vim.list_extend(result, vim.split(left, ","))
  285. table.insert(result, ",")
  286. vim.list_extend(result, vim.split(right, ","))
  287. table.sort(result)
  288. return result
  289. end
  290. if string.find(value, ",^,,", 1, true) then
  291. local comma_split = vim.split(value, ",^,,", true)
  292. local left = comma_split[1]
  293. local right = comma_split[2]
  294. local result = {}
  295. vim.list_extend(result, vim.split(left, ","))
  296. table.insert(result, "^,")
  297. vim.list_extend(result, vim.split(right, ","))
  298. table.sort(result)
  299. return result
  300. end
  301. return vim.split(value, ",")
  302. end,
  303. [OptionTypes.SET] = function(info, value)
  304. if type(value) == "table" then return value end
  305. -- Empty strings mean that there is nothing there,
  306. -- so empty table should be returned.
  307. if value == '' then
  308. return {}
  309. end
  310. assert(info.flaglist, "That is the only one I know how to handle")
  311. if info.flaglist and info.commalist then
  312. local split_value = vim.split(value, ",")
  313. local result = {}
  314. for _, v in ipairs(split_value) do
  315. result[v] = true
  316. end
  317. return result
  318. else
  319. local result = {}
  320. for i = 1, #value do
  321. result[value:sub(i, i)] = true
  322. end
  323. return result
  324. end
  325. end,
  326. [OptionTypes.MAP] = function(info, raw_value)
  327. if type(raw_value) == "table" then return raw_value end
  328. assert(info.commalist, "Only commas are supported currently")
  329. local result = {}
  330. local comma_split = vim.split(raw_value, ",")
  331. for _, key_value_str in ipairs(comma_split) do
  332. local key, value = unpack(vim.split(key_value_str, ":"))
  333. key = vim.trim(key)
  334. result[key] = value
  335. end
  336. return result
  337. end,
  338. }
  339. return function(name, info, option_value)
  340. return to_lua_value[get_option_type(name, info)](info, option_value)
  341. end
  342. end)()
  343. --- Handles the mutation of various different values.
  344. local value_mutator = function(name, info, current, new, mutator)
  345. return mutator[get_option_type(name, info)](current, new)
  346. end
  347. --- Handles the '^' operator
  348. local prepend_value = (function()
  349. local methods = {
  350. [OptionTypes.NUMBER] = function()
  351. error("The '^' operator is not currently supported for")
  352. end,
  353. [OptionTypes.STRING] = function(left, right)
  354. return right .. left
  355. end,
  356. [OptionTypes.ARRAY] = function(left, right)
  357. for i = #right, 1, -1 do
  358. table.insert(left, 1, right[i])
  359. end
  360. return left
  361. end,
  362. [OptionTypes.MAP] = function(left, right)
  363. return vim.tbl_extend("force", left, right)
  364. end,
  365. [OptionTypes.SET] = function(left, right)
  366. return vim.tbl_extend("force", left, right)
  367. end,
  368. }
  369. return function(name, info, current, new)
  370. return value_mutator(
  371. name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
  372. )
  373. end
  374. end)()
  375. --- Handles the '+' operator
  376. local add_value = (function()
  377. local methods = {
  378. [OptionTypes.NUMBER] = function(left, right)
  379. return left + right
  380. end,
  381. [OptionTypes.STRING] = function(left, right)
  382. return left .. right
  383. end,
  384. [OptionTypes.ARRAY] = function(left, right)
  385. for _, v in ipairs(right) do
  386. table.insert(left, v)
  387. end
  388. return left
  389. end,
  390. [OptionTypes.MAP] = function(left, right)
  391. return vim.tbl_extend("force", left, right)
  392. end,
  393. [OptionTypes.SET] = function(left, right)
  394. return vim.tbl_extend("force", left, right)
  395. end,
  396. }
  397. return function(name, info, current, new)
  398. return value_mutator(
  399. name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
  400. )
  401. end
  402. end)()
  403. --- Handles the '-' operator
  404. local remove_value = (function()
  405. local remove_one_item = function(t, val)
  406. if vim.tbl_islist(t) then
  407. local remove_index = nil
  408. for i, v in ipairs(t) do
  409. if v == val then
  410. remove_index = i
  411. end
  412. end
  413. if remove_index then
  414. table.remove(t, remove_index)
  415. end
  416. else
  417. t[val] = nil
  418. end
  419. end
  420. local methods = {
  421. [OptionTypes.NUMBER] = function(left, right)
  422. return left - right
  423. end,
  424. [OptionTypes.STRING] = function()
  425. error("Subtraction not supported for strings.")
  426. end,
  427. [OptionTypes.ARRAY] = function(left, right)
  428. if type(right) == "string" then
  429. remove_one_item(left, right)
  430. else
  431. for _, v in ipairs(right) do
  432. remove_one_item(left, v)
  433. end
  434. end
  435. return left
  436. end,
  437. [OptionTypes.MAP] = function(left, right)
  438. if type(right) == "string" then
  439. left[right] = nil
  440. else
  441. for _, v in ipairs(right) do
  442. left[v] = nil
  443. end
  444. end
  445. return left
  446. end,
  447. [OptionTypes.SET] = function(left, right)
  448. if type(right) == "string" then
  449. left[right] = nil
  450. else
  451. for _, v in ipairs(right) do
  452. left[v] = nil
  453. end
  454. end
  455. return left
  456. end,
  457. }
  458. return function(name, info, current, new)
  459. return value_mutator(name, info, convert_value_to_lua(name, info, current), new, methods)
  460. end
  461. end)()
  462. local create_option_metatable = function(set_type)
  463. local set_mt, option_mt
  464. local make_option = function(name, value)
  465. local info = assert(options_info[name], "Not a valid option name: " .. name)
  466. if type(value) == "table" and getmetatable(value) == option_mt then
  467. assert(name == value._name, "must be the same value, otherwise that's weird.")
  468. value = value._value
  469. end
  470. return setmetatable({
  471. _name = name,
  472. _value = value,
  473. _info = info,
  474. }, option_mt)
  475. end
  476. local scope
  477. if set_type == SET_TYPES.GLOBAL then
  478. scope = "global"
  479. elseif set_type == SET_TYPES.LOCAL then
  480. scope = "local"
  481. end
  482. option_mt = {
  483. -- To set a value, instead use:
  484. -- opt[my_option] = value
  485. _set = function(self)
  486. local value = convert_value_to_vim(self._name, self._info, self._value)
  487. a.nvim_set_option_value(self._name, value, {scope = scope})
  488. return self
  489. end,
  490. get = function(self)
  491. return convert_value_to_lua(self._name, self._info, self._value)
  492. end,
  493. append = function(self, right)
  494. return self:__add(right):_set()
  495. end,
  496. __add = function(self, right)
  497. return make_option(self._name, add_value(self._name, self._info, self._value, right))
  498. end,
  499. prepend = function(self, right)
  500. return self:__pow(right):_set()
  501. end,
  502. __pow = function(self, right)
  503. return make_option(self._name, prepend_value(self._name, self._info, self._value, right))
  504. end,
  505. remove = function(self, right)
  506. return self:__sub(right):_set()
  507. end,
  508. __sub = function(self, right)
  509. return make_option(self._name, remove_value(self._name, self._info, self._value, right))
  510. end
  511. }
  512. option_mt.__index = option_mt
  513. set_mt = {
  514. __index = function(_, k)
  515. return make_option(k, a.nvim_get_option_value(k, {scope = scope}))
  516. end,
  517. __newindex = function(_, k, v)
  518. local opt = make_option(k, v)
  519. opt:_set()
  520. end,
  521. }
  522. return set_mt
  523. end
  524. vim.opt = setmetatable({}, create_option_metatable(SET_TYPES.SET))
  525. vim.opt_local = setmetatable({}, create_option_metatable(SET_TYPES.LOCAL))
  526. vim.opt_global = setmetatable({}, create_option_metatable(SET_TYPES.GLOBAL))