Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: load last cwd #63

Merged
merged 35 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3720e94
feat: add list_cwd command
josh-nz Jun 11, 2024
b446b65
feat: add cwd session support to telescope
josh-nz Jun 11, 2024
2671dc5
feat: load last session for cwd
josh-nz Jun 12, 2024
af1219f
fix: reduce calls to autosave_info
josh-nz Jun 12, 2024
17afbb9
fix: correct autosave log info
josh-nz Jun 12, 2024
7e697bc
fix: changes as per review
josh-nz Jun 13, 2024
0852a37
feat: add skip autosave opt, fixes startup autosave issues
josh-nz Jun 13, 2024
c7187d1
feat: add ListCwd command function to List command
josh-nz Jun 13, 2024
2d662fe
feat: show message when no session to autoload
josh-nz Jun 13, 2024
984b34a
feat: nicer error when loading session that does not exist
josh-nz Jun 13, 2024
aca4efa
fix: correct get_last typespec
josh-nz Jun 13, 2024
a93852a
fix: get_last caller to handle nil result
josh-nz Jun 13, 2024
d5e6255
fix: improve session action messages
josh-nz Jun 13, 2024
ba6eeed
Revert "feat: add ListCwd command function to List command"
josh-nz Jun 16, 2024
945a0f4
feat: add optional dir argument to PossessionListCwd command
josh-nz Jun 16, 2024
1a67b60
docs: correct PossessionLoad optional name description.
josh-nz Jun 16, 2024
8d373b6
feat: add optional dir argument to PossessionLoadCwd command
josh-nz Jun 17, 2024
97ce76f
docs: fixes spelling, more consistent in style.
josh-nz Jun 17, 2024
d715e76
chore: refactor to use absolute path function
josh-nz Jun 17, 2024
84c5748
feat: more autoload options
josh-nz Jun 17, 2024
f139ce3
feat: backwards compatibility with older autosave config
josh-nz Jun 17, 2024
6f9e0b1
fix: strip trailing slash from dir name
josh-nz Jun 17, 2024
056b1a7
fix: expand dir first otherwise dir check fails
josh-nz Jun 17, 2024
cd92d32
fix: run config compatibility before warning of unknown keys
josh-nz Jun 17, 2024
9b12c22
feat: smarter PossessionLoadCwd completion
josh-nz Jun 17, 2024
d9ab715
chore: refactor distinct list as query function
josh-nz Jun 19, 2024
08c9b7e
feat: add autoload config deprecation warning
josh-nz Jun 19, 2024
f9492cb
fix: use vim.deprecate for old autoload config setting
josh-nz Jun 22, 2024
1406a6a
fix: use improved unique session algorithm, adjust name
josh-nz Jun 22, 2024
57bddd8
feat: skip autoload when arguments are passed to Neovim
josh-nz Jun 23, 2024
50c875f
chore: fix spelling
josh-nz Jun 23, 2024
7db1fe6
feat: skip autoload if data is piped to Neovim
josh-nz Jun 25, 2024
bc2a5d9
feat: PossessionLoadCwd now takes a cwd session name
josh-nz Jun 25, 2024
3658c8a
docs: clarify the session name for PossessionSaveCwd
josh-nz Jun 25, 2024
5cc7ba6
fix: use last cwd session if no name passed to PossessionLoadCwd
josh-nz Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ require('possession').setup {
on_load = true,
on_quit = true,
},
autoload = {
cwd = false, -- or fun(): boolean
},
autoload = false, -- or 'last' or 'auto_cwd' or 'last_cwd' or fun(): string
commands = {
save = 'PossessionSave',
load = 'PossessionLoad',
Expand All @@ -89,6 +87,7 @@ require('possession').setup {
delete = 'PossessionDelete',
show = 'PossessionShow',
list = 'PossessionList',
list_cwd = 'PossessionListCwd',
migrate = 'PossessionMigrate',
},
hooks = {
Expand Down Expand Up @@ -192,6 +191,9 @@ require('telescope').load_extension('possession')
Then use `:Telescope possession list` or `require('telescope').extensions.possession.list()`
The default action will load selected session.

Alternatively, use `:Telescope possession list only_cwd=true` or `require('telescope').extensions.possession.list({only_cwd=true})`
This will limit the displayed sessions to those related to the current working directory.

![telescope](./img/telescope.png)

## Auto-save
Expand Down
36 changes: 26 additions & 10 deletions doc/possession.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ currently loaded session is used (unless noted otherwise).

Save the current session information under `session_dir`. Use
`:PossessionSave! [{name}]` to avoid asking for confirmation when overwriting
existing session file. If [{name}] is not provided and a session does not
existing session file. If [{name}] is omitted and a session does not
already exist, you will be prompted for a name.

*:PossessionLoad*
:PossessionLoad [{name}]~

Load given session from disk. When `name` is not ommitted, then use last
Load given session from disk. If `name` is omitted then use last
loaded session name.

*:PossessionSaveCwd*
Expand All @@ -27,15 +27,16 @@ Save session for current working directory. Use `!` to avoid confirmation
dialog.

*:PossessionLoadCwd*
:PossessionLoadCwd
:PossessionLoadCwd [{dir}]

Load session for current working directory.
Load last session for specified `dir`. If `dir` is omitted then use
the current working directory.

*:PossessionRename*
:PossessionRename [{name} [{new_name}]]~

Rename session `name` to `new_name`. If `new_name` is ommited then it is taken
from |vim.ui.input|. If `name` is ommited then current session name is used.
Rename session `name` to `new_name`. If `new_name` is omitted then it is taken
from |vim.ui.input|. If `name` is omitted then current session name is used.

*:PossessionClose*
:PossessionClose~
Expand All @@ -59,6 +60,14 @@ Show given session info.
List available sessions. `:PossessionList!` will not hide the `vimscript`
field that contains commands generated by |:mksession|.


:PossessionListCwd [{dir}]~

List available sessions with a cwd that matches the specified `dir`.
If `dir` is omitted the current working directory is used.
`:PossessionListCwd!` will not hide the `vimscript` field that contains
commands generated by |:mksession|.

*:PossessionMigrate*
:PossessionMigrate {dir_or_file}~

Expand Down Expand Up @@ -146,10 +155,17 @@ autosave.on_quit~
way the current window layout will be preserved.

*possession-autoload*
autoload.cwd~
`boolean | function(): boolean`
Automatically load a session for current working directory (created by
`autosave.cwd`) on VimEnter if such a session exists.
autoload~
`string | function(): string
Automatically load a session. Valid string values are:
- `last` loads the last saved session
- `auto_cwd` loads the session saved by `autosave.cwd` or `:PossessionSaveCwd`
- `last_cwd` loads the last session for the current working directory

If a function is provided, it needs to return the name of a session file
or a directory. If it is a directory, the last session for that directory
is loaded. The function can also return one of the above strings, in
which case the behaviour is as defined for that string.

*possession-hooks*
hooks.before_save~
Expand Down
8 changes: 6 additions & 2 deletions lua/possession.lua
josh-nz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local function setup(opts)
local names = config.commands
local commands = require('possession.commands')
local complete = commands.complete_session
local cwd_complete = commands.cwd_complete_session

cmd(names.save, 'name?', { nargs = '?', complete = complete, bang = true }, function(o)
commands.save(o.fargs[1], o.bang)
Expand All @@ -23,8 +24,8 @@ local function setup(opts)
cmd(names.save_cwd, '', { nargs = 0, bang = true }, function(o)
commands.save_cwd(o.bang)
end)
cmd(names.load_cwd, '', { nargs = 0 }, function(o)
commands.load_cwd()
cmd(names.load_cwd, 'dir?', { nargs = '?', complete = cwd_complete }, function(o)
commands.load_cwd(o.fargs[1])
end)
cmd(names.rename, 'old_name? new_name?', { nargs = '*', complete = complete }, function(o)
commands.rename(o.fargs[1], o.fargs[2])
Expand All @@ -41,6 +42,9 @@ local function setup(opts)
cmd(names.list, '', { nargs = 0, bang = true }, function(o)
commands.list(o.bang)
end)
cmd(names.list_cwd, 'dir?', { nargs = '?', complete = 'dir', bang = true }, function(o)
commands.list_cwd(o.fargs[1], o.bang)
end)
cmd(names.migrate, 'dir_or_file', { nargs = 1, complete = 'file' }, function(o)
commands.migrate(o.fargs[1])
end)
Expand Down
101 changes: 88 additions & 13 deletions lua/possession/commands.lua
jedrzejboczar marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ local function complete_list(candidates, opts)
end
end

-- Limits filesystem access by caching the session names per command line access
---@type table<string, string>?
-- Limits filesystem access by caching the session data per command line access
---@type table<string, { name: string, cwd: string }>?
local cached_names
vim.api.nvim_create_autocmd('CmdlineLeave', {
group = vim.api.nvim_create_augroup('possession.commands.complete', { clear = true }),
Expand All @@ -49,13 +49,33 @@ local function get_session_names()
if not cached_names then
cached_names = {}
for file, data in pairs(session.list()) do
cached_names[file] = data.name
cached_names[file] = { name = data.name, cwd = data.cwd }
end
end
return cached_names
end

M.complete_session = complete_list(get_session_names)
M.complete_session = complete_list(function()
return vim.tbl_map(function(s)
return s.name
end, get_session_names())
end)

M.cwd_complete_session = complete_list(function()
local cwds = vim.tbl_map(function(s)
return s.cwd
end, get_session_names())

local distinct_cwds = {}

for _, v in pairs(cwds) do
if not vim.tbl_contains(distinct_cwds, v) then
josh-nz marked this conversation as resolved.
Show resolved Hide resolved
table.insert(distinct_cwds, v)
end
end

return distinct_cwds
end)

local function get_current()
local name = session.get_session_name()
Expand All @@ -66,15 +86,17 @@ local function get_current()
return name
end

local function get_last()
local sessions = query.as_list()
---@param dir string dir to get sessions for
local function get_sessions_for_dir(dir)
return query.filter_by(query.as_list(), { cwd = paths.absolute_dir(dir) })
end

---@param sessions? table[] list of sessions from `as_list`
local function get_last(sessions)
sessions = sessions or query.as_list()
query.sort_by(sessions, 'mtime', true)
local last_session = sessions and sessions[1]
if not last_session then
utils.error('Cannot find last loaded session - specify session name as an argument')
return nil
end
return last_session.name
return last_session and last_session.name
end

local function name_or(name, getter)
Expand All @@ -100,6 +122,8 @@ function M.load(name)
name = name_or(name, get_last)
if name then
session.load(name)
else
utils.error('Cannot find last loaded session - specify session name as an argument')
end
end

Expand All @@ -108,8 +132,53 @@ function M.save_cwd(no_confirm)
session.save(paths.cwd_session_name(), { no_confirm = no_confirm })
end

function M.load_cwd()
session.load(paths.cwd_session_name())
---@param dir? string directory to load last session from
function M.load_cwd(dir)
if not dir then
session.load(paths.cwd_session_name())
return
end

local abs_dir = paths.absolute_dir(dir)
local last = get_last(get_sessions_for_dir(abs_dir))
if last then
session.load(last)
else
utils.error('No session found for path ' .. abs_dir)
end
end

function M.load_last(session_type)
local last
if session_type == 'last' then
last = get_last()
elseif session_type == 'auto_cwd' then
last = paths.cwd_session_name()
elseif session_type == 'last_cwd' then
last = get_last(get_sessions_for_dir(vim.fn.getcwd()))
elseif session_type then
-- Something was returned from custom config function.
if vim.fn.isdirectory(vim.fn.fnamemodify(session_type, ':p')) == 1 then
local abs = paths.absolute_dir(session_type)
last = get_last(get_sessions_for_dir(abs))
else
-- Try to load returned string as literal session name.

-- Futher down the `session.load` call stack will error
-- if `session_type` ends with `.json`. Strip if off, it
-- will get added back when needed.
last = string.gsub(session_type, '.json$', '')
end
else
utils.error('Possession.nvim: Unknown `autoload` config value `' .. session_type .. '`')
return
end

if last then
session.load(last, { skip_autosave = true })
return last
end
utils.info('No session found to autoload')
end

local function maybe_input(value, opts, callback)
Expand Down Expand Up @@ -174,6 +243,12 @@ function M.list(full)
display.echo_sessions { vimscript = full }
end

---@param full? boolean
function M.list_cwd(dir, full)
dir = dir or vim.fn.getcwd()
display.echo_sessions { vimscript = full, sessions = get_sessions_for_dir(dir) }
end

---@param path string
function M.migrate(path)
if vim.fn.getftype(path) == 'file' then
Expand Down
14 changes: 9 additions & 5 deletions lua/possession/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ local function defaults()
on_load = true,
on_quit = true,
},
autoload = {
cwd = false, -- or fun(): boolean
},
autoload = false, -- or 'last' or 'auto_cwd' or 'last_cwd' or fun(): string
commands = {
save = 'PossessionSave',
load = 'PossessionLoad',
Expand All @@ -35,6 +33,7 @@ local function defaults()
delete = 'PossessionDelete',
show = 'PossessionShow',
list = 'PossessionList',
list_cwd = 'PossessionListCwd',
migrate = 'PossessionMigrate',
},
hooks = {
Expand Down Expand Up @@ -151,13 +150,18 @@ local function fix_compatibility(opts)
enable = opts.telescope.previewer,
}
end

local autoload = vim.tbl_get(opts, 'autoload')
if type(autoload) == 'table' then
opts.autoload = autoload.cwd and 'auto_cwd' or false
end
jedrzejboczar marked this conversation as resolved.
Show resolved Hide resolved
end

function M.setup(opts)
warn_on_unknown_keys(opts)

fix_compatibility(opts)

warn_on_unknown_keys(opts)

local new_config = vim.tbl_deep_extend('force', {}, defaults(), opts or {})
-- Do _not_ replace the table pointer with `config = ...` because this
-- wouldn't change the tables that have already been `require`d by other
Expand Down
4 changes: 4 additions & 0 deletions lua/possession/display.lua
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ function M.echo_sessions(opts)
}, opts or {})

local sessions = opts.sessions or query.as_list()
if #sessions == 0 then
utils.info('No sessions found')
return
end

local info = {}
if opts.buffers or opts.tab_cwd then
Expand Down
9 changes: 9 additions & 0 deletions lua/possession/paths.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ function M.cwd_session_name()
return vim.fn.fnamemodify(global_cwd, ':~')
end

--- Vim expands the given dir, then converts it to an absolute path
function M.absolute_dir(dir)
local p = Path:new(vim.fn.expand(dir)):absolute()
if vim.endswith(p, Path.path.sep) then
p = p:sub(1, #p - 1)
end
return p
end

return M
11 changes: 10 additions & 1 deletion lua/possession/query.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ local config = require('possession.config')
---@param sessions? table<string, table> like from possession.session.list()
---@return table[] list of session data with additional `file` key
function M.as_list(sessions)
sessions = sessions or session.list() --[[@as table<string, table> ]]
sessions = sessions or session.list()
local list = {}
for file, data in pairs(sessions) do
if data.file then
Expand All @@ -21,6 +21,15 @@ function M.as_list(sessions)
return list
end

--- Filters a list of sessions
---@param sessions table[] list of sessions from `as_list`
---@param opts { cwd: string }
function M.filter_by(sessions, opts)
return vim.tbl_filter(function(s)
return s.cwd == opts.cwd
end, sessions)
end

---@alias possession.QuerySortKey 'name'|'atime'|'mtime'|'ctime'

--- Sort a list of sessions in-place
Expand Down
Loading
Loading