local Shared = ... local Msg = Shared.Msg local ffi = require "ffi" local C = ffi.C local F = far.Flags local PPIF_SELECTED = F.PPIF_SELECTED local FILE_ATTRIBUTE_DIRECTORY = 0x00000010 local MCODE_F_SETCUSTOMSORTMODE = 0x80C67 local band, bor = bit.band, bit.bor -- 32 bits, be careful local tonumber = tonumber local CustomSortModes = {} -- key=integer, value=table local CanChangeSortMode = true -- shellsort [ http://lua-users.org/wiki/LuaSorting ] -- Written by Rici Lake. The author disclaims all copyright and offers no warranty. -- For convenience, shellsort returns its first argument. local incs = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 } local function shellsort(t, n, before) for _, h in ipairs(incs) do for i = h, n - 1 do local v = t[i] for j = i - h, 0, -h do local testval = t[j] if not before(v, testval) then break end t[i] = testval i = j end t[i] = v end end return t end -- qsort [ extracted from Lua 5.1 distribution (file /test/sort.lua) ] local function qsort(x,l,u,f) local function recurse(l,u) if l=100 and nMode<=0x7FFFFFFF) if type(Settings) == "table" then local t = {} if type(Settings.Compare) == "function" then t.Compare=Settings.Compare end local cond = Settings.Condition or Settings.condition -- allow lower case for 'Condition' if type(cond) == "function" then t.Condition=cond end if not (t.Compare or t.Condition) then CustomSortModes[nMode] = nil return end if type(Settings.InitSort) == "function" then t.InitSort=Settings.InitSort end if type(Settings.EndSort) == "function" then t.EndSort=Settings.EndSort end if type(Settings.SortFunction) == "string" then t.SortFunction=Settings.SortFunction end if type(Settings.Description) == "string" then t.Description=Settings.Description end if type(Settings.Indicator) == "string" then t.Indicator=(Settings.Indicator.." "):sub(1,2) else t.Indicator = " " end for _,v in ipairs(BooleanProperties) do t[v] = not not Settings[v] end for _,v in ipairs(TernaryProperties) do t[v] = Settings[v]==0 and 0 or Settings[v]==1 and 1 or 2 end CustomSortModes[nMode] = t else CustomSortModes[nMode] = nil end end local function CanDoPanelSort (SortMode) local tSettings = CustomSortModes[SortMode] if tSettings then if tSettings.Condition then CanChangeSortMode = false local ok, ret = pcall(tSettings.Condition, SortMode) CanChangeSortMode = true if not ok then Shared.ErrMsg(ret); return; end if not ret then return; end tSettings = CustomSortModes[SortMode] -- retrieve again as it may be reloaded by Condition() return tSettings and tSettings.Compare and true else return true end end end local function SetCustomSortMode (nMode, whatpanel, order) if CanChangeSortMode then if CanDoPanelSort(nMode) then if order=="auto" then order=0 elseif order=="current" then order=1 elseif order=="direct" then order=2 elseif order=="reverse" then order=3 else order = 0 end local Settings = CustomSortModes[nMode] whatpanel = whatpanel==1 and 1 or 0 Shared.MacroCallFar(MCODE_F_SETCUSTOMSORTMODE, whatpanel, nMode, Settings.InvertByDefault, order) end end end local function CustomSortMenu() local Id = win.Uuid("C323FBCF-6803-4F2C-B8B4-E576E7F125DC") local items, bkeys = {}, { { BreakKey="C+RETURN", order="auto" }, { BreakKey="CS+RETURN", order="auto" }, { BreakKey="ADD", order="direct" }, { BreakKey="C+ADD", order="direct" }, { BreakKey="CS+ADD", order="direct" }, { BreakKey="SUBTRACT", order="reverse" }, { BreakKey="C+SUBTRACT", order="reverse" }, { BreakKey="CS+SUBTRACT", order="reverse" }, } local pinfo = panel.GetPanelInfo(nil,1) for k,v in pairs(CustomSortModes) do local item = { text=v.Description and tostring(v.Description) or Msg.PSDefaultMenuItemText..k; Mode=k; } if pinfo.SortMode == k then item.selected = true item.checked = band(pinfo.Flags,F.PFLAGS_REVERSESORTORDER)==0 and "+" or "-" end items[#items+1] = item end table.sort(items, function(a,b) return a.Mode < b.Mode end) local r, pos = far.Menu({Title=Msg.PSMenuTitle, Id=Id}, items, bkeys) if r and (pos > 0) then local apanel = not r.BreakKey or r.BreakKey:find("^CS%+") or not r.BreakKey:find("%+") local ppanel = r.BreakKey and r.BreakKey:find("^CS?%+") if ppanel then SetCustomSortMode(items[pos].Mode, 1, r.order) end if apanel then SetCustomSortMode(items[pos].Mode, 0, r.order) end end end local function GetSortModes() local t = {} for mode in pairs(CustomSortModes) do t[#t+1]=mode; t[#t+1]=mode; t[#t+1]=mode; end table.sort(t) for k=1,#t,3 do local mode = t[k] t[k+1] = CustomSortModes[mode].InvertByDefault t[k+2] = CustomSortModes[mode].Description or Msg.PSDefaultMenuItemText..mode end t.n = #t return t end ffi.cdef[[ typedef struct { unsigned int *Positions; void *Items; size_t ItemsCount; void (*FileListToPluginItem)(const void*, int, struct SortingPanelItem*); int SortGroups; int SelectedFirst; int DirectoriesFirst; int SortMode; int RevertSorting; int NumericSort; int CaseSensitiveSort; HANDLE hSortPlugin; } CustomSort; ]] -- local function Utf16Buf (str) -- str = win.Utf8ToUtf16(str) -- local buf = ffi.new("wchar_t[?]", #str/2+1) -- ffi.copy(buf, str, #str) -- return buf -- end -- local function wcscmp(p1,p2) -- local pos = 0 -- while p1[pos] == p2[pos] do -- if p1[pos] == 0 then return 0 end -- pos = pos + 1 -- end -- return p1[pos] - p2[pos] -- end local function IsTwoDots(p) return p[0]==46 and p[1]==46 and p[2]==0 end local function Empty(p) return p[0]==0 end -- called from Far local function SortPanelItems (params) jit.flush() params = ffi.cast("CustomSort*", params) local tSettings = CustomSortModes[tonumber(params.SortMode)] if not (tSettings and tSettings.Compare) then return end local Compare = tSettings.Compare local SortEqualsByName = not tSettings.NoSortEqualsByName local pi1 = ffi.new("struct SortingPanelItem") local pi2 = ffi.new("struct SortingPanelItem") local SortGroups, SelectedFirst, DirectoriesFirst, RevertSorting if tSettings.SortGroups==0 then SortGroups=false elseif tSettings.SortGroups==1 then SortGroups=true else SortGroups = params.SortGroups ~= 0 end if tSettings.SelectedFirst==0 then SelectedFirst=false elseif tSettings.SelectedFirst==1 then SelectedFirst=true else SelectedFirst = params.SelectedFirst ~= 0 end if tSettings.DirectoriesFirst==0 then DirectoriesFirst=false elseif tSettings.DirectoriesFirst==1 then DirectoriesFirst=true else DirectoriesFirst = params.DirectoriesFirst ~= 0 end if tSettings.RevertSorting==0 then RevertSorting=false elseif tSettings.RevertSorting==1 then RevertSorting=true else RevertSorting = params.RevertSorting ~= 0 end local outParams = { SortGroups = params.SortGroups ~= 0; SelectedFirst = params.SelectedFirst ~= 0; DirectoriesFirst = params.DirectoriesFirst ~= 0; RevertSorting = params.RevertSorting ~= 0; NumericSort = params.NumericSort ~= 0; CaseSensitiveSort = params.CaseSensitiveSort ~= 0; } local function Before(n1, n2) params.FileListToPluginItem(params.Items, n1, pi1) params.FileListToPluginItem(params.Items, n2, pi2) ---------------------------------------------------------------------------- -- TODO: fix in Far if IsTwoDots(pi1.FileName) then if not IsTwoDots(pi2.FileName) then return true end if Empty(pi1.AlternateFileName) or IsTwoDots(pi1.AlternateFileName) then if not (Empty(pi2.AlternateFileName) or IsTwoDots(pi2.AlternateFileName)) then return true end else if Empty(pi2.AlternateFileName) or IsTwoDots(pi2.AlternateFileName) then return false end end elseif IsTwoDots(pi2.FileName) then return false end ---------------------------------------------------------------------------- if DirectoriesFirst then local a1,a2 = band(tonumber(pi1.FileAttributes), FILE_ATTRIBUTE_DIRECTORY), band(tonumber(pi2.FileAttributes), FILE_ATTRIBUTE_DIRECTORY) if a1 ~= a2 then return a1 > a2 end end ---------------------------------------------------------------------------- if SelectedFirst then local s1,s2 = band(tonumber(pi1.Flags),PPIF_SELECTED), band(tonumber(pi2.Flags),PPIF_SELECTED) if s1 ~= s2 then return s1 > s2 end end ---------------------------------------------------------------------------- if SortGroups then if pi1.SortGroup ~= pi2.SortGroup then return pi1.SortGroup < pi2.SortGroup end end ---------------------------------------------------------------------------- local r = Compare(pi1, pi2, outParams) if r ~= 0 then if RevertSorting then r = -r end return r < 0 else if SortEqualsByName then return 1 == C.CompareStringW(C.LOCALE_USER_DEFAULT, bor(C.NORM_IGNORECASE,C.SORT_STRINGSORT), pi1.FileName, -1, pi2.FileName, -1) end end return false end if tSettings.InitSort then tSettings.InitSort(outParams) end if tSettings.SortFunction == "qsort" then qsort(params.Positions, 0, tonumber(params.ItemsCount)-1, Before) else -- default shellsort(params.Positions, tonumber(params.ItemsCount), Before) end if tSettings.EndSort then tSettings.EndSort() end return tSettings.Indicator end return { SortPanelItems=SortPanelItems, LoadCustomSortMode=LoadCustomSortMode, SetCustomSortMode=SetCustomSortMode, CustomSortMenu=CustomSortMenu, GetSortModes=GetSortModes, DeleteSortModes=function() CustomSortModes={} end, CanDoPanelSort=CanDoPanelSort, }