Repository: neovim
Issue: vim.o (bo/wo/go) works with tables (meta accessors), deprecate vim.opt
Labels: enhancement,lua,breaking-change,options

### Background

The `vim.{o,bo,wo,go}` metamethods allow getting and setting Neovim options from Lua and are full replacements for the `:set` ~~and `:setlocal`~~ commands. (The drawback of `vim.bo` and `vim.wo` over `:set` is that users need to know whether a local option is buffer-local or window-local, but 1. that information is easily looked up via `:h '<option>'` and 2. this is more than made up for by being able to target specific buffers or windows via `vim.bo[bufnr]` or `vim.wo[winid]`, respectively.)

However, Vim has a number of string options that are actually lists (either "commalists" like `runtimepath` or "flaglists" like `formatoptions`) or dictionaries (like `listchars`), which are cumbersome to manipulate as strings from Lua. For this, `vim.opt` offers a convenient special (object-oriented) table-based interface:

For dictionaries:
* `vim.opt.listchars = { space = '_', tab = '>-'}`
* `vim.opt.listchars:append({tab = '>-'})` (same as `set listchars+=tab:>-`)
* `vim.opt.listchars:remove('tab')` (same as `set listchars-=tab:>-`)
* `vim.opt.listchars:get()` (returns a table, e.g. `{ space = '_', tab = '>-'}`)

Similarly for flaglists (which are represented by a "set" (`table<string,boolean>`), e.g.
* `vim.opt.formatoptions = { j = true, c = true}`

And for commalists, which can be ordered (in particular `runtimepath`) additionally
* `vim.opt.runtimepath:append('/tmp')` (same as `set runtimepath+=/tmp`)
* `vim.opt.runtimepath:prepend('/tmp')` (same as `set runtimepath^=/tmp`)
* `vim.opt.runtimepath:remove('/tmp')` (same as `set runtimepath-=/tmp`)


But the existence of two different interfaces is confusing, especially since `vim.opt` can work with number and boolean options as well.

### Proposal

To unify these two interfaces, make `vim.{o,bo,wo,go}` return **tables** for such list- and map-style options (other options are unaffected). This information should be scraped from the metadata in `src/nvim/options.lua`.
One difficulty is that we cannot put too much "metamagic" into these tables without losing the possibility to nicely print or otherwise work with them (hence the need for `:get()` in `vim.opt`). The proposal would be to keep the metamagic in the `vim.o` accessors so that users would only see normal tables. The flip side is that the option keys cannot be manipulated individually (that would require adding an `__index` on the `vim.o.listchars` table), so the option has to be copied to a local table, which can be edited and then reassigned to the option. (The exact same situation occurs when [accessing vimscript dictionaries via `vim.g`](https://github.com/nanotee/nvim-lua-guide#caveats-3).)

The proposal is therefore to add new convenience functions to allow a one-line replacement of the statements above:

* `vim.dict_set_key(table, key, value)` with `value = true` as default
* `vim.dict_del_key(table, key)`
* `vim.list_set_key(table, key, position)` with `position = #table+1` as default
* `vim.list_del_key(table, key)`
* `vim.tbl_del(table, key)`

The first two functions would also be useful for working with `vim.g.dict`; the last function is used to implement the other `del` functions and is of general usefulness.


One can then do for dictionary options (matching the first list above, item by item):
* `vim.o.listchars = { space = '_', tab = '>-'}` (or `= 'space:_,tab:>-'`; the metamethod could dispatch on the argument type)
* `vim.o.listchars = vim.dict_set_key(vim.o.listchars, 'tab', '>-'`)`
* `vim.o.listchars = vim.dict_del_key(vim.o.listchars, 'tab')`
* `vim.o.listchars`

For flaglists:
* `vim.o.formatoptions = { j = true, c = true }` (or `= 'jc'`)
* `vim.o.formatoptions = vim.dict_set_key(vim.o.formatoptions, 'l')`
* `vim.o.formatoptions = vim.dict_del_key(vim.o.formatoptions, 'c')`
* `vim.o.formatoptions`

For commalists:
* `vim.o.runtimepath = vim.list_set_key(vim.o.runtimepath, '/tmp')`
* `vim.o.runtimepath = vim.list_set_key(vim.o.runtimepath, '/tmp', 1)`
* `vim.o.runtimepath = vim.list_del_key(vim.o.runtimepath, '/tmp')`

This would be a breaking change since, e.g., `vim.o.listchars` would now return a table instead of a string;
but it still remains possible to use `vim.api.nvim_get_option_value('listchars', {})` directly to get the raw string.

### Outlook

Further on, the conversion to and from tables could be handled by `vim.api.nvim_{get,set}_option_value` via a new key to `opts` (which should not affect the user interface of `vim.o` and friends).  Even further, it could be useful to change the internal representation of these list- and dict-style options to actual tables and convert to string only when necessary, e.g. for access from Vimscript.