aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_defaults.lua255
-rw-r--r--runtime/lua/vim/_meta.lua1
-rw-r--r--runtime/lua/vim/_meta/options.lua4
-rw-r--r--runtime/lua/vim/termcap.lua52
4 files changed, 227 insertions, 85 deletions
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index c3bb36fc36..b73681be04 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -165,91 +165,92 @@ do
})
end
---- Guess value of 'background' based on terminal color.
----
---- We write Operating System Command (OSC) 11 to the terminal to request the
---- terminal's background color. We then wait for a response. If the response
---- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
---- compute the luminance[1] of the RGB color and classify it as light/dark
---- accordingly. Note that the color components may have anywhere from one to
---- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
---- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
---- ignored in the calculations.
----
---- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
-do
- --- Parse a string of hex characters as a color.
- ---
- --- The string can contain 1 to 4 hex characters. The returned value is
- --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
- ---
- --- For instance, if only a single hex char "a" is used, then this function
- --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
- --- 256).
- ---
- --- @param c string Color as a string of hex chars
- --- @return number? Intensity of the color
- local function parsecolor(c)
- if #c == 0 or #c > 4 then
- return nil
- end
-
- local val = tonumber(c, 16)
- if not val then
- return nil
- end
-
- local max = tonumber(string.rep('f', #c), 16)
- return val / max
+-- Only do the following when the TUI is attached
+local tty = nil
+for _, ui in ipairs(vim.api.nvim_list_uis()) do
+ if ui.chan == 1 and ui.stdout_tty then
+ tty = ui
+ break
end
+end
- --- Parse an OSC 11 response
- ---
- --- Either of the two formats below are accepted:
- ---
- --- OSC 11 ; rgb:<red>/<green>/<blue>
- ---
- --- or
- ---
- --- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
+if tty then
+ --- Guess value of 'background' based on terminal color.
---
- --- where
+ --- We write Operating System Command (OSC) 11 to the terminal to request the
+ --- terminal's background color. We then wait for a response. If the response
+ --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
+ --- compute the luminance[1] of the RGB color and classify it as light/dark
+ --- accordingly. Note that the color components may have anywhere from one to
+ --- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
+ --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
+ --- ignored in the calculations.
---
- --- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
- ---
- --- The alpha component is ignored, if present.
- ---
- --- @param resp string OSC 11 response
- --- @return string? Red component
- --- @return string? Green component
- --- @return string? Blue component
- local function parseosc11(resp)
- local r, g, b
- r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
- if not r and not g and not b then
- local a
- r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
- if not a or #a > 4 then
- return nil, nil, nil
+ --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
+ do
+ --- Parse a string of hex characters as a color.
+ ---
+ --- The string can contain 1 to 4 hex characters. The returned value is
+ --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
+ ---
+ --- For instance, if only a single hex char "a" is used, then this function
+ --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
+ --- 256).
+ ---
+ --- @param c string Color as a string of hex chars
+ --- @return number? Intensity of the color
+ local function parsecolor(c)
+ if #c == 0 or #c > 4 then
+ return nil
end
- end
- if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
- return r, g, b
+ local val = tonumber(c, 16)
+ if not val then
+ return nil
+ end
+
+ local max = tonumber(string.rep('f', #c), 16)
+ return val / max
end
- return nil, nil, nil
- end
+ --- Parse an OSC 11 response
+ ---
+ --- Either of the two formats below are accepted:
+ ---
+ --- OSC 11 ; rgb:<red>/<green>/<blue>
+ ---
+ --- or
+ ---
+ --- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
+ ---
+ --- where
+ ---
+ --- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
+ ---
+ --- The alpha component is ignored, if present.
+ ---
+ --- @param resp string OSC 11 response
+ --- @return string? Red component
+ --- @return string? Green component
+ --- @return string? Blue component
+ local function parseosc11(resp)
+ local r, g, b
+ r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
+ if not r and not g and not b then
+ local a
+ r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
+ if not a or #a > 4 then
+ return nil, nil, nil
+ end
+ end
+
+ if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
+ return r, g, b
+ end
- local tty = false
- for _, ui in ipairs(vim.api.nvim_list_uis()) do
- if ui.chan == 1 and ui.stdout_tty then
- tty = true
- break
+ return nil, nil, nil
end
- end
- if tty then
local timer = assert(vim.uv.new_timer())
---@param bg string New value of the 'background' option
@@ -300,7 +301,7 @@ do
io.stdout:write('\027]11;?\007')
timer:start(1000, 0, function()
- -- No response received. Delete the autocommand
+ -- Delete the autocommand if no response was received
vim.schedule(function()
-- Suppress error if autocommand has already been deleted
pcall(vim.api.nvim_del_autocmd, id)
@@ -311,4 +312,108 @@ do
end
end)
end
+
+ --- If the TUI (term_has_truecolor) was able to determine that the host
+ --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the
+ --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's
+ --- response indicates that it does support truecolor enable 'termguicolors',
+ --- but only if the user has not already disabled it.
+ do
+ if tty.rgb then
+ -- The TUI was able to determine truecolor support
+ vim.o.termguicolors = true
+ else
+ --- Enable 'termguicolors', but only if it was not already set by the user.
+ local function settgc()
+ if not vim.api.nvim_get_option_info2('termguicolors', {}).was_set then
+ vim.o.termguicolors = true
+ end
+ end
+
+ local caps = {} ---@type table<string, boolean>
+ require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found)
+ if not found then
+ return
+ end
+
+ caps[cap] = true
+ if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then
+ settgc()
+ end
+ end)
+
+ local timer = assert(vim.uv.new_timer())
+
+ -- Arbitrary colors to set in the SGR sequence
+ local r = 1
+ local g = 2
+ local b = 3
+
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
+ nested = true,
+ callback = function(args)
+ local resp = args.data ---@type string
+ local decrqss = resp:match('^\027P1%$r([%d;:]+)m$')
+
+ if decrqss then
+ -- The DECRQSS SGR response first contains attributes separated by
+ -- semicolons, followed by the SGR itself with parameters separated
+ -- by colons. Some terminals include "0" in the attribute list
+ -- unconditionally; others do not. Our SGR sequence did not set any
+ -- attributes, so there should be no attributes in the list.
+ local attrs = vim.split(decrqss, ';')
+ if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then
+ return true
+ end
+
+ -- The returned SGR sequence should begin with 48:2
+ local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$')
+ if not sgr then
+ return true
+ end
+
+ -- The remaining elements of the SGR sequence should be the 3 colors
+ -- we set. Some terminals also include an additional parameter
+ -- (which can even be empty!), so handle those cases as well
+ local params = vim.split(sgr, ':')
+ if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then
+ return true
+ end
+
+ if
+ tonumber(params[#params - 2]) == r
+ and tonumber(params[#params - 1]) == g
+ and tonumber(params[#params]) == b
+ then
+ settgc()
+ end
+
+ return true
+ end
+ end,
+ })
+
+ -- Write SGR followed by DECRQSS. This sets the background color then
+ -- immediately asks the terminal what the background color is. If the
+ -- terminal responds to the DECRQSS with the same SGR sequence that we
+ -- sent then the terminal supports truecolor.
+ local decrqss = '\027P$qm\027\\'
+ if os.getenv('TMUX') then
+ decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027'))
+ end
+ io.stdout:write(string.format('\027[48;2;%d;%d;%dm%s', r, g, b, decrqss))
+
+ timer:start(1000, 0, function()
+ -- Delete the autocommand if no response was received
+ vim.schedule(function()
+ -- Suppress error if autocommand has already been deleted
+ pcall(vim.api.nvim_del_autocmd, id)
+ end)
+
+ if not timer:is_closing() then
+ timer:close()
+ end
+ end)
+ end
+ end
end
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
index e3b99f6b3d..bb9ed722e2 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -20,6 +20,7 @@ vim.lsp = require('vim.lsp')
vim.re = require('vim.re')
vim.secure = require('vim.secure')
vim.snippet = require('vim.snippet')
+vim.text = require('vim.text')
vim.treesitter = require('vim.treesitter')
vim.ui = require('vim.ui')
vim.version = require('vim.version')
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index d2bdab4d28..5e65ca6b1b 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -6941,6 +6941,10 @@ vim.go.tbidi = vim.go.termbidi
--- attributes instead of "cterm" attributes. `guifg`
--- Requires an ISO-8613-3 compatible terminal.
---
+--- Nvim will automatically attempt to determine if the host terminal
+--- supports 24-bit color and will enable this option if it does
+--- (unless explicitly disabled by the user).
+---
--- @type boolean
vim.o.termguicolors = false
vim.o.tgc = vim.o.termguicolors
diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua
index 862cc52149..b88d9ac9ba 100644
--- a/runtime/lua/vim/termcap.lua
+++ b/runtime/lua/vim/termcap.lua
@@ -12,7 +12,10 @@ local M = {}
--- emulator supports the XTGETTCAP sequence.
---
--- @param caps string|table A terminal capability or list of capabilities to query
---- @param cb function(cap:string, seq:string) Function to call when a response is received
+--- @param cb function(cap:string, found:bool, seq:string?) Callback function which is called for
+--- each capability in {caps}. {found} is set to true if the capability was found or false
+--- otherwise. {seq} is the control sequence for the capability if found, or nil for
+--- boolean capabilities.
function M.query(caps, cb)
vim.validate({
caps = { caps, { 'string', 'table' } },
@@ -23,21 +26,33 @@ function M.query(caps, cb)
caps = { caps }
end
- local count = #caps
+ local pending = {} ---@type table<string, boolean>
+ for _, v in ipairs(caps) do
+ pending[v] = true
+ end
+
+ local timer = assert(vim.uv.new_timer())
- vim.api.nvim_create_autocmd('TermResponse', {
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
callback = function(args)
local resp = args.data ---@type string
- local k, v = resp:match('^\027P1%+r(%x+)=(%x+)$')
- if k and v then
+ local k, rest = resp:match('^\027P1%+r(%x+)(.*)$')
+ if k and rest then
local cap = vim.text.hexdecode(k)
- local seq =
- vim.text.hexdecode(v):gsub('\\E', '\027'):gsub('%%p%d', ''):gsub('\\(%d+)', string.char)
+ local seq ---@type string?
+ if rest:match('^=%x+$') then
+ seq = vim.text
+ .hexdecode(rest:sub(2))
+ :gsub('\\E', '\027')
+ :gsub('%%p%d', '')
+ :gsub('\\(%d+)', string.char)
+ end
+
+ cb(cap, true, seq)
- cb(cap, seq)
+ pending[cap] = nil
- count = count - 1
- if count == 0 then
+ if next(pending) == nil then
return true
end
end
@@ -57,6 +72,23 @@ function M.query(caps, cb)
end
io.stdout:write(query)
+
+ timer:start(1000, 0, function()
+ -- Delete the autocommand if no response was received
+ vim.schedule(function()
+ -- Suppress error if autocommand has already been deleted
+ pcall(vim.api.nvim_del_autocmd, id)
+
+ -- Call the callback for all capabilities that were not found
+ for k in pairs(pending) do
+ cb(k, false, nil)
+ end
+ end)
+
+ if not timer:is_closing() then
+ timer:close()
+ end
+ end)
end
return M