Skip to content

Commit 555875a

Browse files
feat: tab-scoped sessions
1 parent 867c830 commit 555875a

File tree

5 files changed

+163
-63
lines changed

5 files changed

+163
-63
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ A: While it's amazing that this feature is built-in to vim, and it does an impre
1414

1515
## TODO
1616

17-
- [ ] tab-scoped sessions
1817
- [ ] documentation
1918
- [ ] make filepaths relative so sessions are portable
2019

lua/resession/config.lua

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@ local default_config = {
2222
"winfixheight",
2323
"winfixwidth",
2424
},
25-
buffers = {
26-
-- Custom logic for determining if the buffer should be included
27-
filter = function(bufnr)
28-
if not vim.tbl_contains({ "", "acwrite", "help" }, vim.bo[bufnr].buftype) then
29-
return false
30-
end
31-
return true
32-
end,
33-
},
34-
windows = {},
25+
-- Custom logic for determining if the buffer should be included
26+
buf_filter = function(bufnr)
27+
if not vim.tbl_contains({ "", "acwrite", "help" }, vim.bo[bufnr].buftype) then
28+
return false
29+
end
30+
return vim.bo[bufnr].buflisted
31+
end,
32+
-- Custom logic for determining if a buffer should be included in a tab-scoped session
33+
tab_buf_filter = function(tabpage, bufnr)
34+
return true
35+
end,
3536
-- The name of the directory to store sessions in
3637
dir = "session",
3738
-- Configuration for extensions
@@ -70,9 +71,7 @@ M.setup = function(config)
7071
vim.api.nvim_create_autocmd("VimLeavePre", {
7172
group = autosave_group,
7273
callback = function()
73-
if resession.get_current() then
74-
resession.save(nil, { notify = false })
75-
end
74+
resession.save_all({ notify = false })
7675
end,
7776
})
7877
autosave_timer = vim.loop.new_timer()
@@ -81,9 +80,7 @@ M.setup = function(config)
8180
M.autosave.interval * 1000,
8281
M.autosave.interval * 1000,
8382
vim.schedule_wrap(function()
84-
if resession.get_current() then
85-
resession.save(nil, { notify = M.autosave.notify })
86-
end
83+
resession.save_all({ notify = M.autosave.notify })
8784
end)
8885
)
8986
end

lua/resession/init.lua

Lines changed: 134 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ local M = {}
22

33
local pending_config
44
local current_session
5+
local tab_sessions = {}
56

67
local function do_setup()
78
if pending_config then
@@ -19,12 +20,15 @@ end
1920
---Get the name of the current session
2021
---@return string|nil
2122
M.get_current = function()
22-
return current_session
23+
local tabpage = vim.api.nvim_get_current_tabpage()
24+
return tab_sessions[tabpage] or current_session
2325
end
2426

2527
---Detach from the current session
2628
M.detach = function()
2729
current_session = nil
30+
local tabpage = vim.api.nvim_get_current_tabpage()
31+
tab_sessions[tabpage] = nil
2832
end
2933

3034
---@class resession.ListOpts
@@ -59,6 +63,15 @@ M.list = function(opts)
5963
return ret
6064
end
6165

66+
local function remove_tabpage_session(name)
67+
for k, v in pairs(tab_sessions) do
68+
if v == name then
69+
tab_sessions[k] = nil
70+
break
71+
end
72+
end
73+
end
74+
6275
---@class resession.DeleteOpts
6376
---@field dir nil|string Name of directory to save to (overrides config.dir)
6477

@@ -88,47 +101,33 @@ M.delete = function(name, opts)
88101
if current_session == name then
89102
current_session = nil
90103
end
104+
remove_tabpage_session(name)
91105
end
92106

93-
---@class resession.SaveOpts
94-
---@field detach nil|boolean Immediately detach from the saved session
95-
---@field notify nil|boolean Notify on success
96-
---@field dir nil|string Name of directory to save to (overrides config.dir)
97-
98-
---@param name? string
99-
---@param opts? resession.SaveOpts
100-
M.save = function(name, opts)
101-
opts = vim.tbl_extend("keep", opts or {}, {
102-
notify = true,
103-
})
104-
if not name then
105-
name = current_session
106-
end
107-
if not name then
108-
vim.ui.input({ prompt = "Session name" }, function(selected)
109-
if selected then
110-
M.save(selected, opts)
111-
end
112-
end)
113-
return
114-
end
107+
---@param name string
108+
---@param opts resession.SaveOpts
109+
---@param target_tabpage? integer
110+
local function save(name, opts, target_tabpage)
115111
local config = require("resession.config")
116112
local files = require("resession.files")
117113
local layout = require("resession.layout")
118114
local util = require("resession.util")
119115
local filename = util.get_session_file(name, opts.dir)
116+
local cwd
120117
local data = {
121118
buffers = {},
122119
tabs = {},
120+
tab_scoped = target_tabpage ~= nil,
123121
global = {
124122
cwd = vim.fn.getcwd(-1, -1),
125123
height = vim.o.lines - vim.o.cmdheight,
126124
width = vim.o.columns,
127-
options = util.save_global_options(),
125+
-- Don't save global options for tab-scoped session
126+
options = target_tabpage and {} or util.save_global_options(),
128127
},
129128
}
130129
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
131-
if config.buffers.filter(bufnr) then
130+
if util.include_buf(target_tabpage, bufnr) then
132131
local buf = {
133132
name = vim.api.nvim_buf_get_name(bufnr),
134133
loaded = vim.api.nvim_buf_is_loaded(bufnr),
@@ -137,20 +136,24 @@ M.save = function(name, opts)
137136
table.insert(data.buffers, buf)
138137
end
139138
end
140-
for _, tabpage in ipairs(vim.api.nvim_list_tabpages()) do
139+
local tabpages = target_tabpage and { target_tabpage } or vim.api.nvim_list_tabpages()
140+
local buf_filter = function(bufnr)
141+
return util.include_buf(target_tabpage, bufnr)
142+
end
143+
for _, tabpage in ipairs(tabpages) do
141144
local tab = {}
142145
local tabnr = vim.api.nvim_tabpage_get_number(tabpage)
143-
if vim.fn.haslocaldir(-1, tabnr) == 1 then
146+
if target_tabpage or vim.fn.haslocaldir(-1, tabnr) == 1 then
144147
tab.cwd = vim.fn.getcwd(-1, tabnr)
145148
end
146149
table.insert(data.tabs, tab)
147150
local winlayout = vim.fn.winlayout(tabnr)
148-
tab.wins = layout.add_win_info_to_layout(tabnr, winlayout)
151+
tab.wins = layout.add_win_info_to_layout(tabnr, winlayout, buf_filter)
149152
end
150153

151-
for ext_name in pairs(config.extensions) do
154+
for ext_name, ext_config in pairs(config.extensions) do
152155
local ext = util.get_extension(ext_name)
153-
if ext then
156+
if ext and (ext_config.enable_in_tab or not target_tabpage) then
154157
local ok, ext_data = pcall(ext.on_save)
155158
if ok then
156159
data[ext_name] = ext_data
@@ -164,21 +167,97 @@ M.save = function(name, opts)
164167
end
165168

166169
files.write_json_file(filename, data)
167-
if not opts.detach then
168-
current_session = name
169-
end
170170
if opts.notify then
171171
vim.notify(string.format("Saved session %s", name))
172172
end
173173
end
174174

175+
---@class resession.SaveOpts
176+
---@field attach nil|boolean Stay attached to session after saving
177+
---@field notify nil|boolean Notify on success
178+
---@field dir nil|string Name of directory to save to (overrides config.dir)
179+
180+
---@param name? string
181+
---@param opts? resession.SaveOpts
182+
M.save = function(name, opts)
183+
opts = vim.tbl_extend("keep", opts or {}, {
184+
notify = true,
185+
attach = true,
186+
})
187+
if not name then
188+
-- If no name, default to the current session
189+
name = current_session
190+
end
191+
if not name then
192+
vim.ui.input({ prompt = "Session name" }, function(selected)
193+
if selected then
194+
M.save(selected, opts)
195+
end
196+
end)
197+
return
198+
end
199+
save(name, opts)
200+
tab_sessions = {}
201+
if opts.attach then
202+
current_session = name
203+
else
204+
current_session = nil
205+
end
206+
end
207+
208+
---Save a tab-scoped session
209+
---@param name string
210+
---@param opts? resession.SaveOpts
211+
M.save_tab = function(name, opts)
212+
opts = vim.tbl_extend("keep", opts or {}, {
213+
notify = true,
214+
attach = true,
215+
})
216+
local cur_tabpage = vim.api.nvim_get_current_tabpage()
217+
if not name then
218+
name = tab_sessions[cur_tabpage]
219+
end
220+
if not name then
221+
vim.ui.input({ prompt = "Session name" }, function(selected)
222+
if selected then
223+
M.save_tab(selected, opts)
224+
end
225+
end)
226+
return
227+
end
228+
save(name, opts, cur_tabpage)
229+
current_session = nil
230+
remove_tabpage_session(name)
231+
if opts.attach then
232+
tab_sessions[cur_tabpage] = name
233+
else
234+
tab_sessions[cur_tabpage] = nil
235+
end
236+
end
237+
238+
---Save all current sessions to disk
239+
M.save_all = function(opts)
240+
opts = vim.tbl_extend("keep", opts or {}, {
241+
notify = true,
242+
})
243+
if current_session then
244+
save(current_session, opts)
245+
else
246+
for tabpage, name in pairs(tab_sessions) do
247+
save(name, opts, tabpage)
248+
end
249+
end
250+
end
251+
175252
local function open_clean_tab()
176253
-- Detect if we're already in a "clean" tab
177254
-- (one window, and one empty scratch buffer)
178255
if #vim.api.nvim_tabpage_list_wins(0) == 1 then
179256
if vim.api.nvim_buf_get_name(0) == "" then
180257
local lines = vim.api.nvim_buf_get_lines(0, -1, 2, false)
181-
if #lines == 1 and lines[1] == "" then
258+
if vim.tbl_isempty(lines) then
259+
vim.api.nvim_buf_set_option(0, "buflisted", false)
260+
vim.api.nvim_buf_set_option(0, "bufhidden", "wipe")
182261
return
183262
end
184263
end
@@ -201,16 +280,17 @@ local function close_everything()
201280
end
202281

203282
---@class resession.LoadOpts
204-
---@field detach nil|boolean Detach from session after loading
205-
---@field reset nil|boolean Close everthing before loading the session (default true)
283+
---@field attach nil|boolean Attach to session after loading
284+
---@field reset nil|boolean|"auto" Close everthing before loading the session (default "auto")
206285
---@field silence_errors nil|boolean Don't error when trying to load a missing session
207286
---@field dir nil|string Name of directory to load from (overrides config.dir)
208287

209288
---@param name? string
210289
---@param opts? resession.LoadOpts
211290
M.load = function(name, opts)
212291
opts = vim.tbl_extend("keep", opts or {}, {
213-
reset = true,
292+
reset = "auto",
293+
attach = true,
214294
})
215295
local config = require("resession.config")
216296
local files = require("resession.files")
@@ -237,6 +317,9 @@ M.load = function(name, opts)
237317
end
238318
return
239319
end
320+
if opts.reset == "auto" then
321+
opts.reset = not data.tab_scoped
322+
end
240323
if opts.reset then
241324
close_everything()
242325
else
@@ -248,7 +331,9 @@ M.load = function(name, opts)
248331
vim.o.columns / data.global.width,
249332
(vim.o.lines - vim.o.cmdheight) / data.global.height,
250333
}
251-
vim.cmd(string.format("cd %s", data.global.cwd))
334+
if not data.tab_scoped then
335+
vim.cmd(string.format("cd %s", data.global.cwd))
336+
end
252337
for _, buf in ipairs(data.buffers) do
253338
local bufnr = vim.fn.bufadd(buf.name)
254339
if buf.loaded then
@@ -294,10 +379,17 @@ M.load = function(name, opts)
294379
for k, v in pairs(data.global.options) do
295380
vim.o[k] = v
296381
end
297-
if opts.detach then
298-
current_session = nil
299-
else
300-
current_session = name
382+
current_session = nil
383+
if opts.reset then
384+
tab_sessions = {}
385+
end
386+
remove_tabpage_session(name)
387+
if opts.attach then
388+
if data.tab_scoped then
389+
tab_sessions[vim.api.nvim_get_current_tabpage()] = name
390+
else
391+
current_session = name
392+
end
301393
end
302394
end
303395

lua/resession/layout.lua

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ local M = {}
44

55
---@param tabnr integer
66
---@param winid integer
7+
---@param buf_filter nil|fun(bufnr: integer): boolean
78
---@return table|false
8-
M.get_win_info = function(tabnr, winid)
9+
local function get_win_info(tabnr, winid, buf_filter)
910
local bufnr = vim.api.nvim_win_get_buf(winid)
1011
local win = {}
1112
local supported_by_ext = false
@@ -26,7 +27,7 @@ M.get_win_info = function(tabnr, winid)
2627
break
2728
end
2829
end
29-
if not supported_by_ext and not config.buffers.filter(bufnr) then
30+
if not supported_by_ext and not buf_filter(bufnr) then
3031
return false
3132
end
3233
win = vim.tbl_extend("error", win, {
@@ -46,10 +47,11 @@ end
4647

4748
---@param tabnr integer
4849
---@param layout table
49-
M.add_win_info_to_layout = function(tabnr, layout)
50+
---@param buf_filter nil|fun(bufnr: integer): boolean
51+
M.add_win_info_to_layout = function(tabnr, layout, buf_filter)
5052
local type = layout[1]
5153
if type == "leaf" then
52-
layout[2] = M.get_win_info(tabnr, layout[2])
54+
layout[2] = get_win_info(tabnr, layout[2], buf_filter)
5355
if not layout[2] then
5456
return false
5557
end
@@ -155,6 +157,9 @@ end
155157
---@param scale_factor number[] Scaling factor for [width, height]
156158
---@return nil|integer The window that should have focus after session load
157159
M.set_winlayout = function(layout, scale_factor)
160+
if not layout then
161+
return
162+
end
158163
set_winlayout(layout)
159164
local ret = {}
160165
set_winlayout_data(layout, scale_factor, ret)

0 commit comments

Comments
 (0)