Skip to content

Commit

Permalink
refactor(session-lens): list files in lua
Browse files Browse the repository at this point in the history
Refactor session-lens so it doesn't depend on any external tool to
list sessions. Also clean up how we set up the picker and support all of
the Telescope path_display options. This unifies the session listing
code between AutoSession search/delete and Telescope
  • Loading branch information
cameronr committed Sep 23, 2024
1 parent 30e14d9 commit 98ffedc
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 153 deletions.
38 changes: 3 additions & 35 deletions lua/auto-session/autocmds.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,39 +37,6 @@ end
---@field display_name string
---@field path string

---@return PickerItem[]
local function get_session_files()
local files = {}
local sessions_dir = M.AutoSession.get_root_dir()

if vim.fn.isdirectory(sessions_dir) == Lib._VIM_FALSE then
return files
end

local entries = vim.fn.readdir(sessions_dir, function(item)
return Lib.is_session_file(sessions_dir .. item)
end)

return vim.tbl_map(function(file_name)
-- sessions_dir is guaranteed to have a trailing separator so don't need to add another one here
local session_name
local display_name
if Lib.is_legacy_file_name(file_name) then
session_name = (Lib.legacy_unescape_session_name(file_name):gsub("%.vim$", ""))
display_name = session_name .. " (legacy)"
else
session_name = Lib.escaped_session_name_to_session_name(file_name)
display_name = Lib.get_session_display_name(file_name)
end

return {
session_name = session_name,
display_name = display_name,
path = sessions_dir .. file_name,
}
end, entries)
end

---@param files string[]
---@param prompt string
---@param callback fun(choice: PickerItem)
Expand All @@ -89,7 +56,7 @@ end

---@param data table
local function handle_autosession_command(data)
local files = get_session_files()
local files = Lib.get_session_list(M.AutoSession.get_root_dir())
if data.args:match "search" then
open_picker(files, "Select a session:", function(choice)
M.AutoSession.autosave_and_restore(choice.session_name)
Expand All @@ -105,7 +72,8 @@ end
local function purge_orphaned_sessions()
local orphaned_sessions = {}

for _, session in ipairs(get_session_files()) do
local session_files = Lib.get_session_list(M.AutoSession.get_root_dir())
for _, session in ipairs(session_files) do
if
not Lib.is_named_session(session.session_name) and vim.fn.isdirectory(session.session_name) == Lib._VIM_FALSE
then
Expand Down
8 changes: 8 additions & 0 deletions lua/auto-session/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ local function check_old_config_names(config)
and type(config["cwd_change_handling"]) == "table"
and config.cwd_change_handling["restore_upcoming_session"]
then
M.has_old_config = true
local old_cwd_change_handling = config.cwd_change_handling or {} -- shouldn't be nil but placate LS
config["cwd_change_handling"] = old_cwd_change_handling.restore_upcoming_session
if old_cwd_change_handling["pre_cwd_changed_hook"] then
Expand All @@ -194,6 +195,13 @@ local function check_old_config_names(config)
config.post_cwd_changed_cmds = { old_cwd_change_handling.post_cwd_changed_hook }
end
end

if config.session_lens and config.session_lens.shorten_path ~= nil then
M.has_old_config = true
if config.session_lens.shorten_path then
config.session_lens.path_display = { "shorten" }
end
end
end

---@param config? AutoSession.Config
Expand Down
77 changes: 77 additions & 0 deletions lua/auto-session/lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -604,4 +604,81 @@ function Lib.flatten_table_and_split_strings(input)
return output
end

---Returns the list of files in a directory, sorted by modification time
---@param dir string the directory to list
---@return table The filenames, sorted by modification time
function Lib.sorted_readdir(dir)
-- Get list of files
local files = vim.fn.readdir(dir)

-- Create a table with file names and modification times
local file_times = {}
for _, file in ipairs(files) do
local full_path = dir .. "/" .. file
local mod_time = vim.fn.getftime(full_path)
table.insert(file_times, { name = file, time = mod_time })
end

-- Sort the table based on modification times (most recent first)
table.sort(file_times, function(a, b)
return a.time > b.time
end)

-- Extract just the file names from the sorted table
local sorted_files = {}
for _, file in ipairs(file_times) do
table.insert(sorted_files, file.name)
end

return sorted_files
end

---Get the list of session files. Will filter out any extra command session files
---@param sessions_dir string The directory where the sessions are stored
---@return table the list of session files
function Lib.get_session_list(sessions_dir)
if vim.fn.isdirectory(sessions_dir) == Lib._VIM_FALSE then
return {}
end

local entries = Lib.sorted_readdir(sessions_dir)

return vim.tbl_map(function(file_name)
local session_name
local display_name_component

if not Lib.is_session_file(sessions_dir .. file_name) then
return nil
end

-- an annotation about the session, added to display_name after any path processing
local annotation = ""
if Lib.is_legacy_file_name(file_name) then
session_name = (Lib.legacy_unescape_session_name(file_name):gsub("%.vim$", ""))
display_name_component = session_name
annotation = " (legacy)"
else
session_name = Lib.escaped_session_name_to_session_name(file_name)
display_name_component = session_name
local name_components = Lib.get_session_display_name_as_table(file_name)
if #name_components > 1 then
display_name_component = name_components[1]
annotation = " " .. name_components[2]
end
end

local display_name = display_name_component .. annotation

return {
session_name = session_name,
-- include the components in case telescope wants to shorten the path
display_name_component = display_name_component,
annotation_component = annotation,
display_name = display_name,
file_name = file_name,
path = sessions_dir .. file_name,
}
end, entries)
end

return Lib
4 changes: 2 additions & 2 deletions lua/auto-session/session-lens/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ M.delete_session = function(prompt_bufnr)
local current_picker = action_state.get_current_picker(prompt_bufnr)
current_picker:delete_selection(function(selection)
if selection then
AutoSession.DeleteSessionFile(selection.path, selection.display)
AutoSession.DeleteSessionFile(selection.path, selection.display())
end
end)
end
Expand Down Expand Up @@ -100,7 +100,7 @@ M.copy_session = function(_)
local action_state = require "telescope.actions.state"
local selection = action_state.get_selected_entry()

local new_name = vim.fn.input("New session name: ", selection.display)
local new_name = vim.fn.input("New session name: ", selection.display())
local content = vim.fn.readfile(selection.path)
vim.fn.writefile(content, AutoSession.get_root_dir() .. Lib.escape_session_name(new_name) .. ".vim")
end
Expand Down
153 changes: 62 additions & 91 deletions lua/auto-session/session-lens/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,111 +6,78 @@ local AutoSession = require "auto-session"
----------- Setup ----------
local SessionLens = {}

---@private
---Function generator that returns the function for generating telescope file entries. Only exported
---for testing.
---@param opts table Options for how paths sould be displayed. Only supports opts.shorten
---@return function The function to be set as entry_maker in Telescope picker options
function SessionLens.make_telescope_callback(opts)
local session_root_dir = AutoSession.get_root_dir()

-- just used for shortening the display_name (if enabled)
local path = require "plenary.path"
return function(file_name)
-- Don't include <session>x.vim files that nvim makes for custom user
-- commands
if not Lib.is_session_file(session_root_dir .. file_name) then
return nil
end

-- the name of the session, to be used for restoring/deleting
local session_name

-- the name to display, possibly with a shortened path
local display_name

-- an annotation about the session, added to display_name after any path processing
local annotation = ""
if Lib.is_legacy_file_name(file_name) then
session_name = (Lib.legacy_unescape_session_name(file_name):gsub("%.vim$", ""))
display_name = session_name
annotation = " (legacy)"
else
session_name = Lib.escaped_session_name_to_session_name(file_name)
display_name = session_name
local name_components = Lib.get_session_display_name_as_table(file_name)
if #name_components > 1 then
display_name = name_components[1]
annotation = " " .. name_components[2]
end
end

if opts.path_display and vim.tbl_contains(opts.path_display, "shorten") then
display_name = path:new(display_name):shorten()
if not display_name then
display_name = session_name
end
end
display_name = display_name .. annotation

return {
ordinal = session_name,
value = session_name,
filename = file_name,
cwd = session_root_dir,
display = display_name,
path = session_root_dir .. file_name,
}
end
end

---@private
---Search session
---Triggers the customized telescope picker for switching sessions
---@param custom_opts any
---@param custom_opts table
SessionLens.search_session = function(custom_opts)
local telescope_themes = require "telescope.themes"
local telescope_actions = require "telescope.actions"
local telescope_finders = require "telescope.finders"
local telescope_conf = require("telescope.config").values

custom_opts = (vim.tbl_isempty(custom_opts or {}) or custom_opts == nil) and Config.session_lens or custom_opts
-- use custom_opts if specified and non-empty. Otherwise use the config
if not custom_opts or vim.tbl_isempty(custom_opts) then
custom_opts = Config.session_lens
end
custom_opts = custom_opts or {}

-- Use auto_session_root_dir from the Auto Session plugin
local session_root_dir = AutoSession.get_root_dir()
-- get the theme defaults, with any overrides in custom_opts.theme_conf
local theme_opts = telescope_themes.get_dropdown(custom_opts.theme_conf)

if custom_opts.shorten_path ~= nil then
Lib.logger.warn "`shorten_path` config is deprecated, use the new `path_display` config instead"
if custom_opts.shorten_path then
custom_opts.path_display = { "shorten" }
else
custom_opts.path_display = nil
end
-- path_display could've been in theme_conf but that's not where we put it
if custom_opts.path_display then
-- copy over to the theme options
theme_opts.path_display = custom_opts.path_display
end

custom_opts.shorten_path = nil
if theme_opts.path_display then
-- If there's a path_display setting, we have to force path_display.absolute = true here,
-- otherwise the session for the cwd will be displayed as just a dot
theme_opts.path_display.absolute = true
end

local theme_opts = telescope_themes.get_dropdown(custom_opts.theme_conf)
theme_opts.previewer = custom_opts.previewer

-- Use default previewer config by setting the value to nil if some sets previewer to true in the custom config.
-- Passing in the boolean value errors out in the telescope code with the picker trying to index a boolean instead of a table.
-- This fixes it but also allows for someone to pass in a table with the actual preview configs if they want to.
if custom_opts.previewer ~= false and custom_opts.previewer == true then
custom_opts["previewer"] = nil
end
local session_root_dir = AutoSession.get_root_dir()

local finder_opts = {
entry_maker = SessionLens.make_telescope_callback(custom_opts),
cwd = session_root_dir,
}
local session_entry_maker = function(session_entry)
return {

ordinal = session_entry.session_name,
value = session_entry.session_name,
filename = session_entry.file_name,
path = session_entry.path,
cwd = session_root_dir,

-- We can't calculate the vaue of display until the picker is acutally displayed
-- because telescope.utils.transform_path may depend on the window size,
-- specifically with the truncate option. So we use a function that will be
-- called when actually displaying the row
display = function(_)
if session_entry.already_set_display_name then
return session_entry.display_name
end

session_entry.already_set_display_name = true

local find_command
if 1 == vim.fn.executable "rg" then
find_command = { "rg", "--files", "--color", "never", "--sortr", "modified" }
elseif 1 == vim.fn.executable "ls" then
find_command = { "ls", "-t" }
elseif 1 == vim.fn.executable "cmd" and vim.fn.has "win32" == 1 then
find_command = { "cmd", "/C", "dir", "/b", "/o-d" }
if not theme_opts or not theme_opts.path_display then
return session_entry.display_name
end

local telescope_utils = require "telescope.utils"

return telescope_utils.transform_path(theme_opts, session_entry.display_name_component)
.. session_entry.annotation_component
end,
}
end

local finder_maker = function()
return telescope_finders.new_table {
results = Lib.get_session_list(session_root_dir),
entry_maker = session_entry_maker,
}
end

local opts = {
Expand All @@ -127,7 +94,7 @@ SessionLens.search_session = function(custom_opts)
post = function()
local action_state = require "telescope.actions.state"
local picker = action_state.get_current_picker(prompt_bufnr)
picker:refresh(telescope_finders.new_oneshot_job(find_command, finder_opts), { reset_prompt = true })
picker:refresh(finder_maker(), { reset_prompt = true })
end,
}

Expand All @@ -136,11 +103,15 @@ SessionLens.search_session = function(custom_opts)
return true
end,
}
opts = vim.tbl_deep_extend("force", opts, theme_opts, custom_opts or {})

-- add the theme options
opts = vim.tbl_deep_extend("force", opts, theme_opts)

Lib.logger.debug(opts)

require("telescope.pickers")
.new(opts, {
finder = telescope_finders.new_oneshot_job(find_command, finder_opts),
finder = finder_maker(),
previewer = telescope_conf.file_previewer(opts),
sorter = telescope_conf.file_sorter(opts),
})
Expand Down
7 changes: 7 additions & 0 deletions tests/cmds_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ describe("The default config", function()
-- Make sure there isn't an extra commands file by default
local default_extra_cmds_path = TL.default_session_path:gsub("%.vim$", "x.vim")
assert.equals(0, vim.fn.filereadable(default_extra_cmds_path))

local sessions = Lib.get_session_list(as.get_root_dir())
assert.equal(1, #sessions)

assert.equal(TL.session_dir .. sessions[1].file_name, TL.default_session_path)
assert.equal(sessions[1].display_name, Lib.current_session_name())
assert.equal(sessions[1].session_name, TL.default_session_name)
end)

it("can restore a session for the cwd", function()
Expand Down
Loading

0 comments on commit 98ffedc

Please sign in to comment.