aboutsummaryrefslogtreecommitdiff
path: root/init.lua
blob: af9639956016084020bd93dd3e6450c8d01d04c0 (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
138
139
140
141
-- Copyright (c) 2024 Florian Fischer. All rights reserved.
--
-- vis-lockfiles is free software: you can redistribute it and/or modify it under
-- the terms of the GNU General Public License as published by the Free Software
-- Foundation, either version 3 of the License, or (at your option) any later
-- version.
--
-- vis-lockfiles is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- vis-lockfiles found in the LICENSE file.
-- If not, see <https://www.gnu.org/licenses/>.
local source_str = debug.getinfo(1, 'S').source:sub(2)
local script_path = source_str:match('(.*/)')

local lockfiles = dofile(script_path .. 'lockfiles.lua')

local M = {}
M.directory = '${XDG_STATE_HOME:-${HOME}/.local/state}/vis/locks'
M.directory_expanded = nil
-- Enable this if vis supports changing a window's file from Lua
M.change_file_on_abort = false

-- Detect available backends
if os.execute('command -v whiptail') then
  M.prompt_backend = 'whiptail'
else
  M.prompt_backend = 'vis-menu'
end

local function get_lock_dir()
  if M.directory_expanded ~= M.directory then
    M.directory = lockfiles.capture_cmd('printf %s "' .. M.directory .. '"')
    M.directory_expanded = M.directory
  end
  return M.directory
end

local function prompt_backend_whiptail(msg, menu)
  local cmd = 'whiptail </dev/tty 2>&1 >/dev/tty --title "vis-lockfiles" --nocancel'
  return vis:pipe(cmd .. ' --menu \'' .. msg .. '\n\' 0 0 0 ' .. menu, true)
end

local function prompt_backend_vis_menu(msg, menu, choices)
  local cmd1 = 'printf >/dev/tty %s \'' .. msg .. '\n\''
  local cmd2 = 'printf %s \'' .. choices .. '\''
  local cmd3 = 'vis-menu -b -p \'' .. menu .. '\''
  return vis:pipe(cmd1 .. ';' .. cmd2 .. '|' .. cmd3, true)
end

local function prompt_backend(path, lfile_path, pid, backend)
  local msg = 'File is locked: ' .. path .. '\n  lock: ' .. lfile_path ..
                  '\n  owned by process ID: ' .. pid
  local status, output

  if backend == 'whiptail' then
    local menu = 'abort "operation (a)" edit "anyway (e)" quit "vis (q)"'
    status, output = prompt_backend_whiptail(msg, menu)
  elseif type(backend) == 'function' then
    status, output = backend(path, lfile_path, pid)
  else
    status, output = prompt_backend_vis_menu(msg, '(E)dit anyway, (Q)uit, [A]bort', 'a\ne\nq')
  end
  vis:redraw()

  return (status == 0) and output:sub(1, 1)
end

local cur_file
vis.events.subscribe(vis.events.FILE_OPEN, function()
  -- remember the current file to reopen it when aborting an edit attempt
  cur_file = vis.win and vis.win.file and (vis.win.file.path or vis.win.file.name)
end)

-- get a user confirmation
-- return true if user wants to edit the locked file
local function prompt(path, lfile_path, pid)

  local output = prompt_backend(path, lfile_path, pid, M.backend)
  if output == 'e' then
    return true
  elseif output == 'q' then
    vis:exit(1)
  end
end

local created_locks = {}

vis.events.subscribe(vis.events.WIN_OPEN, function(win)
  local file = win.file
  if created_locks[file.path] or not file.path then
    -- either a noname buffer, or already locked by us
    return
  end

  local lfile_path = lockfiles.lock_file_path(get_lock_dir(), file.path)
  local lock_pid = lockfiles.create_lock_file(get_lock_dir(), lfile_path)
  if lockfiles.vis_pid == lock_pid then
    created_locks[file.path] = lfile_path
  else
    if not prompt(file.path, lfile_path, lock_pid) then
      -- 'abort' or dialog canceled
      if cur_file and M.change_file_on_abort then
        -- restore the previous file
        win.file = cur_file
        -- fire a new WIN_OPEN event because we changed the file
        vis.events.emit(vis.events.WIN_OPEN, win)
      else
        vis:command('q')
        vis:command('new')
      end
      -- stop handling the old FILE_OPEN event either we closed it
      -- or changed its file and emitted a new WIN_OPEN event
      return true
    end
  end
end)

vis.events.subscribe(vis.events.FILE_CLOSE, function(file)
  if not file.path then
    return
  end
  local lfile_path = created_locks[file.path]
  if lfile_path then
    lockfiles.delete_lock_file(lfile_path)
    created_locks[file.path] = nil
  end
end)

vis.events.subscribe(vis.events.QUIT, function()
  for _, lfile_path in pairs(created_locks) do
    if lfile_path then
      lockfiles.delete_lock_file(lfile_path)
    end
  end
  created_locks = {}
end)

return M