aboutsummaryrefslogtreecommitdiff
path: root/data/plugins/autoreload.lua
blob: 49ef99234f36a9ef944a22ac9bb66b73c2384e2b (plain)
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
-- mod-version:4
local core = require "core"
local config = require "core.config"
local Doc = require "core.doc"
local Node = require "core.node"
local common = require "core.common"
local dirwatch = require "core.dirwatch"

config.plugins.autoreload = common.merge({
  always_show_nagview = false,
  config_spec = {
    name = "Autoreload",
    {
      label = "Always Show Nagview",
      description = "Alerts you if an opened file changes externally even if you haven't modified it.",
      path = "always_show_nagview",
      type = "toggle",
      default = false
    }
  }
}, config.plugins.autoreload)

local watch = dirwatch.new()
local times = setmetatable({}, { __mode = "k" })
local visible = setmetatable({}, { __mode = "k" })

local function update_time(doc)
  local info = system.get_file_info(doc.filename)
  times[doc] = info and info.modified
end

local function reload_doc(doc)
  doc:reload()
  update_time(doc)
  core.redraw = true
  core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename)
end


local timers = setmetatable({}, { __mode = "k" })

local function delayed_reload(doc, mtime)
  if timers[doc] then
    -- If mtime remains the same, there's no need to restart the timer
    -- as we're waiting a full second anyways.
    if not mtime or timers[doc].mtime ~= mtime then
      timers[doc] = { last_trigger = system.get_time(), mtime = mtime }
    end
    return
  end

  timers[doc] = { last_trigger = system.get_time(), mtime = mtime }
  core.add_thread(function()
    local diff = system.get_time() - timers[doc].last_trigger
    -- Wait a second before triggering a reload because we're using mtime
    -- to determine if a file has changed, and on many systems it has a
    -- resolution of 1 second.
    while diff < 1 do
      coroutine.yield(diff)
      diff = system.get_time() - timers[doc].last_trigger
    end
    timers[doc] = nil
    reload_doc(doc)
  end)
end

local function check_prompt_reload(doc)
  if doc and doc.deferred_reload then
    core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", {
      { font = style.font, text = "Yes", default_yes = true },
      { font = style.font, text = "No" , default_no = true }
    }, function(item)
      if item.text == "Yes" then reload_doc(doc) end
      doc.deferred_reload = false
    end)
  end
end

local function doc_changes_visiblity(doc, visibility)
  if doc and visible[doc] ~= visibility and doc.abs_filename then
    visible[doc] = visibility
    if visibility then check_prompt_reload(doc) end
    watch:watch(doc.abs_filename, visibility)
  end
end

local core_set_active_view = core.set_active_view
function core.set_active_view(view)
  core_set_active_view(view)
  doc_changes_visiblity(view.doc, true)
end

local node_set_active_view = Node.set_active_view
function Node:set_active_view(view)
  if self.active_view then doc_changes_visiblity(self.active_view.doc, false) end
  node_set_active_view(self, view)
  doc_changes_visiblity(self.active_view.doc, true)
end

core.add_thread(function()
  while true do
    watch:check(function(file)
      for i, doc in ipairs(core.docs) do
        if doc.abs_filename == file then
          local info = system.get_file_info(doc.filename or "")
          if info and times[doc] ~= info.modified then
            if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then
              reload_doc(doc)
            else
              doc.deferred_reload = true
              if doc == core.active_view.doc then check_prompt_reload(doc) end
            end
          end
        end
      end
    end)
    coroutine.yield(0.05)
  end
end)

-- patch `Doc.save|load` to store modified time
local load = Doc.load
local save = Doc.save

Doc.load = function(self, ...)
  local res = load(self, ...)
  update_time(self)
  return res
end

Doc.save = function(self, ...)
  local res = save(self, ...)
  -- if starting with an unsaved document with a filename.
  if not times[self] then watch:watch(self.abs_filename, true) end
  update_time(self)
  return res
end