Skip to content

Instantly share code, notes, and snippets.

@johnd0e
Last active May 21, 2023 22:33
Show Gist options
  • Save johnd0e/a3237d43a908ff78e496b20fbe6a7c68 to your computer and use it in GitHub Desktop.
Save johnd0e/a3237d43a908ff78e496b20fbe6a7c68 to your computer and use it in GitHub Desktop.
[FAR macro] IncSearch
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