Last active
May 21, 2023 22:33
-
-
Save johnd0e/a3237d43a908ff78e496b20fbe6a7c68 to your computer and use it in GitHub Desktop.
[FAR macro] IncSearch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local Info = package.loaded.regscript or function(...) return ... end | |
local nfo = Info {..., | |
name = "IncSearch"; | |
description = "Incremental search in editor"; | |
version = "2.16"; | |
author = "jd"; | |
url = "http://forum.farmanager.com/viewtopic.php?f=15&t=8802"; | |
id = "BAABBEC4-A9B1-49EF-9C54-63DED1F7A47B"; | |
minfarversion = {3,0,0,6096,0}; | |
options = { | |
Alt = "RAlt", -- supported: LAlt | RAlt | CtrlRAlt (AltGr) | |
useLFSearch = true, | |
useEdtFind = true, | |
useBM = false, | |
HighlightMinLen = 2, | |
keepHighlight = true, | |
center = false, | |
ExtraQuick = true, | |
msgdelay = 300, | |
optionsEF = "CaseSensitive:0 Highlight:1 RegExp:0 WholeWords:0 Loop:1", | |
options = { | |
CaseSensitive = 0; | |
Reverse = 0; | |
RegExp = 0; | |
WholeWords = 0; | |
Loop = 1 | |
} | |
}; | |
} | |
if not nfo then return end | |
local O = nfo.options | |
local F = far.Flags | |
-- globals -- | |
local SearchMode, str, notfind | |
local last, appendable = "" | |
local OriginalPos,OriginalSel | |
local BS | |
-- -- -- -- -- | |
local srhInit,srhFin,srhStart,srhFwd,srhBwd,srhToggleOption,srhFindAll | |
local function SetSelection(sel) | |
editor.Select(nil,sel and { | |
BlockType = F.BTYPE_STREAM, | |
BlockStartPos = sel.StartPos, | |
BlockWidth = sel.EndPos-sel.StartPos+1, | |
BlockStartLine = sel.StartLine, | |
BlockHeight = sel.EndLine-sel.StartLine+1, | |
} or F.BTYPE_NONE) | |
end | |
local function MessagePopup(msg,flags) | |
local s = far.SaveScreen() | |
far.Message(msg,nfo.name,"",flags) | |
win.Sleep(O.msgdelay or 500); far.RestoreScreen(s) | |
far.Text() | |
end | |
local lfsGuid = "8E11EA75-0303-4374-AC60-D1E38F865449" | |
local function LFSearch(...) | |
return Plugin.SyncCall(lfsGuid,"code",...) | |
end | |
local function EdtFind(...) | |
return Plugin.SyncCall("E4ABD267-C2F9-4158-818F-B0E040A2AB9F",...) | |
end | |
local function search(f,str,optf,dir) | |
local pos_old = editor.GetInfo().CurPos -- in order to restore if nothing found | |
local pos = editor.GetString().SelStart | |
editor.SetPosition(nil,0,pos) | |
--EdtFind"Highlight:0"--!! | |
if str=="" then | |
--editor.SetPosition(nil,0,pos) | |
--editor.Select(nil,F.BTYPE_NONE) | |
--far.Text() | |
f(str,optf) | |
return | |
end | |
local sel = editor.GetSelection() | |
Keys(dir) | |
local found = f(str,optf) | |
appendable = found | |
if not found then | |
mf.beep() | |
win.Sleep(200) | |
SetSelection(sel) --restore selection | |
editor.SetPosition(nil,0,pos_old) -- restore | |
else--if false then | |
------------------------------- | |
local ei = editor.GetInfo() | |
if O.center then | |
ei.LeftPos = ei.CurTabPos - ei.WindowSizeX/2 | |
ei.TopScreenLine = ei.CurLine - ei.WindowSizeY/2 | |
editor.SetPosition(nil,ei) | |
else | |
--todo ??TopScreenPos | |
local si = editor.GetString() | |
local word = si.StringText:match("%w+",si.SelEnd) or "" | |
local LeftPos = si.SelEnd + word:len() - ei.WindowSizeX | |
if LeftPos>ei.LeftPos then | |
ei.LeftPos = LeftPos | |
editor.SetPosition(nil,ei) | |
end | |
end | |
if O.ExtraQuick then --save pos in order to enable back step | |
ei.sel = editor.GetSelection() | |
BS[str] = ei | |
end | |
end | |
editor.Redraw() | |
far.Text() | |
end | |
if O.useLFSearch and LFSearch("return true") then | |
local srhCode = [[ | |
local Operation, sSearchPat, bSearchBack, bCaseSens, bRegExpr, bWholeWords, bHighlight, bWrapAround, sOrigin = ... | |
return 0~=lfsearch.EditorAction(Operation, { | |
sSearchPat=sSearchPat, | |
bSearchBack=bSearchBack, | |
bCaseSens=bCaseSens, | |
bRegExpr=bRegExpr, | |
bWholeWords=bWholeWords, | |
bHighlight=bHighlight, | |
bWrapAround=bWrapAround, | |
sOrigin=sOrigin, | |
}, true) | |
]] | |
srhInit = function() end | |
srhFin = function(keephighlight) | |
if not keephighlight then | |
Plugin.Call(lfsGuid, "own", "editor", "resethighlight") | |
end | |
end | |
srhStart = function(str,back) | |
local o = O.options | |
return LFSearch(srhCode,"test:search",str,back, | |
o.CaseSensitive==1, | |
o.RegExp==1, | |
o.WholeWords==1, | |
str:len()>=O.HighlightMinLen, | |
o.Loop==1 and 1) | |
end | |
srhFwd = function(str) return srhStart(str) end | |
srhBwd = function(str) return srhStart(str,"back") end | |
srhToggleOption = function(opt) | |
O.options[opt] = 1-O.options[opt] | |
MessagePopup(opt..":"..O.options[opt]) | |
return "" | |
end | |
srhFindAll = function() | |
local o = O.options | |
LFSearch(srhCode,"test:showall",str,nil, | |
o.CaseSensitive==1, | |
o.RegExp==1, | |
o.WholeWords==1, | |
true, | |
nil, | |
"scope") | |
end | |
elseif O.useEdtFind and EdtFind("") then | |
srhInit = function() EdtFind"Highlight:0" end | |
srhFin = function(keephighlight) | |
if not keephighlight then EdtFind"Highlight:0" end | |
end | |
srhStart = function(str,extra) | |
return EdtFind(('%s %s %s Error:0 Find:"%s"'):format( | |
O.optionsEF, | |
extra or "", | |
str:len()<O.HighlightMinLen and "Highlight:0" or "", | |
str:gsub('"','""'))) | |
end | |
srhFwd = function(str) return srhStart(str) end | |
srhBwd = function(str) return srhStart(str,"Reverse:1 ") end | |
srhToggleOption = function(opt) | |
O.optionsEF = O.optionsEF:gsub(("(%s:)(%%d)"):format(opt),function(opt,value) | |
local new = opt..(1-value) | |
MessagePopup(new) | |
return new | |
end,1) | |
return "" | |
end | |
srhFindAll = function() | |
EdtFind(('%s Highlight:1 Error:0 Grep %s'):format(O.optionsEF,str)) | |
end | |
else ------------------------- | |
local optpos = { | |
CaseSensitive = 12; | |
WholeWords = 13; | |
Reverse = 14; | |
RegExp = 15; | |
} | |
local SearchSelFound = 17 | |
local SelFoundInitState | |
srhInit = function() | |
--save init state of [x] Select found and turn it on | |
SelFoundInitState = Editor.Set(SearchSelFound,1) | |
end | |
srhFin = function() | |
Editor.Set(SearchSelFound,SelFoundInitState) --restore init state of [x] Select found | |
if SelFoundInitState==0 then editor.Select(nil,{BlockStartPos=0}) end | |
end | |
srhStart = function(str,optf) | |
Far.DisableHistory(-1) | |
Keys"F7 CtrlY"; mf.print(str) | |
if optf then | |
optf() | |
else | |
for opt,pos in pairs(optpos) do | |
if Dlg.SetFocus(pos)==-1 then | |
MessagePopup("Wrong position for '"..opt.."': "..pos,"w") | |
mf.beep() | |
elseif Dlg.GetValue()~=O.options[opt] then | |
Keys"Space" | |
end | |
end | |
end | |
Keys"Enter" | |
if not Area.Editor then | |
far.Text(); Keys"Esc" | |
notfind = str | |
else | |
notfind = false | |
return true | |
end | |
end | |
local function searchNext(key)--bug:http://forum.farmanager.com/viewtopic.php?p=143571#p143571 | |
Keys(key) | |
if not Area.Editor then far.Text(); Keys"Esc" else return true end | |
end | |
srhFwd = function(str) | |
if notfind and str~=notfind then return srhStart(str) end | |
return searchNext"ShiftF7" | |
end | |
srhBwd = function() return searchNext"AltF7" end | |
srhToggleOption = function(opt) | |
if optpos[opt] then | |
O.options[opt] = 1-O.options[opt] | |
MessagePopup(opt..":"..O.options[opt]) | |
else | |
MessagePopup("Wrong option: "..opt,"w") | |
mf.beep() | |
end | |
return "" | |
end | |
srhFindAll = function() | |
Far.DisableHistory(-1) | |
Keys"F7 PgDn Right Enter" | |
mf.mmode(1,0) | |
end | |
end | |
local xform = { --http://bugs.farmanager.com/view.php?id=2851 | |
Tab =" "; | |
Space =" "; | |
Divide ="/"; | |
Multiply="*"; | |
Subtract="-"; | |
Add ="+"; | |
Decimal ="."; | |
BackSlash="\\"; | |
["Shift`"]="~"; | |
["Shift1"]="!"; | |
["Shift2"]="@"; | |
["Shift3"]="#"; | |
["Shift4"]="$"; | |
["Shift5"]="%"; | |
["Shift6"]="^"; | |
["Shift7"]="&"; | |
["Shift8"]="*"; | |
["Shift9"]="("; | |
["Shift0"]=")"; | |
["Shift-"]="_"; | |
["Shift="]="+"; | |
["ShiftBackSlash"]="|"; | |
["Shift["]="{"; | |
["Shift]"]="}"; | |
["Shift;"]=":"; | |
["Shift'"]="\""; | |
["Shift,"]="<"; | |
["Shift."]=">"; | |
["Shift/"]="?"; | |
-- | |
-- ="BS"; --backspace/restore | |
-- ="CtrlBS"; --clear/restore | |
CtrlV ="ShiftIns"; --insert | |
-- ="Esc"; --quit and restore pos | |
RAlt ="Alt"; --quit | |
-- CtrlI ="Alt"; --quit | |
F3 ="ShiftF7"; --next | |
Enter ="ShiftF7"; | |
RCtrl ="ShiftF7"; | |
ShiftF3 ="AltF7"; --prev | |
ShiftEnter ="AltF7"; | |
Ctrl ="AltF7"; | |
RCtrlBS ="CtrlBS"; | |
RAltRight="AltRight"; | |
RCtrlRAlt="ShiftF7"; --modal RCtrl | |
CtrlRAlt="AltF7"; --modal Ctrl | |
} | |
local function setStatus(str) | |
editor.SetTitle(nil,str and " ↓"..str:gsub(" ","·") or nil) | |
end | |
local function pickSel() | |
local s1 = editor.GetString(nil,0,0) | |
if s1.SelStart>0 then | |
local sel = OriginalSel | |
if sel.StartLine==sel.EndLine then | |
return s1.StringText:sub(s1.SelStart,s1.SelEnd) | |
end | |
end | |
end | |
local function enterSearchMode(arg) | |
srhInit() | |
if O.useBM then BM.Add() end | |
OriginalPos,OriginalSel = editor.GetInfo(),editor.GetSelection() --todo | |
SearchMode = arg.mode or true; appendable = true | |
local picked | |
if arg.str then | |
str = arg.str; | |
else | |
picked = pickSel() --single-line selection only | |
str = picked | |
end | |
if not picked then editor.Select(nil,{BlockStartPos=0}) end | |
BS = {[""] = OriginalPos} | |
if str then | |
search(srhStart,str,nil,arg.dir) | |
if arg.cmd then arg.cmd() end --todo | |
else | |
str = "" | |
end | |
setStatus(str) | |
end | |
local function quitSearchMode(arg) | |
SearchMode = false | |
if str~="" then last = str end | |
setStatus() | |
srhFin(O.keepHighlight or arg.keepHighlight) | |
if arg.restorePos then | |
editor.SetPosition(nil,OriginalPos) | |
SetSelection(OriginalSel) | |
end | |
end | |
---------------------------------------------- | |
local function append(text) --,bwd)--?? | |
str = appendable and str..text or str | |
search(srhStart, str, nil, O.ExtraQuick and appendable and text~="" and "Right") | |
setStatus(str) | |
end | |
local function strIsEmpty() -- if str is empty - trying to use last | |
if str=="" then | |
if last=="" then return true end | |
str = last | |
setStatus(str) | |
end | |
end | |
local actions = { | |
--todo http://forum.farmanager.com/viewtopic.php?p=131745#p131745 | |
--CtrlW=function() return append"\\w" end;--todo regexp | |
--CtrlS=function() return append"\\s" end;--todo regexp | |
CtrlBackSlash=function() --todo regexp | |
mf.postmacro(Keys,"End") | |
str = far.InputBox(nil,"",nil,"IncSearch",str) or str | |
return "" | |
end; | |
--skip | |
Shift=function() Keys"Shift" end; | |
--backspace/restore | |
BS=function() | |
if str:len()==0 then return last end | |
str = str:sub(1,-2) | |
appendable = true | |
if O.ExtraQuick then | |
setStatus(str) | |
local ei = BS[str] --back step | |
if ei then | |
editor.SetPosition(nil,ei) | |
SetSelection(ei.sel) | |
editor.Redraw() | |
far.Text() | |
end | |
if str:len()==0 then return "" end --??to set sel off | |
else | |
--rem: return value in order to append it to search string | |
return "","Bwd" | |
end | |
end; | |
--clear/restore | |
CtrlBS=function() | |
if O.ExtraQuick then --restore pos | |
editor.SetPosition(nil,OriginalPos) | |
SetSelection(OriginalSel) | |
end | |
str = str:len()>0 and "" or last | |
appendable = true | |
return ""--?? | |
end; | |
--insert | |
ShiftIns=function() | |
return far.PasteFromClipboard():match"[^\r\n]+" | |
end; | |
--quit and restore pos | |
Esc=function() quitSearchMode{restorePos=not O.useBM} end; | |
--quit | |
Alt=function() quitSearchMode{keepHighlight=true} end; | |
--next | |
ShiftF7=function() | |
if strIsEmpty() then return end | |
search(srhFwd,str,nil,"Right") | |
end; | |
--prev | |
AltF7=function() | |
if strIsEmpty() then return end | |
search(srhBwd,str,nil,"Left") | |
end; | |
CtrlA=function() | |
if strIsEmpty() then return end | |
srhFindAll() | |
end; | |
AltC=function() return srhToggleOption"CaseSensitive" end; | |
AltW=function() return srhToggleOption"WholeWords" end; | |
AltG=function() return srhToggleOption"RegExp" end; | |
AltQ=function() | |
O.ExtraQuick = not O.ExtraQuick | |
local txt = "ExtraQuick:"..(O.ExtraQuick and 1 or 0) | |
MessagePopup(txt) | |
end; | |
AltRight=function() | |
local si = editor.GetString() | |
local pos = si.SelStart~=0 and si.SelEnd+1 or editor.GetInfo().CurPos | |
local word = si.StringText:match("^%w+",pos) | |
word = word or si.StringText:sub(pos,pos) | |
return word~="" and word | |
end; | |
F1=function() | |
far.Message([[ | |
• RAlt — включить режим инкрементального поиска. | |
Если в текущей строке есть выделенный текст — он подхватывается. | |
• BS — удалить последний введённый символ. | |
При пустой строке: восстановить предыдущую строку. | |
• CtrlBS — очистить строку поиска. | |
• ShiftIns/CtrlV — вставить строку из буфера обмена. | |
• RCtrl/ShiftF7/F3/Enter — найти следующее вхождение строки. | |
• LCtrl/AltF7/ShiftF3/ShiftEnter — найти предыдущее вхождение строки. | |
• Нажатие любой алфавитно-цифровой (или символьной) клавиши добавляет символ к искомой строке. | |
• Нажатие любой функциональной клавиши отключает режим поиска, клавиша передаётся фару. | |
(Это касается клавиш управления курсором, и всех клавиш с модификаторами Ctrl/Alt/Shift). | |
• Для прекращения поиска можно также воспользоваться повторным нажатием RAlt. | |
• Esc — прекращает поиск и восстанавливает исходную позицию. | |
• CtrlA: диалог с результатами поиска всех вхождений. | |
• AltRight — расширяет выделение до конца слова. | |
• Alt+Option | |
C: CaseSensitive | |
W: WholeWords | |
G: RegExp | |
Q: ExtraQuick | |
Немодальный поиск: | |
• Активируется нажатием RAlt+буква (точнее символ слова: \w), или RAltSpace. | |
Набирать текст нужно удерживая RAlt. | |
При отпускании RAlt поиск завершается. | |
• Даже после отпускания вернуться в позицию "до поиска" можно с помощью макроса на RAltBS. | |
• Кроме того, поиск можно активировать нажатием RAlt+LCtrl / RAlt+RCtrl. | |
В этом случае продолжается предыдущий поиск (назад / вперёд). | |
• Доступны те же шорткаты, что и в обычном режиме, за исключением изменения опций. | |
• F1 — Справка | |
]],nfo.name.." v"..nfo.version,nil,"l") | |
end; | |
} | |
local function AltState(Mod) return band(Mouse.LastCtrlState,F.LEFT_ALT_PRESSED+F.RIGHT_ALT_PRESSED) end | |
local rus = 0x04190419 | |
local function IncSearch(arg) | |
enterSearchMode(arg) | |
local key | |
repeat | |
if arg.mode=="modeless" then | |
repeat | |
if AltState()==0 then | |
quitSearchMode{}; return | |
end | |
key = mf.waitkey(100) | |
until key~="" | |
key = key:match"^R?Alt(.*)$" or key | |
key = key:match"^R?CtrlR?AltBS" and "CtrlBS" or key | |
key = key=="Right" and "AltRight" or key | |
key = key=="F7" and "AltF7" or key | |
if key:len()==1 and Far.KbdLayout()==rus then key=far.XLat(key) end | |
else | |
key = mf.waitkey() | |
end | |
key = xform[key] or key | |
if actions[key] then | |
local text,bwd = actions[key]() | |
if text then append(text,bwd) end | |
elseif key:len()>1 then | |
quitSearchMode{} | |
if mf.eval(key,2)==-2 then Keys(key) end | |
else | |
append(key) | |
end | |
until not SearchMode | |
end | |
Macro { description="incremental search mode"; | |
area="Editor"; key=O.Alt; | |
id="9CDB70B8-2774-491C-9F7C-46B4B0BC14CE"; | |
action=function() | |
IncSearch{} | |
end; | |
} | |
local function AKey() return mf.akey(1,1) end | |
if O.Alt=="CtrlRAlt" then return end -- no luck with AltGr | |
Macro { description="incremental search: modeless"; | |
area="Editor"; key="/" .. O.Alt .. "(\\w|Space)/"; | |
priority=40; -- yield to Alt Screens and other Alt+letter macros | |
id="9C58832B-3F12-4974-AB18-EF3162A3DF8D"; | |
action=function() | |
local key = AKey():match"^R?Alt(%w)$" | |
if key and key:len()==1 and Far.KbdLayout()==rus then key = far.XLat(key) end | |
local sel = Editor.SelValue | |
if sel:match"\n" then sel = "" end | |
IncSearch{str=sel..key:lower(), mode="modeless"} | |
end; | |
} | |
Macro { description="incremental search: continue"; | |
area="Editor"; key="Ctrl"..O.Alt; | |
id="DA1821EA-2C4B-415D-8462-42014A8186F9"; | |
action=function() | |
local bwd = AKey():match("^Ctrl") | |
IncSearch { | |
str=str, | |
dir=bwd and "Left" or "Right", | |
cmd=bwd and actions.AltF7, | |
mode="modeless" | |
} | |
end; | |
} | |
Macro { description="incremental search: restore original pos"; | |
area="Editor"; key=O.Alt.."BS"; | |
id="0CA47A7D-BB21-44DA-8FC0-1ACF5673F3DB"; | |
action=function() | |
editor.SetPosition(nil,OriginalPos) --todo | |
SetSelection(OriginalSel) | |
end; | |
} | |
-- todo ideas | |
-- WholeWords http://forum.farmanager.com/viewtopic.php?p=126210#p126210 | |
-- regexp builder http://forum.farmanager.com/viewtopic.php?p=131742#p131742 | |
-- options + optionsEF = single options | |
-- options menu |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment