init_single_file_based_hotexit_data.lua 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. -- Base 64 encoding decoding from: https://stackoverflow.com/a/35303321
  7. local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
  8. -- encoding
  9. function enc_base64(data)
  10. return ((data:gsub('.', function(x)
  11. local r,b='',x:byte()
  12. for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
  13. return r;
  14. end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
  15. if (#x < 6) then return '' end
  16. local c=0
  17. for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
  18. return b:sub(c+1,c+1)
  19. end)..({ '', '==', '=' })[#data%3+1])
  20. end
  21. -- decoding
  22. function dec_base64(data)
  23. data = string.gsub(data, '[^'..b..'=]', '')
  24. return (data:gsub('.', function(x)
  25. if (x == '=') then return '' end
  26. local r,f='',(b:find(x)-1)
  27. for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
  28. return r;
  29. end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
  30. if (#x ~= 8) then return '' end
  31. local c=0
  32. for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
  33. return string.char(c)
  34. end))
  35. end
  36. _UNSAVED_CHANGED = false
  37. -- when made changes on a saved buffer
  38. events.connect(events.SAVE_POINT_LEFT, function(str)
  39. buffer.notsaved = true
  40. _UNSAVED_CHANGED = true
  41. end)
  42. events.connect(events.CHAR_ADDED, function(str)
  43. -- so that ui.print does not see it as a savable change
  44. if buffer.tab_label ~= '[Message Buffer]' then
  45. buffer.notsaved = true
  46. _UNSAVED_CHANGED = true
  47. end
  48. end)
  49. events.connect(events.SAVE_POINT_REACHED, function(str)
  50. buffer.notsaved = false
  51. end)
  52. function save_unsaveddata(force_save)
  53. if not force_save then force_save = false end
  54. -- when force_save is true, we won't respect the timeout.
  55. -- it will run the function right away if it's set to true
  56. -- regardless of what is set on _UNSAVED_CHANGED.
  57. if force_save == false and _UNSAVED_CHANGED == false then return '' end
  58. local buffer_filename = ''
  59. os.remove(_USERHOME..'/unsaveddata.txt')
  60. if (#_BUFFERS > 0) then
  61. file = io.open(_USERHOME..'/unsaveddata.txt', "a")
  62. for j = 1, #_BUFFERS do
  63. if (_BUFFERS[j].notsaved == true) then
  64. if not _BUFFERS[j].filename then
  65. buffer_filename = ''
  66. else
  67. buffer_filename = _BUFFERS[j].filename
  68. end
  69. file:write('unsavedbuffer||'..buffer_filename..'||'..enc_base64(buffer.get_text(_BUFFERS[j]))..'||'..(_BUFFERS[j].current_pos), "\n")
  70. end
  71. end
  72. io.close(file)
  73. _UNSAVED_CHANGED = false
  74. end
  75. return false -- quit. returning true will not quit
  76. end
  77. events.connect(events.QUIT, function(str)
  78. textadept.session.save( _USERHOME..'/session' )
  79. save_unsaveddata(true)
  80. return false
  81. end, 1) -- adding 1 prevents the default quit event function to run
  82. -- save unsaved data after certain intervals
  83. timeout(10, function()
  84. save_unsaveddata()
  85. return true -- true means repeat
  86. end)
  87. function file_exists(file)
  88. local f = io.open(file, "rb")
  89. if f then f:close() end
  90. return f ~= nil
  91. end
  92. function split(pString, pPattern)
  93. local Table = {n = 0} -- NOTE: use {n = 0} in Lua-5.0
  94. local fpat = "(.-)" .. pPattern
  95. local last_end = 1
  96. local s, e, cap = pString:find(fpat, 1)
  97. while s do
  98. if s ~= 1 or cap ~= "" then
  99. table.insert(Table,cap)
  100. end
  101. last_end = e+1
  102. s, e, cap = pString:find(fpat, last_end)
  103. end
  104. if last_end <= #pString then
  105. cap = pString:sub(last_end)
  106. table.insert(Table, cap)
  107. end
  108. return Table
  109. end
  110. -- return buffer index after searching for filename
  111. function getbufferbyfilename(filename)
  112. for j = 1, #_BUFFERS do
  113. if (_BUFFERS[j].filename == filename) then
  114. return j
  115. end
  116. end
  117. return nil
  118. end
  119. function restore_unsaveddata()
  120. local unsaveddata_filename = _USERHOME..'/unsaveddata.txt'
  121. local line_data
  122. if not file_exists(unsaveddata_filename) then return {} end
  123. lines = {}
  124. for line in io.lines(unsaveddata_filename) do
  125. line_data = split(line, "||")
  126. local buffer_index = getbufferbyfilename(line_data[2])
  127. if buffer_index ~= nil then -- buffer already exists
  128. _mybuffer = _BUFFERS[buffer_index]
  129. else
  130. _mybuffer = buffer.new()
  131. end
  132. -- fix: set the text so that it can undo to original text
  133. -- when buffer has unsaved change
  134. buffer.set_text(_mybuffer, dec_base64(line_data[3]))
  135. if (line_data[2] ~= '') then
  136. _mybuffer.filename = line_data[2]
  137. end
  138. _mybuffer.notsaved = true
  139. buffer.goto_pos(_mybuffer, tonumber(line_data[4])) -- doesn't work properly for all buffers
  140. buffer.scroll_caret(_mybuffer)
  141. end
  142. end
  143. events.connect(events.INITIALIZED, function(str)
  144. restore_unsaveddata()
  145. end)
  146. return M