-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.lua
More file actions
307 lines (267 loc) · 9.79 KB
/
main.lua
File metadata and controls
307 lines (267 loc) · 9.79 KB
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
VERSION = "0.0.2"
local micro = import("micro")
local config = import("micro/config")
local shell = import("micro/shell")
local go_filepath = import("path/filepath")
---@type string
local plugName = "diff_preview" -- NOTE: same name as in repo.json
---@module 'tooltip'
local TooltipModule = nil
---@type Tooltip?
local diffPreview = nil
local function log(...)
-- micro.Log("["..plugName.."]", unpack(arg))
end
function init()
local plugDirPath = config.ConfigDir .. "/plug/?.lua;"
if package.path:find(plugDirPath, 1, true) == nil then
package.path = plugDirPath .. package.path
end
local ok, module = pcall(require, 'micro-diff-preview.tooltip')
if ok then -- Cloned as micro-diff-preview
TooltipModule = module
else -- Downloaded from Micro as diff_preview
TooltipModule = require(plugName .. ".tooltip")
end
config.MakeCommand("diff-preview", DiffPreview, config.NoComplete)
end
local function Error(msg, ...)
micro.InfoBar():Error(plugName .. ": " .. msg:format(unpack(arg)))
end
local function Message(msg, ...)
micro.InfoBar():Message(plugName .. ": " .. msg:format(unpack(arg)))
end
---@type table
local LineStatusStr = {
[0] = "Unchanged",
[1] = "Added",
[2] = "Modified",
[3] = "DeletedAbove"
}
---Counts the number of times that `substr` appears in `str`.
---@param str string
---@param substr string
function string.count(str, substr)
return select(2, str:gsub(substr, ""))
end
---Show the preview for the current hunk.
---@param bp BufPane
---@param _ userdata?
function DiffPreview(bp, _)
local buf = bp.Buf
local absPath = buf.AbsPath
-- NOTE: if is not saved on disk is not a file from a git repo
if buf.Path == "" then
Error("'%s' is not saved on the disk", absPath)
return
end
-- check the line consulted has changes
local line = bp.Cursor.Loc.Y
local lineEditor = line + 1 -- lines start at 0
local lineStatus = buf:DiffStatus(line)
if lineStatus == 0 then
Message("no change in line %d", lineEditor)
return
end
Message("line %d: %s", lineEditor, LineStatusStr[lineStatus])
-- calculate the begin of the hunk
local hunkBegin = 0
for i = line - 1, 0, -1 do
if buf:DiffStatus(i) ~= lineStatus then
hunkBegin = i + 1 -- previous line was the beginning of the hunk
break
end
end
-- NOTE: If is 'DSDeletedAbove(3)' we need to get the line above!
if lineStatus == 3 then hunkBegin = hunkBegin - 1 end
log("hunk_begin", hunkBegin)
-- resolve symlinks
local realPath, err = shell.RunCommand("realpath " .. absPath)
if err then
Error("could not get the realpath for '%s', %s", absPath, tostring(err))
return
end
-- Get the diff for the file (HEAD)
local diff
diff, err = shell.RunCommand(
("git -C %s diff --unified=0 HEAD -- %s"):format(go_filepath.Dir(realPath), realPath)
)
if err then
Error("could not get the diff for real path '%s', %s", realPath, tostring(err))
return
end
if diff == "" then Error("diff is empty"); return end
log("diff", diff)
--
-- Get the hunk from the diff
--
-- find hunk (NOTE: you have to check the last number, the current state)
local regex = "@@ %-%d+,?%d* %+" .. tostring(hunkBegin + 1) -- +1 translate to editor lines
local idx = diff:find(regex)
if not idx then
Error("hunk not found at line %d", hunkBegin + 1)
return
end
-- find end of line for the hunk label
idx = diff:find("\n", idx, true)
local begin = idx + 1 --skip newline
-- find begin next hunk (end of our hunk)
idx = diff:find("@@ -", idx, true)
local isLastHunk = idx == nil
local ending = not isLastHunk
and idx - 2 --step back first char of the label and the last newline
or nil -- end of diff
local hunk = diff:sub(begin, ending)
log(("hunk '%s'"):format(hunk))
--
-- Display the hunk in the tooltip
--
local maxHeight = hunk:count("\n")
log("maxHeight", maxHeight)
local padding = 6 --use an even number
local x, y, w, h = TooltipModule.FitAroundLocation(
bp, -bp.Cursor.Loc,
TooltipModule.Screen().Width - padding,
maxHeight + 2, --space for statusline, and the last newline removed in hunk
false--[[respectBufferBounds]]
)
diffPreview = TooltipModule.Tooltip.new(plugName, hunk, x, y, w, h, {
["ruler"] = false,
["filetype"] = "patch",
["softwrap"] = false,
["eofnewline"] = false,
["diffgutter"] = false,
["statusline"] = true,
["colorcolumn"] = 0,
["hltrailingws"] = false,
["diff"] = false,
})
end
-------------------------------------------------------------------------------
-- MICRO EVENT CALLBACKS
-- NOTE: This has been copied from `gutter_message` with some changes.
-------------------------------------------------------------------------------
---If the tooltip exists and is not closing, close it.
---@param from string The name of the caller function.
local function IfTooltipCloseIt(from)
log("IfTooltipCloseIt from: ", from)
if diffPreview and not diffPreview:IsClosing() then
diffPreview = diffPreview:Close()
end
end
---In case DiffPrevious is executed inside the tooltip we manually close the
---tooltip, go to the previous diff and execute the plugin again.
---@param bp BufPane
---@return boolean `false` to cancel the action, `true` to continue
function preDiffPrevious(bp)
if diffPreview and diffPreview:IsTooltip(bp) then
local origin = diffPreview.origin
IfTooltipCloseIt("preDiffPrevious")
if not origin:DiffPrevious() then
Message("No previous diff")
origin:CursorStart()
else
DiffPreview(origin)
end
return false
end
return true
end
---Same logic as `preDiffPrevious()`.
---@param bp BufPane
---@return boolean `false` to cancel the action, `true` to continue
function preDiffNext(bp)
if diffPreview and diffPreview:IsTooltip(bp) then
local origin = diffPreview.origin
IfTooltipCloseIt("preDiffNext")
if not origin:DiffNext() then
Message("No next diff")
origin:CursorEnd()
else
DiffPreview(origin)
end
return false
end
return true
end
---If we are quitting the tooltip's BufPane, we intercept Quit() and use Tooltip:Close().
---@param bp BufPane
---@return boolean `true` if the Quit action should proceed, `false` otherwise.
function preQuit(bp)
if not bp then return true end
if not diffPreview or diffPreview:IsClosing() then
return true -- Continue
end
if diffPreview:IsTooltip(bp) then
diffPreview = diffPreview:Close()
return false -- Cancel
end
return true
end
-- NOTE: MouseWheel*() do not have any effect.
-- NOTE: Mouse scroll does not work inside the tooltip. This is likely due to
-- the tree node and being over a BufPane with a higher "priority" index, which
-- is the one that receives the scroll events.
function onScrollUp(_) IfTooltipCloseIt("onScrollUp") end
function onScrollDown(_) IfTooltipCloseIt("onScrollDown") end
--Close the tooltip when entering Shell/Command mode (inside InfoBar) or ESC.
function onShellMode(_) IfTooltipCloseIt("onShellMode") end
function onCommandMode(_) IfTooltipCloseIt("onCommandMode") end
function onEscape(_) IfTooltipCloseIt("onEscape") end
---Close the tooltip before adding a Tab. It seems that I cannot catch the events
---with `onAnyEvent`. Do not reset the plugin; this will occur in other actions.
function preAddTab(_) IfTooltipCloseIt("preAddTab"); return true end
--Reset the plugin when the BufPane is changed (tabs or splits). If the buffer is
--replaced, we need to save it to run the linter, so the plugin will be reset as well.
function prePreviousTab(_) IfTooltipCloseIt("prePreviousTab") end
function preNextTab(_) IfTooltipCloseIt("preNextTab") end
function preNextSplit(_) IfTooltipCloseIt("preNextSplit") end
function prePreviousSplit(_) IfTooltipCloseIt("prePreviousSplit") end
function preUnsplit(_) IfTooltipCloseIt("preUnsplit") end
---NOTE: This is mandatory to handle; otherwise, the Vsplit will be created
---inside the Tooltip, resulting in an Hsplit. `onAnyEvent()` catches this too late.
---@param bp BufPane
function preVSplit(bp)
if diffPreview and diffPreview:IsTooltip(bp) then
IfTooltipCloseIt("preVSplit");
return false -- NOTE: true here crashes micro
end
return true
end
---@param bp BufPane
function preHSplit(bp)
if diffPreview and diffPreview:IsTooltip(bp) then
IfTooltipCloseIt("preHSplit");
return false -- NOTE: true here crashes micro
end
return true
end
---Reset the plugin when the buffer is opened and it is not a tooltip or a new one.
---@param buf Buffer
function onBufferOpen(buf)
local bufName = buf:GetName()
if bufName ~= plugName and bufName ~= "No name" then
IfTooltipCloseIt("onBufferOpen")
end
end
-- I can not detect `RunInteractiveShell` nor Screen termination...
-- but I can detect executed actions for `command:` and `command-edit:` with
-- pre()` because the name asigned to is an empty string ;)
-- NOTE: the redraw of the tooltip has beend remove because this is always
-- triggered when a command is called, so we cannot reuse anymore the BufPane
-- between calls.
function pre(_) IfTooltipCloseIt("pre") end
function preMousePress(_)
if diffPreview and not diffPreview:IsClosing() then
diffPreview = diffPreview:Close()
return false
end
return true
end
function preMouseMultiCursor(_)
if diffPreview and not diffPreview:IsClosing() then
diffPreview = diffPreview:Close()
return false
end
return true
end