Repository: neovim
Issue: option scopes redesign
Labels: refactor,compatibility,complexity:high,options,architecture

Currently options can have the following scopes:

- Buffer local. These options, despite the name, do still have a global value which is used as the default.
- Window local: Despite the name, these options can be dependent on the buffer as well. You can set a window option for all buffers on that window, or for a single buffer, which means there is nested scoping at play.
- Global-local options: These options have a global value and a value that is local to the buffer/window. When a local value doesn't exist, the global value is used instead.
- There is also a much lesser known tab scope for options, which is currently only used for `cmdheight`.

These scopes are confusing even to veteran Vim/Neovim users, including Neovim developers. They do not follow a consistent or intuitive pattern and have all sorts of quirks, which can befuddle new and old users alike. The maintenance burden is also much higher for options because of this reason.

I propose a complete revamp of the scoping method we use. Note that while this is a revamp, it aims to keep most of the behavior that makes sense, while removing the unintuitive behaviors of the current method. 

My proposal is, instead of having different scoping schemes for different options, we instead have a set of scopes that follow a linear hierarchy. Which could look like:
Global scope -> Tab scope -> Window scope -> Buffer scope.

An option could choose to support one or more (any number) of these scopes. So an option could just use the global scope, or the global and buffer scope, or the global, window and buffer scope.

The value of the innermost scope in the scope hierarchy will always be used. For example: If an option has global, window and buffer scopes, first it will check for a value in the buffer scope and use it if it exists, then check window scope if there is no value in the buffer scope, and then use the global value if window scope doesn't have a value either.

Through this method, the current scopes could be implemented like this:
1. Buffer local and Global-local buffer options would use the Global and Buffer scopes.
2. Window local options would use the Global, Window and Buffer scopes.
3. Global-local window options would use the Global and Window scopes.
4. Global-local tab options would use the Global and Tab scopes.

This _should_ keep most of the behavior that options currently have, whilst getting rid of the weird quirks and inconsistencies. Buffer-local and global-local options will have almost the same behavior as they do now. Window-local options will have a lot of breaking changes, but window-local options are already broken and unintuitive as they are currently, so we have to fix that one way or another anyway.

This approach also allows us the opportunity to do things like add an option that uses a new combination of scopes (e.g. tab and window) in the future, with zero additional complexity.

Keeping almost complete backwards compatibility with `:set` and other Vim script option interfaces is also an explicit goal of this revamp. This is to ensure Vim plugins don't have to do anything special to continue supporting Neovim.

Lua interfaces for setting options and the Neovim API will have some changed behavior due to the change in scoping.

CC: @bfredl, @justinmk, @zeertzjq, @lewis6991, @dundargoc