mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	Problem: luals returns stricter diagnostics with bundled luarc.json Solution: Improve some function and type annotations: * use recognized uv.* types * disable diagnostic for global `vim` in shared.lua * docs: don't start comment lines with taglink (otherwise LuaLS will interpret it as a type) * add type alias for lpeg pattern * fix return annotation for `vim.secure.trust` * rename local Range object in vim.version (shadows `Range` in vim.treesitter) * fix some "missing fields" warnings * add missing required fields for test functions in eval.lua * rename lsp meta files for consistency
		
			
				
	
	
		
			219 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
local M = {}
 | 
						|
 | 
						|
--- Enumeration describing the types of events watchers will emit.
 | 
						|
M.FileChangeType = vim.tbl_add_reverse_lookup({
 | 
						|
  Created = 1,
 | 
						|
  Changed = 2,
 | 
						|
  Deleted = 3,
 | 
						|
})
 | 
						|
 | 
						|
--- Joins filepath elements by static '/' separator
 | 
						|
---
 | 
						|
---@param ... (string) The path elements.
 | 
						|
---@return string
 | 
						|
local function filepath_join(...)
 | 
						|
  return table.concat({ ... }, '/')
 | 
						|
end
 | 
						|
 | 
						|
--- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle
 | 
						|
---
 | 
						|
---@param handle (uv.uv_fs_event_t|uv.uv_fs_poll_t) The handle to stop
 | 
						|
local function stop(handle)
 | 
						|
  local _, stop_err = handle:stop()
 | 
						|
  assert(not stop_err, stop_err)
 | 
						|
  local is_closing, close_err = handle:is_closing()
 | 
						|
  assert(not close_err, close_err)
 | 
						|
  if not is_closing then
 | 
						|
    handle:close()
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
--- Initializes and starts a |uv_fs_event_t|
 | 
						|
---
 | 
						|
---@param path (string) The path to watch
 | 
						|
---@param opts (table|nil) Additional options
 | 
						|
---     - uvflags (table|nil)
 | 
						|
---                Same flags as accepted by |uv.fs_event_start()|
 | 
						|
---@param callback (function) The function called when new events
 | 
						|
---@return (function) Stops the watcher
 | 
						|
function M.watch(path, opts, callback)
 | 
						|
  vim.validate({
 | 
						|
    path = { path, 'string', false },
 | 
						|
    opts = { opts, 'table', true },
 | 
						|
    callback = { callback, 'function', false },
 | 
						|
  })
 | 
						|
 | 
						|
  path = vim.fs.normalize(path)
 | 
						|
  local uvflags = opts and opts.uvflags or {}
 | 
						|
  local handle, new_err = vim.uv.new_fs_event()
 | 
						|
  assert(not new_err, new_err)
 | 
						|
  local _, start_err = handle:start(path, uvflags, function(err, filename, events)
 | 
						|
    assert(not err, err)
 | 
						|
    local fullpath = path
 | 
						|
    if filename then
 | 
						|
      filename = filename:gsub('\\', '/')
 | 
						|
      fullpath = filepath_join(fullpath, filename)
 | 
						|
    end
 | 
						|
    local change_type = events.change and M.FileChangeType.Changed or 0
 | 
						|
    if events.rename then
 | 
						|
      local _, staterr, staterrname = vim.uv.fs_stat(fullpath)
 | 
						|
      if staterrname == 'ENOENT' then
 | 
						|
        change_type = M.FileChangeType.Deleted
 | 
						|
      else
 | 
						|
        assert(not staterr, staterr)
 | 
						|
        change_type = M.FileChangeType.Created
 | 
						|
      end
 | 
						|
    end
 | 
						|
    callback(fullpath, change_type)
 | 
						|
  end)
 | 
						|
  assert(not start_err, start_err)
 | 
						|
  return function()
 | 
						|
    stop(handle)
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
local default_poll_interval_ms = 2000
 | 
						|
 | 
						|
--- @class watch.Watches
 | 
						|
--- @field is_dir boolean
 | 
						|
--- @field children? table<string,watch.Watches>
 | 
						|
--- @field cancel? fun()
 | 
						|
--- @field started? boolean
 | 
						|
--- @field handle? uv.uv_fs_poll_t
 | 
						|
 | 
						|
--- @class watch.PollOpts
 | 
						|
--- @field interval? integer
 | 
						|
--- @field include_pattern? userdata
 | 
						|
--- @field exclude_pattern? userdata
 | 
						|
 | 
						|
--- Implementation for poll, hiding internally-used parameters.
 | 
						|
---
 | 
						|
---@param path string
 | 
						|
---@param opts watch.PollOpts
 | 
						|
---@param callback fun(patch: string, filechangetype: integer)
 | 
						|
---@param watches (watch.Watches|nil) A tree structure to maintain state for recursive watches.
 | 
						|
---     - handle (uv_fs_poll_t)
 | 
						|
---               The libuv handle
 | 
						|
---     - cancel (function)
 | 
						|
---               A function that cancels the handle and all children's handles
 | 
						|
---     - is_dir (boolean)
 | 
						|
---               Indicates whether the path is a directory (and the poll should
 | 
						|
---               be invoked recursively)
 | 
						|
---     - children (table|nil)
 | 
						|
---               A mapping of directory entry name to its recursive watches
 | 
						|
---     - started (boolean|nil)
 | 
						|
---               Whether or not the watcher has first been initialized. Used
 | 
						|
---               to prevent a flood of Created events on startup.
 | 
						|
---@return fun() Cancel function
 | 
						|
local function poll_internal(path, opts, callback, watches)
 | 
						|
  path = vim.fs.normalize(path)
 | 
						|
  local interval = opts and opts.interval or default_poll_interval_ms
 | 
						|
  watches = watches or {
 | 
						|
    is_dir = true,
 | 
						|
  }
 | 
						|
  watches.cancel = function()
 | 
						|
    if watches.children then
 | 
						|
      for _, w in pairs(watches.children) do
 | 
						|
        w.cancel()
 | 
						|
      end
 | 
						|
    end
 | 
						|
    if watches.handle then
 | 
						|
      stop(watches.handle)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  local function incl_match()
 | 
						|
    return not opts.include_pattern or opts.include_pattern:match(path) ~= nil
 | 
						|
  end
 | 
						|
  local function excl_match()
 | 
						|
    return opts.exclude_pattern and opts.exclude_pattern:match(path) ~= nil
 | 
						|
  end
 | 
						|
  if not watches.is_dir and not incl_match() or excl_match() then
 | 
						|
    return watches.cancel
 | 
						|
  end
 | 
						|
 | 
						|
  if not watches.handle then
 | 
						|
    local poll, new_err = vim.uv.new_fs_poll()
 | 
						|
    assert(not new_err, new_err)
 | 
						|
    watches.handle = poll
 | 
						|
    local _, start_err = poll:start(
 | 
						|
      path,
 | 
						|
      interval,
 | 
						|
      vim.schedule_wrap(function(err)
 | 
						|
        if err == 'ENOENT' then
 | 
						|
          return
 | 
						|
        end
 | 
						|
        assert(not err, err)
 | 
						|
        poll_internal(path, opts, callback, watches)
 | 
						|
        callback(path, M.FileChangeType.Changed)
 | 
						|
      end)
 | 
						|
    )
 | 
						|
    assert(not start_err, start_err)
 | 
						|
    if watches.started then
 | 
						|
      callback(path, M.FileChangeType.Created)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  if watches.is_dir then
 | 
						|
    watches.children = watches.children or {}
 | 
						|
    local exists = {} --- @type table<string,true>
 | 
						|
    for name, ftype in vim.fs.dir(path) do
 | 
						|
      exists[name] = true
 | 
						|
      if not watches.children[name] then
 | 
						|
        watches.children[name] = {
 | 
						|
          is_dir = ftype == 'directory',
 | 
						|
          started = watches.started,
 | 
						|
        }
 | 
						|
        poll_internal(filepath_join(path, name), opts, callback, watches.children[name])
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    local newchildren = {} ---@type table<string,watch.Watches>
 | 
						|
    for name, watch in pairs(watches.children) do
 | 
						|
      if exists[name] then
 | 
						|
        newchildren[name] = watch
 | 
						|
      else
 | 
						|
        watch.cancel()
 | 
						|
        watches.children[name] = nil
 | 
						|
        if watch.handle then
 | 
						|
          callback(path .. '/' .. name, M.FileChangeType.Deleted)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
    watches.children = newchildren
 | 
						|
  end
 | 
						|
 | 
						|
  watches.started = true
 | 
						|
 | 
						|
  return watches.cancel
 | 
						|
end
 | 
						|
 | 
						|
--- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the
 | 
						|
--- directory at path.
 | 
						|
---
 | 
						|
---@param path (string) The path to watch. Must refer to a directory.
 | 
						|
---@param opts (table|nil) Additional options
 | 
						|
---     - interval (number|nil)
 | 
						|
---                Polling interval in ms as passed to |uv.fs_poll_start()|. Defaults to 2000.
 | 
						|
---     - include_pattern (LPeg pattern|nil)
 | 
						|
---                An |lpeg| pattern. Only changes to files whose full paths match the pattern
 | 
						|
---                will be reported. Only matches against non-directoriess, all directories will
 | 
						|
---                be watched for new potentially-matching files. exclude_pattern can be used to
 | 
						|
---                filter out directories. When nil, matches any file name.
 | 
						|
---     - exclude_pattern (LPeg pattern|nil)
 | 
						|
---                An |lpeg| pattern. Only changes to files and directories whose full path does
 | 
						|
---                not match the pattern will be reported. Matches against both files and
 | 
						|
---                directories. When nil, matches nothing.
 | 
						|
---@param callback (function) The function called when new events
 | 
						|
---@return function Stops the watcher
 | 
						|
function M.poll(path, opts, callback)
 | 
						|
  vim.validate({
 | 
						|
    path = { path, 'string', false },
 | 
						|
    opts = { opts, 'table', true },
 | 
						|
    callback = { callback, 'function', false },
 | 
						|
  })
 | 
						|
  return poll_internal(path, opts, callback, nil)
 | 
						|
end
 | 
						|
 | 
						|
return M
 |