local _NAME,_VER = "luacheck","1.2H"
local _forumtopic = "http://forum.farmanager.com/viewtopic.php?f=15&t=9650"
local luacheck_min_version,lmv_err_msg = "0.11.0","luacheck version %s or newer expected"
-- list options
local O = {
MaxHeight = 10,
InitPos = "Bottom", --Top,Bottom,Center
AutoPos = true,
--CycleMode = "ping-pong",
--ConvertToAnsi = true,
}
-- luacheck options
local options = { --http://luacheck.readthedocs.org/en/0.11.0/config.html
new_read_globals={
"Macro","NoMacro","Event","NoEvent","MenuItem","NoMenuItem","CommandLine","NoCommandLine",
};
new_globals={
"_filename", --scripts executed in luamacro via cmdline prefix
"export","IsLuaStateRecreated", --lua plugins
}
--global=false;
--allow_defined=true;
--allow_defined_top=true;
--unused=false;
--unused_args=false;
--unused_secondaries=false;
--redefined=false;
--http://luacheck.readthedocs.org/en/0.11.0/cli.html#patterns
--ignore={"4../.*"};
--enable={};
--only={};
}
local only_globals
local function showHelp()
if far.Message([[
Основные действия:
• Enter - перейти к позиции предупреждения
• Esc - закрыть список и восстановить исходную позицию
Дополнительные действия (не закрывают список):
• Ctrl-PgUp - перейти к позиции, на которую ссылается предупреждение
• Ctrl-Up/Down - скроллировать текст редактора
Управление расположением списка:
• Ctrl-Home, Num7 - вверху
• Ctrl-End, Num1 - внизу
• Num4 - по центру
• F6, Divide - перебор режимов
• F5, Num5 - максимизация
Действия с закладками
• Ctrl-0..9 - перейти к позиции закладки 0..9
• Ctrl-Shift-0..9 - установить закладку 0..9 в текущей позиции
• Add - добавить сессионную закладку с текущей позицией]],
("%s v%s"):format(_NAME,_VER),"OK;Go to forum;","l")==2 then win.ShellExecute(nil,"open",_forumtopic) end
end
--todo
--• Ctrl-Shift-Enter,
-- Ctrl-Enter - перейти к позиции предупреждения
--• Shift-Up, Num2 - перейти к предыдущему предупреждению
--• Shift-Down,Num8 - перейти к следующему предупреждению
local luacheck = require "luacheck"
if luacheck._VERSION<luacheck_min_version then error(lmv_err_msg:format(luacheck_min_version)) end
local function MessagePopup(msg,title,flags,delay)
local s = far.SaveScreen()
far.Message(msg,title or "","",flags)
win.Sleep(delay or 500); far.RestoreScreen(s)
end
local Id = "CFD31C22-0D17-4D87-829A-14561B529D34"
local function expandBreakKeys(BreakKeysArr)
local newBreakKeysArr = {}
for _,item in ipairs(BreakKeysArr) do
if item then
for key in item.BreakKey:gmatch("%S+") do
local newitem = {}
for k,v in pairs(item) do newitem[k]=v end
newitem.BreakKey = key
table.insert(newBreakKeysArr,newitem)
end
end
end
return newBreakKeysArr
end
local function setEditorPos(info) editor.SetPosition(nil,info); editor.Redraw() end
local bak_pos
local function restorePos()
if bak_pos then
editor.Select(nil,bak_pos)
setEditorPos(bak_pos)
end
end
local function scrollEditor(dir)
local ei = editor.GetInfo()
ei.TopScreenLine = ei.TopScreenLine+dir
if ei.TopScreenLine<1 or ei.TopScreenLine>ei.TotalLines-ei.WindowSizeY+1 then mf.beep(); return end
if ei.CurLine-ei.TopScreenLine-(dir==1 and -1 or ei.WindowSizeY)==0 then
ei.CurLine = ei.CurLine+dir
end
setEditorPos(ei)
end
local F = far.Flags
local list = {}
function list:calcProps(Items,Props)
self.Pos = self.Pos or O.InitPos
Items = assert(Items or self.Items,"Items not set")
local nItems = #Items
Props = assert(Props or self.Props,"Props not set")
-- calculate required list's dimensions and position
local r = far.AdvControl(F.ACTL_GETFARRECT)
local xSize,ySize = r.Right-r.Left,r.Bottom-r.Top --Far size
local MaxItems = ySize-3 --max items n to fit
local X,Y
X = Items.maxlen<xSize-4 and (xSize-Items.maxlen)/2-2 or -1
local nLines = math.min(nItems,O.MaxHeight,MaxItems) --actual list lines n
local Pos = self.Pos or O.InitPos
local isMaximizable = nItems>nLines and nLines<MaxItems
local isMaximized = self.isMaximized and isMaximizable
if Pos=="Center" or isMaximized then
X,Y = -1,-1
elseif Pos=="Top" then
Y = 0
elseif Pos=="Bottom" then
Y = MaxItems-nLines>0 and MaxItems-nLines+1+1 or -1
end
-- set properties
Props.X,Props.Y,Props.MaxHeight = X,Y,isMaximized and 0 or O.MaxHeight
self.Props,self.Items,self.nItems = Props,Items,nItems
self.nLines,self.isMaximized = nLines,isMaximized
self.EditorShiftTop = Far.GetConfig"Editor.ShowTitleBar" and 0 or 1
self.EditorShiftBottom = Far.KeyBar_Show()-1
end
function list:maximize(state)
self.isMaximized = state==nil and not self.isMaximized or state
self:calcProps()
end
function list:setPos(Pos)
if Pos==self.Pos then
return list:maximize()
elseif Pos=="ping-pong" then
if self.Pos=="Center" then
Pos = self.prevPos=="Top" and "Bottom" or "Top"
else
Pos = "Center"
end
elseif not Pos then
local next = {["Top"]="Bottom",["Bottom"]="Center",["Center"]="Top"}
Pos = next[self.Pos]
end
self.prevPos = Pos~="Center" and Pos or self.prevPos
self.Pos = Pos
self.isMaximized = false
self:calcProps()
end
function list:gotoWarning(pos,to_prev,force_top)
local w = self.Items[pos]
local line = to_prev and w.prev_line or w.line
if not line then return end
local column = to_prev and w.prev_column or w.column
local end_column = not to_prev and w.end_column
--select
local width
if end_column then
width = end_column-column+1
elseif w.name then
width = w.name:len()
else
local str = editor.GetString(nil,line)
if not str then return end
local a,b = str.StringText:find("^%S+",column)
width = a and b-a+1 or str.StringLength-column+1
end
editor.Select(nil,F.BTYPE_STREAM,line,column,width,1)
--goto
local nLines,TopScreenLine = self.nLines
if nLines then --trying to correct TopScreenLine in order not to let cursor be hidden behind list
local ei = editor.GetInfo()
local y = line-ei.TopScreenLine --calculated screen line of warning
local a,b = 0,ei.WindowSizeY --valid screen lines (a..b)
if self.Pos=="Top" then
a = a+nLines+3+self.EditorShiftTop
elseif self.Pos=="Bottom" then
b = b-nLines-4+1+self.EditorShiftBottom
end
TopScreenLine = (y<a or force_top) and math.max(line-a,1)
or (y>b) and line-b
--win.OutputDebugString(("%s…%s = %s = %s"):format(a,b,y,TopScreenLine or '-'))
end
setEditorPos{CurLine=line,CurPos=column,TopScreenLine=TopScreenLine}
end
function list:Up(pos) return pos-1>=1 and pos-1 or self.nItems end
function list:Down(pos) return pos+1<=self.nItems and pos+1 or 1 end
local function go(dir,pos,autopos)
local nextpos = list[dir](list,pos)
if autopos then list:gotoWarning(nextpos) end
return nextpos
end
local Check --fw decl
local bKeys = expandBreakKeys {
{BreakKey="C+UP C+NUMPAD8 C+8",action=function() scrollEditor(-1) end},
{BreakKey="C+DOWN C+NUMPAD2 C+2",action=function() scrollEditor(1) end},
{BreakKey="C+RETURN C+NEXT C+NUMPAD3 C+3",action=function(pos) list:gotoWarning(pos) end},
{BreakKey="CS+RETURN", action=function(pos) list:gotoWarning(pos,nil,true) end},
{BreakKey="RETURN", action=function(pos)
if list.Props.SelectIndex~=pos then list:gotoWarning(pos) end
return "break"
end},
{BreakKey="ESCAPE", action=function() restorePos(); return "break" end},
{BreakKey="C+PRIOR C+NUMPAD9 C+9",action=function(pos) list:gotoWarning(pos,true) end},
{BreakKey="C+HOME NUMPAD7 7", action=function() list:setPos("Top") end},
{BreakKey="C+END NUMPAD1 1", action=function() list:setPos("Bottom") end},
{BreakKey="NUMPAD4 4", action=function() list:setPos("Center") end},
{BreakKey="F6 DIVIDE", action=function() list:setPos(O.CycleMode) end},
{BreakKey="F5 CLEAR 5", action=function() list:maximize() end},
{BreakKey="ADD", action=function() editor.AddSessionBookmark() end},
{BreakKey="CS+1 CS+2 CS+3 CS+4 CS+5 CS+6 CS+7 CS+8 CS+9 CS+0 C+1 C+2 C+3 C+4 C+5 C+6 C+7 C+8 C+9 C+0",keys=true},
{BreakKey="F1", action=showHelp},
--http://bugs.farmanager.com/view.php?id=1301#c12736
{BreakKey="UP", action=function(pos) return go("Up",pos,O.AutoPos) end},
{BreakKey="S+UP NUMPAD8 8", action=function(pos) return go("Up",pos,not O.AutoPos) end},
{BreakKey="DOWN", action=function(pos) return go("Down",pos,O.AutoPos) end},
{BreakKey="S+DOWN NUMPAD2 2", action=function(pos) return go("Down",pos,not O.AutoPos) end},
--{BreakKey="F3", action=function(pos) require"le"(list.Items[pos],"Event") end},
--{BreakKey="F9", action=function() require"le"(O,"Options");list:calcProps() end},
{BreakKey="C+H", action=function(_,Items)
only_globals = not only_globals
for i=1,#Items do
local item = Items[i]
item.hidden = only_globals and item.code and item.code:sub(1,1)~="1"
end
end},
}
local Props = {
Title=_NAME;
Bottom="Ctrl-Enter, Ctrl-Up/Down, F5, F6, F1";
Id=win.Uuid(Id);
}
function Check()
local ei = editor.GetInfo()
local sel = editor.GetSelection()
if sel then --save for further restoring
ei.BlockStartPos = sel.StartPos
ei.BlockWidth = sel.EndPos-sel.StartPos+1
ei.BlockHeight = sel.EndLine-sel.StartLine+1
end
bak_pos = ei
local source = {}
for i=1,ei.TotalLines do source[i] = editor.GetString(nil,i).StringText end
local text = table.concat(source,"\n")
if O.ConvertToAnsi then
text = win.WideCharToMultiByte(win.Utf8ToUtf16(text),win.GetACP())
end
local report = luacheck.check_strings({text}, options)[1]
if not report then
far.Message("Internal error: unable to get report",_NAME,nil,"w")
return
end
if #report==0 then
MessagePopup("No warnings",_NAME)
else -- proceed with warnings list
local Items,pos,maxlen,err = {},nil,0
for i,w in ipairs(report) do
err = w.code:sub(1,1)=="0"
w.text = ("%4u:%-3u "):format(w.line,w.column)..
("│ %s%s │ "):format(err and "E" or "W",w.code)..
luacheck.get_message(w)
local len = w.text:len()
maxlen = len>maxlen and len or maxlen
w.checked = err and "‼" or w.code:sub(1,1)=="1" and "!" --[[??]] or w.secondary and "²"
if not pos and w.line==ei.CurLine and w.column==ei.CurPos then pos = i end
Items[i] = w
if not w.end_column then mf.beep() end--??
end
Items.maxlen = maxlen
if err then mf.beep(); mf.postmacro(MessagePopup,"Errors found!",_NAME,"w",300) end
list:calcProps(Items,Props)
if O.AutoPos then list:gotoWarning(pos or 1) end
repeat --todo use dialog instead of menu
-- show list
Props.SelectIndex = pos
local item
item,pos = far.Menu(Props,Items,bKeys)
-- process breakkeys
if not item then
break
elseif item.keys then
Keys(item.BreakKey:gsub("^CS%+","CtrlShift"):gsub("^C%+","Ctrl"))
elseif not item.action then
list:gotoWarning(pos)
else
pos = item.action(pos,Items) or pos
if pos=="break" then break end
end
until false
only_globals = false
end
end
Event { description="luacheck menu handler";
group="DialogEvent";
action=function(Event,param)
local hDlg,Msg = param.hDlg,param.Msg
if Msg==F.DN_RESIZECONSOLE then
if Event==F.DE_DLGPROCINIT and Menu.Id==Id then
hDlg:send(F.DM_CLOSE,-1,0)
list:calcProps()
end
elseif Msg==F.DN_INITDIALOG and Event==F.DE_DLGPROCINIT then
local info = hDlg:send(F.DM_GETDIALOGINFO)
if info and info.Id==win.Uuid(Id) then
local r = hDlg:send(F.DM_GETITEMPOSITION,1)
hDlg:send(F.DM_SETITEMPOSITION,1,{Left=r.Left-2,Top=r.Top-1,Right=r.Right-2,Bottom=r.Bottom-1})
r = hDlg:send(F.DM_GETDLGRECT)
hDlg:send(F.DM_RESIZEDIALOG,0,{Y=r.Bottom-r.Top-1,X=r.Right-r.Left-3})
hDlg:send(F.DM_MOVEDIALOG,0,{X=2,Y=list.Pos=="Bottom" and 1 or 0})
end
end
end
}
Macro { description="luacheck menu helper";
area="Menu"; key="ShiftUp ShiftDown"; flags="NoSendKeysToPlugins";
uid="60596FEB-E7C5-47A5-A3C9-B782C8F8EF93";
condition=function()return O.AutoPos and Menu.Id==Id end;
action=function()
Keys(mf.akey(1,1):match"Up" and "Num8" or "Num2")
end;
}
Macro { description="luacheck: process current editor";
area="Editor"; key="CtrlShiftF7"; filemask="*.lua";
uid="CE634ECE-1039-43F3-80CC-8BB17B1FDB05";
action=Check;
}