Some checks failed
		
		
	
	Detach Plugins / check (FlyGrep.vim) (push) Has been cancelled
				
			Detach Plugins / check (GitHub.vim) (push) Has been cancelled
				
			Detach Plugins / check (JavaUnit.vim) (push) Has been cancelled
				
			Detach Plugins / check (SourceCounter.vim) (push) Has been cancelled
				
			Detach Plugins / check (cpicker.nvim) (push) Has been cancelled
				
			Detach Plugins / check (dein-ui.vim) (push) Has been cancelled
				
			Detach Plugins / check (git.vim) (push) Has been cancelled
				
			Detach Plugins / check (iedit.vim) (push) Has been cancelled
				
			Detach Plugins / check (scrollbar.vim) (push) Has been cancelled
				
			Detach Plugins / check (vim-chat) (push) Has been cancelled
				
			Detach Plugins / check (vim-cheat) (push) Has been cancelled
				
			Detach Plugins / check (vim-todo) (push) Has been cancelled
				
			Detach Plugins / check (xmake.vim) (push) Has been cancelled
				
			test / Linux (nvim, nightly) (push) Has been cancelled
				
			test / Linux (nvim, v0.3.8) (push) Has been cancelled
				
			test / Linux (nvim, v0.4.0) (push) Has been cancelled
				
			test / Linux (nvim, v0.4.2) (push) Has been cancelled
				
			test / Linux (nvim, v0.4.3) (push) Has been cancelled
				
			test / Linux (nvim, v0.4.4) (push) Has been cancelled
				
			test / Linux (nvim, v0.5.0) (push) Has been cancelled
				
			test / Linux (nvim, v0.5.1) (push) Has been cancelled
				
			test / Linux (nvim, v0.6.0) (push) Has been cancelled
				
			test / Linux (nvim, v0.6.1) (push) Has been cancelled
				
			test / Linux (nvim, v0.7.0) (push) Has been cancelled
				
			test / Linux (nvim, v0.7.2) (push) Has been cancelled
				
			test / Linux (nvim, v0.8.0) (push) Has been cancelled
				
			test / Linux (nvim, v0.8.1) (push) Has been cancelled
				
			test / Linux (nvim, v0.8.2) (push) Has been cancelled
				
			test / Linux (nvim, v0.8.3) (push) Has been cancelled
				
			test / Linux (nvim, v0.9.0) (push) Has been cancelled
				
			test / Linux (nvim, v0.9.1) (push) Has been cancelled
				
			test / Linux (true, vim, v7.4.052) (push) Has been cancelled
				
			test / Linux (true, vim, v7.4.1689) (push) Has been cancelled
				
			test / Linux (true, vim, v7.4.629) (push) Has been cancelled
				
			test / Linux (true, vim, v8.0.0027) (push) Has been cancelled
				
			test / Linux (true, vim, v8.0.0183) (push) Has been cancelled
				
			test / Linux (vim, nightly) (push) Has been cancelled
				
			test / Linux (vim, v8.0.0184) (push) Has been cancelled
				
			test / Linux (vim, v8.0.1453) (push) Has been cancelled
				
			test / Linux (vim, v8.1.2269) (push) Has been cancelled
				
			test / Linux (vim, v8.2.2434) (push) Has been cancelled
				
			test / Linux (vim, v8.2.3995) (push) Has been cancelled
				
			test / Windows (nvim, nightly) (push) Has been cancelled
				
			test / Windows (nvim, v0.3.8) (push) Has been cancelled
				
			test / Windows (nvim, v0.4.2) (push) Has been cancelled
				
			test / Windows (nvim, v0.4.3) (push) Has been cancelled
				
			test / Windows (nvim, v0.4.4) (push) Has been cancelled
				
			test / Windows (nvim, v0.5.0) (push) Has been cancelled
				
			test / Windows (nvim, v0.5.1) (push) Has been cancelled
				
			test / Windows (nvim, v0.6.0) (push) Has been cancelled
				
			test / Windows (nvim, v0.6.1) (push) Has been cancelled
				
			test / Windows (nvim, v0.7.0) (push) Has been cancelled
				
			test / Windows (nvim, v0.7.2) (push) Has been cancelled
				
			test / Windows (nvim, v0.8.0) (push) Has been cancelled
				
			test / Windows (nvim, v0.8.1) (push) Has been cancelled
				
			test / Windows (nvim, v0.8.2) (push) Has been cancelled
				
			test / Windows (nvim, v0.8.3) (push) Has been cancelled
				
			test / Windows (nvim, v0.9.0) (push) Has been cancelled
				
			test / Windows (nvim, v0.9.1) (push) Has been cancelled
				
			test / Windows (vim, nightly) (push) Has been cancelled
				
			test / Windows (vim, v7.4.1185) (push) Has been cancelled
				
			test / Windows (vim, v7.4.1689) (push) Has been cancelled
				
			test / Windows (vim, v8.0.0027) (push) Has been cancelled
				
			test / Windows (vim, v8.0.1453) (push) Has been cancelled
				
			test / Windows (vim, v8.1.2269) (push) Has been cancelled
				
			test / Windows (vim, v8.2.2434) (push) Has been cancelled
				
			test / Windows (vim, v8.2.3995) (push) Has been cancelled
				
			docker / docker (push) Has been cancelled
				
			mirror / check (coding) (push) Has been cancelled
				
			mirror / check (gitee) (push) Has been cancelled
				
			mirror / check (gitlab) (push) Has been cancelled
				
			
		
			
				
	
	
		
			432 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			432 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
--This file should have all functions that are in the public api and either set
 | 
						|
--or read the state of this source.
 | 
						|
 | 
						|
local vim = vim
 | 
						|
local utils = require("neo-tree.utils")
 | 
						|
local fs_scan = require("neo-tree.sources.filesystem.lib.fs_scan")
 | 
						|
local renderer = require("neo-tree.ui.renderer")
 | 
						|
local events = require("neo-tree.events")
 | 
						|
local log = require("neo-tree.log")
 | 
						|
local manager = require("neo-tree.sources.manager")
 | 
						|
local git = require("neo-tree.git")
 | 
						|
local glob = require("neo-tree.sources.filesystem.lib.globtopattern")
 | 
						|
 | 
						|
local M = {
 | 
						|
  name = "filesystem",
 | 
						|
  display_name = "  Files ",
 | 
						|
}
 | 
						|
 | 
						|
local wrap = function(func)
 | 
						|
  return utils.wrap(func, M.name)
 | 
						|
end
 | 
						|
 | 
						|
local get_state = function(tabid)
 | 
						|
  return manager.get_state(M.name, tabid)
 | 
						|
end
 | 
						|
 | 
						|
-- TODO: DEPRECATED in 1.19, remove in 2.0
 | 
						|
-- Leaving this here for now because it was mentioned in the help file.
 | 
						|
M.reveal_current_file = function()
 | 
						|
  log.warn("DEPRECATED: use `neotree.sources.manager.reveal_current_file('filesystem')` instead")
 | 
						|
  return manager.reveal_current_file(M.name)
 | 
						|
end
 | 
						|
 | 
						|
local follow_internal = function(callback, force_show, async)
 | 
						|
  log.trace("follow called")
 | 
						|
  if vim.bo.filetype == "neo-tree" or vim.bo.filetype == "neo-tree-popup" then
 | 
						|
    return
 | 
						|
  end
 | 
						|
  local path_to_reveal = manager.get_path_to_reveal()
 | 
						|
  if not utils.truthy(path_to_reveal) then
 | 
						|
    return false
 | 
						|
  end
 | 
						|
 | 
						|
  local state = get_state()
 | 
						|
  if state.current_position == "float" then
 | 
						|
    return false
 | 
						|
  end
 | 
						|
  if not state.path then
 | 
						|
    return false
 | 
						|
  end
 | 
						|
  local window_exists = renderer.window_exists(state)
 | 
						|
  if window_exists then
 | 
						|
    local node = state.tree and state.tree:get_node()
 | 
						|
    if node then
 | 
						|
      if node:get_id() == path_to_reveal then
 | 
						|
        -- already focused
 | 
						|
        return false
 | 
						|
      end
 | 
						|
    end
 | 
						|
  else
 | 
						|
    if not force_show then
 | 
						|
      return false
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  local is_in_path = path_to_reveal:sub(1, #state.path) == state.path
 | 
						|
  if not is_in_path then
 | 
						|
    return false
 | 
						|
  end
 | 
						|
 | 
						|
  log.debug("follow file: ", path_to_reveal)
 | 
						|
  local show_only_explicitly_opened = function()
 | 
						|
    local eod = state.explicitly_opened_directories or {}
 | 
						|
    local expanded_nodes = renderer.get_expanded_nodes(state.tree)
 | 
						|
    local state_changed = false
 | 
						|
    for _, id in ipairs(expanded_nodes) do
 | 
						|
      local is_explicit = eod[id]
 | 
						|
      if not is_explicit then
 | 
						|
        local is_in_path = path_to_reveal:sub(1, #id) == id
 | 
						|
        if is_in_path then
 | 
						|
          is_explicit = true
 | 
						|
        end
 | 
						|
      end
 | 
						|
      if not is_explicit then
 | 
						|
        local node = state.tree:get_node(id)
 | 
						|
        if node then
 | 
						|
          node:collapse()
 | 
						|
          state_changed = true
 | 
						|
        end
 | 
						|
      end
 | 
						|
      if state_changed then
 | 
						|
        renderer.redraw(state)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  state.position.is.restorable = false -- we will handle setting cursor position here
 | 
						|
  fs_scan.get_items(state, nil, path_to_reveal, function()
 | 
						|
    show_only_explicitly_opened()
 | 
						|
    renderer.focus_node(state, path_to_reveal, true)
 | 
						|
    if type(callback) == "function" then
 | 
						|
      callback()
 | 
						|
    end
 | 
						|
  end, async)
 | 
						|
  return true
 | 
						|
end
 | 
						|
 | 
						|
M.follow = function(callback, force_show)
 | 
						|
  if vim.fn.bufname(0) == "COMMIT_EDITMSG" then
 | 
						|
    return false
 | 
						|
  end
 | 
						|
  if utils.is_floating() then
 | 
						|
    return false
 | 
						|
  end
 | 
						|
  utils.debounce("neo-tree-follow", function()
 | 
						|
    return follow_internal(callback, force_show)
 | 
						|
  end, 100, utils.debounce_strategy.CALL_LAST_ONLY)
 | 
						|
end
 | 
						|
 | 
						|
M._navigate_internal = function(state, path, path_to_reveal, callback, async)
 | 
						|
  log.trace("navigate_internal", state.current_position, path, path_to_reveal)
 | 
						|
  state.dirty = false
 | 
						|
  local is_search = utils.truthy(state.search_pattern)
 | 
						|
  local path_changed = false
 | 
						|
  if not path and not state.bind_to_cwd then
 | 
						|
    path = state.path
 | 
						|
  end
 | 
						|
  if path == nil then
 | 
						|
    log.debug("navigate_internal: path is nil, using cwd")
 | 
						|
    path = manager.get_cwd(state)
 | 
						|
  end
 | 
						|
  if path ~= state.path then
 | 
						|
    log.debug("navigate_internal: path changed from ", state.path, " to ", path)
 | 
						|
    state.path = path
 | 
						|
    path_changed = true
 | 
						|
  end
 | 
						|
 | 
						|
  if path_to_reveal then
 | 
						|
    renderer.position.set(state, path_to_reveal)
 | 
						|
    log.debug(
 | 
						|
      "navigate_internal: in path_to_reveal, state.position is ",
 | 
						|
      state.position.node_id,
 | 
						|
      ", restorable = ",
 | 
						|
      state.position.is.restorable
 | 
						|
    )
 | 
						|
    fs_scan.get_items(state, nil, path_to_reveal, callback)
 | 
						|
  else
 | 
						|
    local is_current = state.current_position == "current"
 | 
						|
    local follow_file = state.follow_current_file
 | 
						|
      and not is_search
 | 
						|
      and not is_current
 | 
						|
      and manager.get_path_to_reveal()
 | 
						|
    local handled = false
 | 
						|
    if utils.truthy(follow_file) then
 | 
						|
      handled = follow_internal(callback, true, async)
 | 
						|
    end
 | 
						|
    if not handled then
 | 
						|
      local success, msg = pcall(renderer.position.save, state)
 | 
						|
      if success then
 | 
						|
        log.trace("navigate_internal: position saved")
 | 
						|
      else
 | 
						|
        log.trace("navigate_internal: FAILED to save position: ", msg)
 | 
						|
      end
 | 
						|
      fs_scan.get_items(state, nil, nil, callback, async)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  if path_changed and state.bind_to_cwd then
 | 
						|
    manager.set_cwd(state)
 | 
						|
  end
 | 
						|
  local config = require("neo-tree").config
 | 
						|
  if config.enable_git_status and not is_search and config.git_status_async then
 | 
						|
    git.status_async(state.path, state.git_base, config.git_status_async_options)
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
---Navigate to the given path.
 | 
						|
---@param path string? Path to navigate to. If empty, will navigate to the cwd.
 | 
						|
---@param path_to_reveal string? Node to focus after the items are loaded.
 | 
						|
---@param callback function? Callback to call after the items are loaded.
 | 
						|
M.navigate = function(state, path, path_to_reveal, callback, async)
 | 
						|
  log.trace("navigate", path, path_to_reveal, async)
 | 
						|
  utils.debounce("filesystem_navigate", function()
 | 
						|
    M._navigate_internal(state, path, path_to_reveal, callback, async)
 | 
						|
  end, utils.debounce_strategy.CALL_FIRST_AND_LAST, 100)
 | 
						|
end
 | 
						|
 | 
						|
M.reset_search = function(state, refresh, open_current_node)
 | 
						|
  log.trace("reset_search")
 | 
						|
  -- Cancel any pending search
 | 
						|
  require("neo-tree.sources.filesystem.lib.filter_external").cancel()
 | 
						|
  -- reset search state
 | 
						|
  state.fuzzy_finder_mode = nil
 | 
						|
  state.use_fzy = nil
 | 
						|
  state.fzy_sort_result_scores = nil
 | 
						|
  state.fzy_sort_file_list_cache = nil
 | 
						|
  state.sort_function_override = nil
 | 
						|
 | 
						|
  if refresh == nil then
 | 
						|
    refresh = true
 | 
						|
  end
 | 
						|
  if state.open_folders_before_search then
 | 
						|
    state.force_open_folders = vim.deepcopy(state.open_folders_before_search, { noref = 1 })
 | 
						|
  else
 | 
						|
    state.force_open_folders = nil
 | 
						|
  end
 | 
						|
  state.search_pattern = nil
 | 
						|
  state.open_folders_before_search = nil
 | 
						|
  if open_current_node then
 | 
						|
    local success, node = pcall(state.tree.get_node, state.tree)
 | 
						|
    if success and node then
 | 
						|
      local path = node:get_id()
 | 
						|
      renderer.position.set(state, path)
 | 
						|
      if node.type == "directory" then
 | 
						|
        path = utils.remove_trailing_slash(path)
 | 
						|
        log.trace("opening directory from search: ", path)
 | 
						|
        M.navigate(state, nil, path, function()
 | 
						|
          pcall(renderer.focus_node, state, path, false)
 | 
						|
        end)
 | 
						|
      else
 | 
						|
        utils.open_file(state, path)
 | 
						|
        if
 | 
						|
          refresh
 | 
						|
          and state.current_position ~= "current"
 | 
						|
          and state.current_position ~= "float"
 | 
						|
        then
 | 
						|
          M.navigate(state, nil, path)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  elseif refresh then
 | 
						|
    M.navigate(state)
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
M.show_new_children = function(state, node_or_path)
 | 
						|
  local node = node_or_path
 | 
						|
  if node_or_path == nil then
 | 
						|
    node = state.tree:get_node()
 | 
						|
    node_or_path = node:get_id()
 | 
						|
  elseif type(node_or_path) == "string" then
 | 
						|
    node = state.tree:get_node(node_or_path)
 | 
						|
    if node == nil then
 | 
						|
      local parent_path, _ = utils.split_path(node_or_path)
 | 
						|
      node = state.tree:get_node(parent_path)
 | 
						|
      if node == nil then
 | 
						|
        M.navigate(state, nil, node_or_path)
 | 
						|
        return
 | 
						|
      end
 | 
						|
    end
 | 
						|
  else
 | 
						|
    node = node_or_path
 | 
						|
    node_or_path = node:get_id()
 | 
						|
  end
 | 
						|
 | 
						|
  if node.type ~= "directory" then
 | 
						|
    return
 | 
						|
  end
 | 
						|
 | 
						|
  M.navigate(state, nil, node_or_path)
 | 
						|
end
 | 
						|
 | 
						|
---Configures the plugin, should be called before the plugin is used.
 | 
						|
---@param config table Configuration table containing any keys that the user
 | 
						|
--wants to change from the defaults. May be empty to accept default values.
 | 
						|
M.setup = function(config, global_config)
 | 
						|
  config.filtered_items = config.filtered_items or {}
 | 
						|
  config.enable_git_status = global_config.enable_git_status
 | 
						|
 | 
						|
  for _, key in ipairs({ "hide_by_pattern", "never_show_by_pattern" }) do
 | 
						|
    local list = config.filtered_items[key]
 | 
						|
    if type(list) == "table" then
 | 
						|
      for i, pattern in ipairs(list) do
 | 
						|
        list[i] = glob.globtopattern(pattern)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  for _, key in ipairs({ "hide_by_name", "always_show", "never_show" }) do
 | 
						|
    local list = config.filtered_items[key]
 | 
						|
    if type(list) == "table" then
 | 
						|
      config.filtered_items[key] = utils.list_to_dict(list)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  --Configure events for before_render
 | 
						|
  if config.before_render then
 | 
						|
    --convert to new event system
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.BEFORE_RENDER,
 | 
						|
      handler = function(state)
 | 
						|
        local this_state = get_state()
 | 
						|
        if state == this_state then
 | 
						|
          config.before_render(this_state)
 | 
						|
        end
 | 
						|
      end,
 | 
						|
    })
 | 
						|
  elseif global_config.enable_git_status and global_config.git_status_async then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.GIT_STATUS_CHANGED,
 | 
						|
      handler = wrap(manager.git_status_changed),
 | 
						|
    })
 | 
						|
  elseif global_config.enable_git_status then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.BEFORE_RENDER,
 | 
						|
      handler = function(state)
 | 
						|
        local this_state = get_state()
 | 
						|
        if state == this_state then
 | 
						|
          state.git_status_lookup = git.status(state.git_base)
 | 
						|
        end
 | 
						|
      end,
 | 
						|
    })
 | 
						|
  end
 | 
						|
 | 
						|
  -- Respond to git events from git_status source or Fugitive
 | 
						|
  if global_config.enable_git_status then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.GIT_EVENT,
 | 
						|
      handler = function()
 | 
						|
        manager.refresh(M.name)
 | 
						|
      end,
 | 
						|
    })
 | 
						|
  end
 | 
						|
 | 
						|
  --Configure event handlers for file changes
 | 
						|
  if config.use_libuv_file_watcher then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.FS_EVENT,
 | 
						|
      handler = wrap(manager.refresh),
 | 
						|
    })
 | 
						|
  else
 | 
						|
    require("neo-tree.sources.filesystem.lib.fs_watch").unwatch_all()
 | 
						|
    if global_config.enable_refresh_on_write then
 | 
						|
      manager.subscribe(M.name, {
 | 
						|
        event = events.VIM_BUFFER_CHANGED,
 | 
						|
        handler = function(arg)
 | 
						|
          local afile = arg.afile or ""
 | 
						|
          if utils.is_real_file(afile) then
 | 
						|
            log.trace("refreshing due to vim_buffer_changed event: ", afile)
 | 
						|
            manager.refresh("filesystem")
 | 
						|
          else
 | 
						|
            log.trace("Ignoring vim_buffer_changed event for non-file: ", afile)
 | 
						|
          end
 | 
						|
        end,
 | 
						|
      })
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  --Configure event handlers for cwd changes
 | 
						|
  if config.bind_to_cwd then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.VIM_DIR_CHANGED,
 | 
						|
      handler = wrap(manager.dir_changed),
 | 
						|
    })
 | 
						|
  end
 | 
						|
 | 
						|
  --Configure event handlers for lsp diagnostic updates
 | 
						|
  if global_config.enable_diagnostics then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.VIM_DIAGNOSTIC_CHANGED,
 | 
						|
      handler = wrap(manager.diagnostics_changed),
 | 
						|
    })
 | 
						|
  end
 | 
						|
 | 
						|
  --Configure event handlers for modified files
 | 
						|
  if global_config.enable_modified_markers then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.VIM_BUFFER_MODIFIED_SET,
 | 
						|
      handler = wrap(manager.opened_buffers_changed),
 | 
						|
    })
 | 
						|
  end
 | 
						|
 | 
						|
  if global_config.enable_opened_markers then
 | 
						|
    for _, event in ipairs({ events.VIM_BUFFER_ADDED, events.VIM_BUFFER_DELETED }) do
 | 
						|
      manager.subscribe(M.name, {
 | 
						|
        event = event,
 | 
						|
        handler = wrap(manager.opened_buffers_changed),
 | 
						|
      })
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  -- Configure event handler for follow_current_file option
 | 
						|
  if config.follow_current_file then
 | 
						|
    manager.subscribe(M.name, {
 | 
						|
      event = events.VIM_BUFFER_ENTER,
 | 
						|
      handler = function(args)
 | 
						|
        if utils.is_real_file(args.afile) then
 | 
						|
          M.follow()
 | 
						|
        end
 | 
						|
      end,
 | 
						|
    })
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
---Expands or collapses the current node.
 | 
						|
M.toggle_directory = function(state, node, path_to_reveal, skip_redraw, recursive)
 | 
						|
  local tree = state.tree
 | 
						|
  if not node then
 | 
						|
    node = tree:get_node()
 | 
						|
  end
 | 
						|
  if node.type ~= "directory" then
 | 
						|
    return
 | 
						|
  end
 | 
						|
  state.explicitly_opened_directories = state.explicitly_opened_directories or {}
 | 
						|
  if node.loaded == false then
 | 
						|
    local id = node:get_id()
 | 
						|
    state.explicitly_opened_directories[id] = true
 | 
						|
    renderer.position.set(state, nil)
 | 
						|
    fs_scan.get_items(state, id, path_to_reveal, nil, false, recursive)
 | 
						|
  elseif node:has_children() then
 | 
						|
    local updated = false
 | 
						|
    if node:is_expanded() then
 | 
						|
      updated = node:collapse()
 | 
						|
      state.explicitly_opened_directories[node:get_id()] = false
 | 
						|
    else
 | 
						|
      updated = node:expand()
 | 
						|
      state.explicitly_opened_directories[node:get_id()] = true
 | 
						|
    end
 | 
						|
    if updated and not skip_redraw then
 | 
						|
      renderer.redraw(state)
 | 
						|
    end
 | 
						|
    if path_to_reveal then
 | 
						|
      renderer.focus_node(state, path_to_reveal)
 | 
						|
    end
 | 
						|
  elseif require("neo-tree").config.filesystem.scan_mode == "deep" then
 | 
						|
    node.empty_expanded = not node.empty_expanded
 | 
						|
    renderer.redraw(state)
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
return M
 |