1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
-- Copyright 2007-2018 Mitchell mitchell.att.foicica.com. See LICENSE.
local M = {}
--[[ This comment is for LuaDoc.
---
-- Session support for Textadept.
-- @field save_on_quit (bool)
-- Save the session when quitting.
-- The default value is `true` unless the user passed the command line switch
-- `-n` or `--nosession` to Textadept.
-- @field max_recent_files (number)
-- The maximum number of recent files to save in session files.
-- Recent files are stored in [`io.recent_files`]().
-- The default value is `10`.
module('textadept.session')]]
M.save_on_quit = true
M.max_recent_files = 10
local session_file = _USERHOME..(not CURSES and '/session' or '/session_term')
---
-- Loads session file *filename* or the user-selected session, returning `true`
-- if a session file was opened and read.
-- Textadept restores split views, opened buffers, cursor information, recent
-- files, and bookmarks.
-- @param filename Optional absolute path to the session file to load. If `nil`,
-- the user is prompted for one.
-- @return `true` if the session file was opened and read; `false` otherwise.
-- @usage textadept.session.load(filename)
-- @name load
function M.load(filename)
local dir, name = session_file:match('^(.-[/\\]?)([^/\\]+)$')
filename = filename or ui.dialogs.fileselect{
title = _L['Load Session'], with_directory = dir, with_file = name
}
if not filename then return end
local not_found = {}
local f = io.open(filename, 'rb')
if not f or not io.close_all_buffers() then return false end
io.recent_files = {}
local current_view, splits = view, {[0] = {}}
for line in f:lines() do
if line:find('^buffer:') then
local patt = '^buffer: (%d+) (%d+) (%d+) (.+)$'
local anchor, current_pos, top_line, filename = line:match(patt)
if not filename:find('^%[.+%]$') then
if lfs.attributes(filename) then
io.open_file(filename)
else
not_found[#not_found + 1] = filename
end
else
buffer.new()._type = filename
events.emit(events.FILE_OPENED, filename) -- close initial untitled buf
end
-- Restore saved buffer selection and view.
buffer:set_sel(tonumber(anchor), tonumber(current_pos))
buffer:line_scroll(0, buffer:visible_from_doc_line(tonumber(top_line)) -
buffer.first_visible_line)
elseif line:find('^bookmarks:') then
local lines = line:match('^bookmarks: (.*)$')
for line in lines:gmatch('%d+') do
buffer:marker_add(tonumber(line), textadept.bookmarks.MARK_BOOKMARK)
end
elseif line:find('^size:') then
local maximized, width, height = line:match('^size: (%l+) (%d+) (%d+)$')
ui.maximized = maximized == 'true'
if not ui.maximized then ui.size = {width, height} end
elseif line:find('^%s*split%d:') then
local level, num, type, size = line:match('^(%s*)split(%d): (%S+) (%d+)')
local view = splits[#level] and splits[#level][tonumber(num)] or view
ui.goto_view(view)
splits[#level + 1] = {view:split(type == 'true')}
splits[#level + 1][1].size = tonumber(size) -- could be 1 or 2
elseif line:find('^%s*view%d:') then
local level, num, buf_idx = line:match('^(%s*)view(%d): (%d+)$')
local view = splits[#level][tonumber(num)] or view
buf_idx = tonumber(buf_idx)
if buf_idx > #_BUFFERS then buf_idx = #_BUFFERS end
view:goto_buffer(_BUFFERS[buf_idx])
elseif line:find('^current_view:') then
current_view = _VIEWS[tonumber(line:match('^current_view: (%d+)')) or 1]
elseif line:find('^recent:') then
-- If a recent file is already open, do not add it to the list again.
local recent_file, exists = line:match('^recent: (.+)$'), false
for i = 1, #io.recent_files do
if io.recent_files[i] == recent_file then exists = true break end
end
if not exists then io.recent_files[#io.recent_files + 1] = recent_file end
end
end
f:close()
ui.goto_view(current_view)
if #not_found > 0 then
ui.dialogs.msgbox{
title = _L['Session Files Not Found'],
text = _L['The following session files were not found'],
informative_text = table.concat(not_found, '\n'):iconv('UTF-8', _CHARSET),
icon = 'gtk-dialog-warning'
}
end
session_file = filename
return true
end
-- Load session when no args are present.
local function load_default_session()
if M.save_on_quit then M.load(session_file) end
end
events.connect(events.ARG_NONE, load_default_session)
---
-- Saves the session to file *filename* or the user-selected file.
-- Saves split views, opened buffers, cursor information, recent files, and
-- bookmarks.
-- @param filename Optional absolute path to the session file to save. If `nil`,
-- the user is prompted for one.
-- @usage textadept.session.save(filename)
-- @name save
function M.save(filename)
local dir, name = session_file:match('^(.-[/\\]?)([^/\\]+)$')
filename = filename or ui.dialogs.filesave{
title = _L['Save Session'], with_directory = dir,
with_file = name:iconv('UTF-8', _CHARSET)
}
if not filename then return end
local session = {}
local buffer_line = 'buffer: %d %d %d %s' -- anchor, cursor, line, filename
local split_line = '%ssplit%d: %s %d' -- level, number, type, size
local view_line = '%sview%d: %d' -- level, number, doc index
-- Write out opened buffers.
for i = 1, #_BUFFERS do
local buffer = _BUFFERS[i]
local filename = buffer.filename or buffer._type
if filename then
local current = buffer == view.buffer
local anchor = current and 'anchor' or '_anchor'
local current_pos = current and 'current_pos' or '_current_pos'
local top_line = current and 'first_visible_line' or '_top_line'
session[#session + 1] = buffer_line:format(buffer[anchor] or 0,
buffer[current_pos] or 0,
buffer[top_line] or 0,
filename)
-- Write out bookmarks.
local lines = {}
local line = buffer:marker_next(0, 2^textadept.bookmarks.MARK_BOOKMARK)
while line >= 0 do
lines[#lines + 1] = line
line = buffer:marker_next(line + 1, 2^textadept.bookmarks.MARK_BOOKMARK)
end
session[#session + 1] = 'bookmarks: '..table.concat(lines, ' ')
end
end
-- Write out window size. Do this before writing split views since split view
-- size depends on the window size.
local maximized, size = tostring(ui.maximized), ui.size
session[#session + 1] = string.format('size: %s %d %d', maximized, size[1],
size[2])
-- Write out split views.
local function write_split(split, level, number)
local c1, c2 = split[1], split[2]
local vertical, size = tostring(split.vertical), split.size
local spaces = string.rep(' ', level)
session[#session + 1] = split_line:format(spaces, number, vertical, size)
spaces = string.rep(' ', level + 1)
if c1[1] and c1[2] then
write_split(c1, level + 1, 1)
else
session[#session + 1] = view_line:format(spaces, 1, _BUFFERS[c1.buffer])
end
if c2[1] and c2[2] then
write_split(c2, level + 1, 2)
else
session[#session + 1] = view_line:format(spaces, 2, _BUFFERS[c2.buffer])
end
end
local splits = ui.get_split_table()
if splits[1] and splits[2] then
write_split(splits, 0, 0)
else
session[#session + 1] = view_line:format('', 1, _BUFFERS[splits.buffer])
end
-- Write out the current focused view.
session[#session + 1] = string.format('current_view: %d', _VIEWS[view])
-- Write out other things.
for i = 1, #io.recent_files do
if i > M.max_recent_files then break end
session[#session + 1] = string.format('recent: %s', io.recent_files[i])
end
-- Write the session.
local f = io.open(filename, 'wb')
if f then f:write(table.concat(session, '\n')):close() end
session_file = filename
end
-- Saves session on quit.
events.connect(events.QUIT, function()
if M.save_on_quit then M.save(session_file) end
end, 1)
-- Does not save session on quit.
args.register('-n', '--nosession', 0,
function() M.save_on_quit = false end, 'No session functionality')
-- Loads the given session on startup.
args.register('-s', '--session', 1, function(name)
if not lfs.attributes(name) then name = _USERHOME..'/'..name end
M.load(name)
events.disconnect(events.ARG_NONE, load_default_session)
end, 'Load session')
return M
|