Some checks are pending
quality / changes (push) Waiting to run
quality / Lua Format Check (push) Blocked by required conditions
quality / Lua Lint Check (push) Blocked by required conditions
quality / Lua Type Check (push) Blocked by required conditions
quality / Markdown Format Check (push) Blocked by required conditions
## Problem
Calling \`:LiveServerStart\` from an oil.nvim buffer (or any URI-scheme
buffer) caused two issues: first, \`find_cached_dir\` entered an
infinite loop as \`fnamemodify(cur, ':h')\` degenerated to \`.\`,
freezing Neovim and pegging the CPU; second, even after fixing the loop,
the server would error out instead of doing the right thing — serving
the directory being browsed.
## Solution
Add a progress check to \`find_cached_dir\` so it bails if the path
stops changing. Fix \`resolve_dir\` to detect URI-scheme buffer names
(e.g. \`oil:///path/to/dir\`) and extract the real filesystem path from
them, so \`:LiveServerStart\` correctly serves the browsed directory.
Also corrects the bug report repro template (\`require('lazy')\`, \`lazy
= false\`, no deprecated \`opts\`) and ignores \`repro.lua\`.
Closes #34
244 lines
5.2 KiB
Lua
244 lines
5.2 KiB
Lua
local server = require('live-server.server')
|
|
|
|
local M = {}
|
|
|
|
---@type boolean
|
|
local initialized = false
|
|
|
|
---@type table<string, live_server.Instance>
|
|
local instances = {}
|
|
|
|
---@class live_server.Config
|
|
---@field port? integer
|
|
---@field browser? boolean
|
|
---@field debounce? integer
|
|
---@field ignore? string[]
|
|
---@field css_inject? boolean
|
|
---@field debug? boolean
|
|
|
|
---@type live_server.Config
|
|
local defaults = {
|
|
port = 5500,
|
|
browser = true,
|
|
debounce = 120,
|
|
ignore = {},
|
|
css_inject = true,
|
|
debug = false,
|
|
}
|
|
|
|
---@type live_server.Config
|
|
local config = vim.deepcopy(defaults)
|
|
|
|
---@param message string
|
|
---@param level string
|
|
local function log(message, level)
|
|
vim.notify(('live-server.nvim: %s'):format(message), vim.log.levels[level])
|
|
end
|
|
|
|
---@type table<string, boolean>
|
|
local UNSUPPORTED_FLAGS = {
|
|
['--host'] = true,
|
|
['--open'] = true,
|
|
['--browser'] = true,
|
|
['--quiet'] = true,
|
|
['--entry-file'] = true,
|
|
['--spa'] = true,
|
|
['--mount'] = true,
|
|
['--proxy'] = true,
|
|
['--htpasswd'] = true,
|
|
['--cors'] = true,
|
|
['--https'] = true,
|
|
['--https-module'] = true,
|
|
['--middleware'] = true,
|
|
['--ignorePattern'] = true,
|
|
}
|
|
|
|
---@param user_config table
|
|
---@return table
|
|
local function migrate_args(user_config)
|
|
if not user_config.args then
|
|
return user_config
|
|
end
|
|
|
|
vim.deprecate(
|
|
'`vim.g.live_server.args`',
|
|
'`:h live-server-config`',
|
|
'v0.2.0',
|
|
'live-server.nvim',
|
|
false
|
|
)
|
|
|
|
local migrated = {}
|
|
for k, v in pairs(user_config) do
|
|
if k ~= 'args' then
|
|
migrated[k] = v
|
|
end
|
|
end
|
|
|
|
for _, arg in ipairs(user_config.args) do
|
|
local port = arg:match('%-%-port=(%d+)')
|
|
if port then
|
|
migrated.port = tonumber(port)
|
|
elseif arg == '--no-browser' then
|
|
migrated.browser = false
|
|
elseif arg == '--no-css-inject' then
|
|
migrated.css_inject = false
|
|
else
|
|
local wait = arg:match('%-%-wait=(%d+)')
|
|
if wait then
|
|
migrated.debounce = tonumber(wait)
|
|
else
|
|
local ignore_val = arg:match('%-%-ignore=(.*)')
|
|
if ignore_val then
|
|
migrated.ignore = migrated.ignore or {}
|
|
for pattern in ignore_val:gmatch('[^,]+') do
|
|
migrated.ignore[#migrated.ignore + 1] = vim.trim(pattern)
|
|
end
|
|
else
|
|
local flag = arg:match('^(%-%-[%w-]+)')
|
|
if flag and UNSUPPORTED_FLAGS[flag] then
|
|
log(('flag `%s` is not supported and will be ignored'):format(arg), 'WARN')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return migrated
|
|
end
|
|
|
|
local function init()
|
|
if initialized then
|
|
return
|
|
end
|
|
|
|
local user_config = vim.g.live_server or {}
|
|
user_config = migrate_args(user_config)
|
|
config = vim.tbl_deep_extend('force', defaults, user_config)
|
|
|
|
vim.api.nvim_create_autocmd('VimLeavePre', {
|
|
callback = function()
|
|
for dir, inst in pairs(instances) do
|
|
server.stop(inst)
|
|
instances[dir] = nil
|
|
end
|
|
end,
|
|
})
|
|
|
|
initialized = true
|
|
end
|
|
|
|
---@param dir? string
|
|
---@return string?
|
|
local function find_cached_dir(dir)
|
|
if not dir then
|
|
return nil
|
|
end
|
|
|
|
local cur = dir
|
|
while not instances[cur] do
|
|
if cur == '/' or cur:match('^[A-Z]:\\$') then
|
|
return nil
|
|
end
|
|
local parent = vim.fn.fnamemodify(cur, ':h')
|
|
if parent == cur then
|
|
return nil
|
|
end
|
|
cur = parent
|
|
end
|
|
return cur
|
|
end
|
|
|
|
---@param dir string
|
|
---@return live_server.Instance?
|
|
local function is_running(dir)
|
|
local cached_dir = find_cached_dir(dir)
|
|
return cached_dir and instances[cached_dir]
|
|
end
|
|
|
|
---@param dir? string
|
|
---@return string
|
|
local function resolve_dir(dir)
|
|
if not dir or dir == '' then
|
|
local bufname = vim.api.nvim_buf_get_name(0)
|
|
local uri_path = bufname:match('^%a+://(/.*)')
|
|
dir = uri_path or '%:p:h'
|
|
end
|
|
return vim.fn.expand(vim.fn.fnamemodify(vim.fn.expand(dir), ':p'))
|
|
end
|
|
|
|
---@param dir? string
|
|
function M.start(dir)
|
|
init()
|
|
|
|
dir = resolve_dir(dir)
|
|
|
|
if is_running(dir) then
|
|
log('already running', 'INFO')
|
|
return
|
|
end
|
|
|
|
local root_real = vim.uv.fs_realpath(dir)
|
|
if not root_real then
|
|
log(('directory does not exist: %s'):format(dir), 'ERROR')
|
|
return
|
|
end
|
|
|
|
local inst = server.start({
|
|
port = config.port,
|
|
root_real = root_real,
|
|
debounce = config.debounce,
|
|
ignore = config.ignore,
|
|
css_inject = config.css_inject,
|
|
debug = config.debug,
|
|
})
|
|
|
|
instances[dir] = inst
|
|
log(('started on 127.0.0.1:%d'):format(config.port), 'INFO')
|
|
|
|
if config.browser then
|
|
vim.ui.open(('http://127.0.0.1:%d/'):format(config.port))
|
|
end
|
|
end
|
|
|
|
---@param dir? string
|
|
function M.stop(dir)
|
|
dir = resolve_dir(dir)
|
|
local cached_dir = find_cached_dir(dir)
|
|
if cached_dir and instances[cached_dir] then
|
|
server.stop(instances[cached_dir])
|
|
instances[cached_dir] = nil
|
|
log('stopped', 'INFO')
|
|
end
|
|
end
|
|
|
|
---@param dir? string
|
|
function M.toggle(dir)
|
|
dir = resolve_dir(dir)
|
|
if is_running(dir) then
|
|
M.stop(dir)
|
|
else
|
|
M.start(dir)
|
|
end
|
|
end
|
|
|
|
---@deprecated Use `vim.g.live_server` instead
|
|
---@param user_config? live_server.Config
|
|
function M.setup(user_config)
|
|
vim.deprecate(
|
|
'`require("live-server").setup()`',
|
|
'`vim.g.live_server`',
|
|
'v0.1.0',
|
|
'live-server.nvim',
|
|
false
|
|
)
|
|
|
|
if user_config then
|
|
vim.g.live_server = vim.tbl_deep_extend('force', vim.g.live_server or {}, user_config)
|
|
end
|
|
|
|
initialized = false
|
|
init()
|
|
end
|
|
|
|
return M
|