/
visual_compare.lua
162 lines (141 loc) · 4.82 KB
/
visual_compare.lua
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
-- Started: 2017-08-02
-- Portable Far3/far2m: 2023-12-22
-- The algorithm for choosing a file pair for visual comparison (in descending priorities):
-- 1. Active panel: 2 selected
-- 2. Active panel: 1 selected; passive panel: 1 selected
-- 3. Active panel: 1 selected; passive panel: the same name
-- 4. Active panel: has current; passive panel: the same name
local SLASH = string.sub(package.config,1,1)
local FAR3 = SLASH=="\\"
local CommonKey = FAR3 and "CtrlAltF2" or "CtrlShiftF2"
local Modes = { -- uncomment those modes you will actually use
meld = not FAR3;
winmerge = FAR3;
-- diff_console = true;
-- diff_edit = true;
-- diff_view = true;
}
local F = far.Flags
local FarCmdsId = FAR3 and "3A2AF458-43E2-4715-AFEA-93D33D56C0C2" or far.GetPluginId()
local function GetPanelDirectory(pan)
if FAR3 then return panel.GetPanelDirectory(nil,pan).Name
else return panel.GetPanelDirectory(nil,pan)
end
end
local function isfile(item) return not item.FileAttributes:find("d") end
local function isselected(item) return bit64.band(item.Flags,F.PPIF_SELECTED) ~= 0 end
local function extract_name(s)
if FAR3 then return s:match("[^\\]+$")
else return s:match("[^/]+$")
end
end
local function fexist(file)
local attr = win.GetFileAttr(file)
return attr and not attr:find("d")
end
local function join(dir, file)
return dir=="" and file
or dir:find(SLASH.."$") and dir..file
or dir..SLASH..file
end
local function fullname(dir, file)
return file:find(SLASH) and file or join(dir, file)
end
-- collect items, skip directories
local function Collect(whatPanel, limit)
local selTable = {}
local aInfo = panel.GetPanelInfo(nil,whatPanel)
for k=1,aInfo.SelectedItemsNumber do
local item = panel.GetSelectedPanelItem(nil,whatPanel,k)
if isfile(item) and isselected(item) then
if #selTable < limit then
table.insert(selTable,item)
else
selTable = {}; break -- too many selected items, discard all
end
end
end
local item = panel.GetCurrentPanelItem(nil, whatPanel)
if isfile(item) then selTable.Current=item; end
return selTable
end
local function GetPairForCompare()
local ACT,PSV = 1,0 -- active and passive panels
local dirActive = GetPanelDirectory(ACT)
local dirPassive = GetPanelDirectory(PSV)
local trgActive, trgPassive
--------------------------------------------------------------------------------------------------
local selAct, selPass = Collect(ACT,2), Collect(PSV,1)
if #selAct == 2 then
trgActive = fullname(dirActive, selAct[1].FileName)
trgPassive = fullname(dirActive, selAct[2].FileName)
elseif #selAct == 1 and #selPass == 1 then
trgActive = fullname(dirActive, selAct[1].FileName)
trgPassive = fullname(dirPassive, selPass[1].FileName)
elseif next(selAct) and dirPassive ~= "" then
local name1 = (selAct[1] or selAct.Current).FileName
local fullname2 = join(dirPassive, extract_name(name1))
if fexist(fullname2) then
trgActive = fullname(dirActive, name1)
trgPassive = fullname2
end
end
return trgActive, trgPassive
end
local function Run(trgActive, trgPassive)
-- prepare the menu
local items = {}
for k,v in pairs(Modes) do
if v then table.insert(items, {Val=k}) end
end
local mode
if items[1] == nil then
far.Message("No mode is specified", "Error", nil, "w")
return
elseif items[2] == nil then
mode = items[1].Val -- only 1 mode available; no menu needed
else
table.sort(items, function(a,b) return a.Val < b.Val; end)
for i,v in ipairs(items) do
v.text = ("&%d. %s"):format(i, v.Val)
end
local item = far.Menu({Title="Select compare mode"},items)
if item then mode = item.Val
else return
end
end
if mode == "meld" then
local command = ("meld %q %q &"):format(trgActive,trgPassive)
os.execute(command)
elseif mode == "winmerge" then
local command = ("%q %q"):format(trgActive,trgPassive)
win.ShellExecute(nil, nil, "winmerge", command)
elseif mode == "diff_console" then
local command = ("diff -u %q %q"):format(trgActive,trgPassive)
if FAR3 then
panel.GetUserScreen(); win.system(command); panel.SetUserScreen()
else
far.Execute(command)
end
far.AdvControl("ACTL_REDRAWALL")
Keys("CtrlO")
elseif mode == "diff_edit" then
local command = ("diff -u %q %q"):format(trgActive,trgPassive)
Plugin.Command(FarCmdsId, "edit:<"..command)
elseif mode == "diff_view" then
local command = ("diff -u %q %q"):format(trgActive,trgPassive)
Plugin.Command(FarCmdsId, "view:<"..command)
end
end
Macro {
description="Visual compare";
area="Shell"; key=CommonKey;
action=function()
local file1,file2 = GetPairForCompare()
if file1 then
Run(file1,file2)
else
far.Message("No suitable file pair found", "Visual compare", nil, "w")
end
end;
}