Skip to content

Commit

Permalink
feat: better :checkhealth, add default WSL reg.exe location, othe…
Browse files Browse the repository at this point in the history
…r various refactor
  • Loading branch information
nekowinston committed Oct 10, 2024
1 parent b221547 commit 3022174
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 53 deletions.
46 changes: 30 additions & 16 deletions lua/auto-dark-mode/health.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
local M = {}

local uv = vim.uv or vim.loop

local adm = require("auto-dark-mode")
local interval = require("auto-dark-mode.interval")

M.benchmark = function(iterations)
local results = {}

for _ = 1, iterations do
local _start = vim.uv.hrtime()
vim.system(adm.state.query_command, { text = true }):wait()
local _end = vim.uv.hrtime()
local _start = uv.hrtime()
-- by using an empty function, parsing the response is measured, but
-- actually syncing the vim theme isn't performed
interval.poll_dark_mode(function() end)
local _end = uv.hrtime()

table.insert(results, (_end - _start) / 1000000)
end

Expand All @@ -24,37 +30,45 @@ M.benchmark = function(iterations)
return { avg = sum / #results, max = max, min = min }
end

-- support for neovim < 0.9.0
local H = vim.health
local health = {}
health.start = H.start or H.report_start
health.ok = H.ok or H.report_ok
health.info = H.info or H.report_info
health.error = H.error or H.report_error

M.check = function()
vim.health.start("auto-dark-mode.nvim")
health.start("auto-dark-mode.nvim")

if adm.state.setup_correct then
vim.health.ok("Setup is correct")
health.ok("Setup is correct")
else
vim.health.error("Setup is incorrect")
health.error("Setup is incorrect")
end

vim.health.info("Detected operating system: " .. adm.state.system)
vim.health.info("Using query command: `" .. table.concat(adm.state.query_command, " ") .. "`")
health.info(string.format("Detected operating system: %s", adm.state.system))
health.info(string.format("Using query command: `%s`", table.concat(adm.state.query_command, " ")))

local benchmark = M.benchmark(30)
vim.health.info(
health.info(
string.format("Benchmark: %.2fms avg / %.2fms min / %.2fms max", benchmark.avg, benchmark.min, benchmark.max)
)

local interval = adm.options.update_interval
local ratio = interval / benchmark.avg
local info = string.format("Update interval (%dms) is %.2fx the average query time", interval, ratio)
local update_interval = adm.options.update_interval
local ratio = update_interval / benchmark.avg
local info = string.format("Update interval (%dms) is %.2fx the average query time", update_interval, ratio)
local error = string.format(
"Update interval (%dms) seems too short compared to current benchmarks, consider increasing it",
interval
update_interval
)

if ratio > 30 then
vim.health.ok(info)
health.ok(info)
elseif ratio > 5 then
vim.health.warn(info)
health.warn(info)
else
vim.health.error(error)
health.error(error)
end
end

Expand Down
64 changes: 38 additions & 26 deletions lua/auto-dark-mode/init.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
local M = {}

local uv = vim.uv or vim.loop

---@alias Appearance "light" | "dark"
---@alias DetectedOS "Linux" | "Darwin" | "Windows_NT" | "WSL"

---@class AutoDarkModeOptions
local default_options = {
-- Optional. Fallback theme to use if the system theme can't be detected.
-- Useful for linux and environments without a desktop manager.
---@type Appearance?
fallback = "dark",

-- Optional. If not provided, `vim.api.nvim_set_option_value('background', 'dark', {})` will be used.
---@type fun(): nil | nil
set_dark_mode = function()
Expand All @@ -17,28 +24,23 @@ local default_options = {
vim.api.nvim_set_option_value("background", "light", {})
end,

-- Every `update_interval` milliseconds a theme check will be performed.
-- Optional. Specifies the `update_interval` milliseconds a theme check will be performed.
---@type number?
update_interval = 3000,

-- Optional. Fallback theme to use if the system theme can't be detected.
-- Useful for linux and environments without a desktop manager.
---@type Appearance
fallback = "dark",
}

local function validate_options(options)
vim.validate({
set_dark_mode = { options.set_dark_mode, "function" },
set_light_mode = { options.set_light_mode, "function" },
update_interval = { options.update_interval, "number" },
fallback = {
options.fallback,
function(opt)
return opt == "dark" or opt == "light"
end,
"`fallback` must be either 'light' or 'dark'",
},
set_dark_mode = { options.set_dark_mode, "function" },
set_light_mode = { options.set_light_mode, "function" },
update_interval = { options.update_interval, "number" },
})
M.state.setup_correct = true
end
Expand All @@ -53,21 +55,11 @@ M.state = {
query_command = {},
}

-- map the vim.loop functions to vim.uv if available
local getuid = vim.uv.getuid or vim.loop.getuid

M.init = function()
local os_uname = vim.uv.os_uname() or vim.loop.os_uname()
local os_uname = uv.os_uname()

if string.match(os_uname.release, "WSL") then
M.state.system = "WSL"
if not vim.fn.executable("reg.exe") then
error([[
auto-dark-mode.nvim:
`reg.exe` is not available. To support syncing with the host system,
this plugin relies on `reg.exe` being on the `$PATH`.
]])
end
else
M.state.system = os_uname.sysname
end
Expand All @@ -77,9 +69,9 @@ M.init = function()
elseif M.state.system == "Linux" then
if not vim.fn.executable("dbus-send") then
error([[
auto-dark-mode.nvim:
`dbus-send` is not available. The Linux implementation of
auto-dark-mode.nvim relies on `dbus-send` being on the `$PATH`.
auto-dark-mode.nvim:
`dbus-send` is not available. The Linux implementation of
auto-dark-mode.nvim relies on `dbus-send` being on the `$PATH`.
]])
end

Expand All @@ -95,8 +87,28 @@ M.init = function()
"string:color-scheme",
}
elseif M.state.system == "Windows_NT" or M.state.system == "WSL" then
local reg = "reg.exe"

-- on WSL, if `reg.exe` cannot be found on the `$PATH`
-- (see interop.appendWindowsPath https://learn.microsoft.com/en-us/windows/wsl/wsl-config),
-- assume that it's in the default location
if M.state.system == "WSL" and not vim.fn.executable("reg.exe") then
local assumed_path = "/mnt/c/Windows/system32/reg.exe"

if vim.fn.filereadable(assumed_path) then
reg = assumed_path
else
-- `reg.exe` isn't on `$PATH` or in the default location, so throw an error
error([[
auto-dark-mode.nvim:
`reg.exe` is not available. To support syncing with the host system,
this plugin relies on `reg.exe` being on the `$PATH`.
]])
end
end

M.state.query_command = {
"reg.exe",
reg,
"Query",
"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"/v",
Expand All @@ -107,7 +119,7 @@ M.init = function()
end

-- when on a supported unix system, and the userid is root
if (M.state.system == "Darwin" or M.state.system == "Linux") and getuid() == 0 then
if (M.state.system == "Darwin" or M.state.system == "Linux") and uv.getuid() == 0 then
local sudo_user = vim.env.SUDO_USER

if sudo_user ~= nil then
Expand All @@ -122,7 +134,7 @@ M.init = function()
auto-dark-mode.nvim:
Running as `root`, but `$SUDO_USER` is not set.
Please open an issue to add support for your system.
]])
]])
end
end

Expand Down
37 changes: 26 additions & 11 deletions lua/auto-dark-mode/interval.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ local M = {
currently_in_dark_mode = nil,
}

local uv = vim.uv or vim.loop

-- Parses the query response for each system, returning `true` if the system is
-- in Dark mode, `false` when in Light mode.
---@param res string
Expand Down Expand Up @@ -35,8 +37,10 @@ local function parse_query_response(res)
return false
end

-- Executes the `set_dark_mode` and `set_light_mode` hooks when needed,
-- otherwise it's a no-op.
---@param is_dark_mode boolean
local function change_theme_if_needed(is_dark_mode)
local function sync_theme(is_dark_mode)
if is_dark_mode == M.currently_in_dark_mode then
return
end
Expand All @@ -57,11 +61,17 @@ local function change_theme_if_needed(is_dark_mode)
end
end

M.poll_dark_mode = function()
---@param callback? fun(is_dark_mode: boolean): nil
M.poll_dark_mode = function(callback)
-- if no callback is provided, use a no-op
if callback == nil then
callback = function() end
end

if vim.system then
vim.system(M.state.query_command, { text = true }, function(data)
local is_dark_mode = parse_query_response(data.stdout)
change_theme_if_needed(is_dark_mode)
callback(is_dark_mode)
end)
else
-- Legacy implementation using `vim.fn.jobstart` instead of `vim.system`,
Expand All @@ -70,7 +80,7 @@ M.poll_dark_mode = function()
stdout_buffered = true,
on_stdout = function(_, data, _)
local is_dark_mode = parse_query_response(table.concat(data, "\n"))
change_theme_if_needed(is_dark_mode)
callback(is_dark_mode)
end,
})
end
Expand All @@ -80,17 +90,22 @@ M.start_timer = function()
---@type number
local interval = M.options.update_interval

if vim.uv.new_timer or vim.loop.new_timer then
M.timer = vim.uv.new_timer() or vim.loop.new_timer()
M.timer:start(interval, interval, M.poll_dark_mode)
local timer_callback = function()
M.poll_dark_mode(sync_theme)
end

-- needs to check for `vim.system` because the poll function depends on it
if uv and vim.system then
M.timer = uv.new_timer()
M.timer:start(interval, interval, timer_callback)
else
M.timer_id = vim.fn.timer_start(interval, M.poll_dark_mode, { ["repeat"] = -1 })
M.timer_id = vim.fn.timer_start(interval, timer_callback, { ["repeat"] = -1 })
end
end

M.stop_timer = function()
if vim.uv.timer_stop or vim.loop.timer_stop then
vim.uv.timer_stop(M.timer)
if uv.timer_stop then
uv.timer_stop(M.timer)
else
vim.fn.timer_stop(M.timer_id)
end
Expand All @@ -102,7 +117,7 @@ M.start = function(options, state)
M.options = options
M.state = state

M.poll_dark_mode()
M.poll_dark_mode(sync_theme)
M.start_timer()
end

Expand Down

0 comments on commit 3022174

Please sign in to comment.