init.lua 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. local M = {}
  2. --[[ This comment is for LuaDoc.
  3. ---
  4. -- A module for hotexit - keeps unsaved buffers on quit
  5. module('_M.hotexit')]]
  6. -- for better random()
  7. math.randomseed(os.time())
  8. -- Base 64 encoding decoding from: https://stackoverflow.com/a/35303321
  9. local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
  10. -- encoding
  11. function enc_base64(data)
  12. return ((data:gsub('.', function(x)
  13. local r,b='',x:byte()
  14. for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
  15. return r;
  16. end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
  17. if (#x < 6) then return '' end
  18. local c=0
  19. for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
  20. return b:sub(c+1,c+1)
  21. end)..({ '', '==', '=' })[#data%3+1])
  22. end
  23. -- decoding
  24. function dec_base64(data)
  25. data = string.gsub(data, '[^'..b..'=]', '')
  26. return (data:gsub('.', function(x)
  27. if (x == '=') then return '' end
  28. local r,f='',(b:find(x)-1)
  29. for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
  30. return r;
  31. end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
  32. if (#x ~= 8) then return '' end
  33. local c=0
  34. for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
  35. return string.char(c)
  36. end))
  37. end
  38. _UNSAVED_CHANGED = false
  39. -- when made changes on a saved buffer
  40. events.connect(events.SAVE_POINT_LEFT, function(str)
  41. -- we remember that this has not been saved on unsaved memory
  42. buffer.unsavedbuffer = true
  43. -- pending changes to be written to disk (changes are in RAM)
  44. buffer.unsavedpendingchanges = true
  45. -- we give unsaved buffer an id
  46. if buffer.unsavedid == nil then
  47. buffer.unsavedid = math.random(100001,999999)
  48. end
  49. -- we keep the time when it was modified
  50. -- we will use it later to determine if buffer changed after we last
  51. -- saved it to unsaved memory
  52. buffer.unsavedmod = os.time(os.date("!*t"))
  53. _UNSAVED_CHANGED = true
  54. end)
  55. events.connect(events.CHAR_ADDED, function(str)
  56. -- so that ui.print does not see it as a savable change
  57. if buffer.tab_label ~= '[Message Buffer]' then
  58. buffer.unsavedbuffer = true
  59. buffer.unsavedpendingchanges = true
  60. _UNSAVED_CHANGED = true
  61. end
  62. end)
  63. events.connect(events.SAVE_POINT_REACHED, function(str)
  64. buffer.unsavedbuffer = false
  65. buffer.unsavedpendingchanges = false
  66. end)
  67. function save_unsaveddata(force_save)
  68. if not force_save then force_save = false end
  69. -- when force_save is true, we won't respect the timeout.
  70. -- it will run the function right away if it's set to true
  71. -- regardless of what is set on _UNSAVED_CHANGED.
  72. if force_save == false and _UNSAVED_CHANGED == false then return '' end
  73. local buffer_filename = ''
  74. local buffer_filenames = {}
  75. local unsaved_buffer_count = 0
  76. os.execute("mkdir " .. _USERHOME..'/_hotexitdata')
  77. os.remove(_USERHOME..'/_hotexitdata/hotexitdata.txt')
  78. if (#_BUFFERS > 0) then
  79. file = io.open(_USERHOME..'/_hotexitdata/hotexitdata.txt', "a")
  80. for j = 1, #_BUFFERS do
  81. if (_BUFFERS[j].unsavedbuffer == true) then
  82. unsaved_buffer_count = unsaved_buffer_count + 1
  83. if not _BUFFERS[j].filename then
  84. buffer_filename = ''
  85. else
  86. buffer_filename = _BUFFERS[j].filename
  87. end
  88. --[[
  89. Data order:
  90. 1. the text "unsavedbuffer"
  91. 2. unsaved buffer serial number
  92. 3. buffer filename (blank if not saved anywhere)
  93. 4. unsaved buffer modified date
  94. 5. cursor position
  95. 6. lexer type for the unsaved buffer
  96. * Separated by double pipe characters (||)
  97. --]]
  98. file:write('unsavedbuffer||'.._BUFFERS[j].unsavedid..'||'..buffer_filename..'||'.._BUFFERS[j].unsavedmod..'||'.._BUFFERS[j].current_pos..'||'..buffer.get_lexer(_BUFFERS[j]), "\n")
  99. if _BUFFERS[j].unsavedpendingchanges == true then
  100. unsavedfile = io.open(_USERHOME..'/_hotexitdata/'.._BUFFERS[j].unsavedid..'.txt', "w") -- w to overwrite existing file
  101. unsavedfile:write(enc_base64(buffer.get_text(_BUFFERS[j])))
  102. io.close(unsavedfile)
  103. _BUFFERS[j].unsavedpendingchanges = false
  104. end
  105. -- keep the filename for cleanup later
  106. buffer_filenames[#buffer_filenames+1] = _BUFFERS[j].unsavedid .. '.txt'
  107. end
  108. end
  109. io.close(file)
  110. _UNSAVED_CHANGED = false
  111. -- cleanup
  112. clean_unsaveddata(buffer_filenames)
  113. end
  114. return false -- quit. returning true will not quit
  115. end
  116. -- gets the filenames in an array
  117. function get_files(dir)
  118. local dircmd = "ls -1 " .. dir -- default to Unix
  119. if string.sub(package.config,1,1) == '\\' then
  120. -- Windows
  121. dircmd = "dir /b " .. dir
  122. end
  123. --~ local files = os.execute(dircmd)
  124. local handle = io.popen(dircmd)
  125. local result = handle:read("*a")
  126. handle:close()
  127. -- get the files as array
  128. local files = explode("\n", result)
  129. --~ print(files)
  130. return files
  131. end
  132. -- Source: http://lua-users.org/wiki/MakingLuaLikePhp
  133. -- Credit: http://richard.warburton.it/
  134. function explode(div,str)
  135. if (div=='') then return false end
  136. local pos,arr = 0,{}
  137. for st,sp in function() return string.find(str,div,pos,true) end do
  138. table.insert(arr,string.sub(str,pos,st-1))
  139. pos = sp + 1
  140. end
  141. table.insert(arr,string.sub(str,pos))
  142. return arr
  143. end
  144. -- check if a string is there in an array
  145. local function array_has_value(tab, val)
  146. for index, value in ipairs(tab) do
  147. if value == val then
  148. return true
  149. end
  150. end
  151. return false
  152. end
  153. -- clean unsaved data files that we don't need
  154. -- (files that are no longer referenced in the hostexitdata.txt)
  155. function clean_unsaveddata(logged_buffers)
  156. local buffer_files = get_files(_USERHOME..'/_hotexitdata/')
  157. for fileCount = 1, #buffer_files do
  158. if buffer_files[fileCount] ~= 'hotexitdata.txt' and array_has_value(logged_buffers, buffer_files[fileCount]) == false then
  159. os.remove(_USERHOME .. '/_hotexitdata/' .. buffer_files[fileCount])
  160. end
  161. end
  162. end
  163. events.connect(events.QUIT, function(str)
  164. textadept.session.save( _USERHOME..'/session' )
  165. save_unsaveddata(true)
  166. return false
  167. end, 1) -- adding 1 prevents the default quit event function to run
  168. -- save unsaved data after certain intervals
  169. timeout(10, function()
  170. save_unsaveddata()
  171. return true -- true means repeat
  172. end)
  173. function file_exists(file)
  174. local f = io.open(file, "rb")
  175. if f then f:close() end
  176. return f ~= nil
  177. end
  178. function split(pString, pPattern)
  179. local Table = {n = 0} -- NOTE: use {n = 0} in Lua-5.0
  180. local fpat = "(.-)" .. pPattern
  181. local last_end = 1
  182. local s, e, cap = pString:find(fpat, 1)
  183. while s do
  184. if s ~= 1 or cap ~= "" then
  185. table.insert(Table,cap)
  186. end
  187. last_end = e+1
  188. s, e, cap = pString:find(fpat, last_end)
  189. end
  190. if last_end <= #pString then
  191. cap = pString:sub(last_end)
  192. table.insert(Table, cap)
  193. end
  194. return Table
  195. end
  196. -- return buffer index after searching for filename
  197. function getbufferbyfilename(filename)
  198. for j = 1, #_BUFFERS do
  199. if (_BUFFERS[j].filename == filename) then
  200. return j
  201. end
  202. end
  203. return nil
  204. end
  205. -- read all the content on file
  206. function readallcontent(file)
  207. local f = assert(io.open(file, "rb"))
  208. local content = f:read("*all")
  209. f:close()
  210. return content
  211. end
  212. -- read back the previously saved unsaved data
  213. function restore_unsaveddata()
  214. local unsaveddata_filename = _USERHOME..'/_hotexitdata/hotexitdata.txt'
  215. local line_data
  216. if not file_exists(unsaveddata_filename) then return {} end
  217. lines = {}
  218. for line in io.lines(unsaveddata_filename) do
  219. line_data = split(line, "||") -- index is 1 based
  220. local unsaveddata_buffer_filename = _USERHOME..'/_hotexitdata/'..line_data[2]..'.txt'
  221. if file_exists(unsaveddata_buffer_filename) then
  222. local buffer_index = getbufferbyfilename(line_data[3])
  223. if buffer_index ~= nil then -- buffer already exists
  224. _mybuffer = _BUFFERS[buffer_index]
  225. else
  226. _mybuffer = buffer.new()
  227. end
  228. -- fix: set the text so that it can undo to original text
  229. -- when buffer has unsaved change
  230. buffer.set_text(_mybuffer, dec_base64(readallcontent(unsaveddata_buffer_filename)))
  231. if (line_data[3] ~= '') then
  232. _mybuffer.filename = line_data[3]
  233. end
  234. buffer.goto_pos(_mybuffer, tonumber(line_data[5])) -- doesn't work properly for all buffers
  235. buffer.scroll_caret(_mybuffer)
  236. buffer.set_lexer(buffer, line_data[6])
  237. -- we indicate that we have this state saved
  238. _mybuffer.unsavedbuffer = true
  239. _mybuffer.unsavedid = line_data[2]
  240. _mybuffer.unsavedmod = line_data[4]
  241. _mybuffer.unsavedpendingchanges = false
  242. end
  243. end
  244. end
  245. events.connect(events.INITIALIZED, function(str)
  246. restore_unsaveddata()
  247. end)
  248. return M