-- coding: utf-8 -- Minimal Far version: 3.0.3300 -- http://forum.farmanager.com/viewtopic.php?p=141965#p141965 -- Исходный скрипт был написан для плагина LuaFAR for Editor. -- Первоначальный автор: Максим Гончар ("maxdrfl" в форуме Far Manager). -- Адаптация к Far3 API, плагину LuaMacro и некоторые изменения в коде: Shmuel Zeigerman. --[[ ОРИГИНАЛЬНАЯ СПРАВКА (адаптировано): ------------------------------------ Небольшой калькулятор. Написан в первую очередь для проверки работы диалогов и скорости сборки скриптов lua. Свойства: - синтаксис lua - функции из math находятся в _G - сборка на лету. Указание этапа работы на котором произошла ошибки - 4 строки вывода с настраиваемым форматом (см. lua:string.format) - если в строке есть команда return, результатом вычисления считается возвращаемое значение - поддерживаются пользовательские функции. Пользовательские функции хранятся в модуле fl_calc.lua. Наличие модуля не обязательно для работы калькулятора. Краткая справка пишется в том же файле в таблицу help. Вызывается из калькулятора по F1. Для справки см. уже определённые функции fib, fact, mean, sum. --]] local M -- message localization table local F = far.Flags local KEEP_DIALOG_OPEN = 0 local HOTKEY_IS_DONE = 0 local SendMessage = far.SendDlgMessage local farbuild = select(4, far.AdvControl("ACTL_GETFARMANAGERVERSION", true)) local enable_custom_hotkeys = (farbuild >= 4269) -- DN_HOTKEY was fixed in LuaFAR build 483 local python -- Lunatic Python module local lastres -- last result -- tobase(10,2) --> "1010" -- tobase(200,16) --> "C8" -- tobase(-200,16) --> "-C8" local function tobase(num, base) local floor, fmod = math.floor, math.fmod base = assert(base >=2 and base <= 36) and floor(base) local offset = string.byte("A") - 10 local t = {""} if num < 0 then t[1],num = "-",-num end num = floor(num) repeat local r = fmod(num, base) -- don't use % operation (it may be Lua implementation dependent) num = (num - r) / base table.insert(t, 2, r<10 and tostring(r):sub(1,1) or string.char(r+offset)) -- sub() needed for Lua 5.3 until num == 0 return table.concat(t) end local function loadhelp() -- load user functions and help message local userlib, strhelp local ok,lib = pcall(require, "shmuz.fl_calc") if ok then userlib, strhelp = lib, lib.help if strhelp then local message={} for i=1,#strhelp,2 do table.insert(message, ('%-20.20s - %s'):format(strhelp[i], strhelp[i+1])) end table.sort(message) strhelp = table.concat(message,'\n') end end strhelp = strhelp or M.mNoFuncAvail -- also list functions available in math local tb = { [1] = "\n\1\nmath:\n " } for k in pairs(math) do table.insert(tb,k) end table.sort(tb) for i=2,#tb do local suffix = i==#tb and "" or (i-1)%8==0 and ",\n " or ", " tb[i] = tb[i]..suffix end strhelp = strhelp..table.concat(tb) -- add help for special formatting feature strhelp = strhelp.."\n\1\n".. "Format hint:\n #n (where n=2..36) gives radix n result representation" return userlib, strhelp end local function getdata(item) return item.text; end local function setdata(item,data) item.text=data; end local function calculator() local sDialog = require "shmuz.simpledialog" local xx = M.mSyntax:len()+8 local function shortcut(n) lastres = n or lastres+1 return "&"..lastres end local items = { guid="E7588240-0523-4AA5-8A31-EE829E20CD26"; { tp="dbox"; text=M.mMainDlgTitle; }, { tp="text"; text=M.mSyntax; x1=7; }, { tp="rbutt"; name="lng_lua"; text="&Lua"; x1=xx; y1=""; group=1; val=1; }, { tp="rbutt"; name="lng_c"; text="&C"; x1=xx+9; y1=""; }, { tp="rbutt"; name="lng_py"; text="&Python"; x1=xx+16; y1=""; disable=not python; }, { tp="edit"; name="calc"; x1=7; hist='LuaCalc'; focus=1; }, { tp="text"; x1=5; x2=5; text=shortcut(0); ystep=0; }, { tp="text"; x1=""; x2=""; text=shortcut(); }, { tp="text"; x1=""; x2=""; text=shortcut(); }, { tp="text"; x1=""; x2=""; text=shortcut(); }, { tp="text"; x1=""; x2=""; text=shortcut(); }, { tp="text"; x1=""; x2=""; text=shortcut(); }, { tp="text"; x1=""; x2=""; text=shortcut(); }, { tp="rbutt"; name="decrad"; x1=7; group=1; val=1; Item="dec"; ystep=1-lastres; }, { tp="rbutt"; name="octrad"; x1=""; Item="oct"; }, { tp="rbutt"; name="hexrad"; x1=""; Item="hex"; }, { tp="rbutt"; name="rawrad"; x1=""; Item="raw"; }, { tp="rbutt"; name="hflrad"; x1=""; Item="hfl"; }, { tp="rbutt"; name="dbhrad"; x1=""; Item="dbh"; }, { tp="edit", name="decfmt"; x1=11; x2=16; text="%g" ; Fmt="dec"; ystep=1-lastres; }, { tp="edit", name="octfmt"; x1=""; x2=""; text="%o" ; Fmt="oct"; }, { tp="edit", name="hexfmt"; x1=""; x2=""; text="%#x"; Fmt="hex"; }, { tp="edit", name="rawfmt"; x1=""; x2=""; text="%s" ; Fmt="raw"; }, { tp="edit", name="hflfmt"; x1=""; x2=""; text="%a" ; Fmt="hfl"; }, { tp="edit", name="dbhfmt"; x1=""; x2=""; text="dbh"; Fmt="dbh"; }, { tp="text"; x1=18; x2=18; text=":"; ystep=1-lastres; }, { tp="text"; x1=""; x2=""; text=":"; }, { tp="text"; x1=""; x2=""; text=":"; }, { tp="text"; x1=""; x2=""; text=":"; }, { tp="text"; x1=""; x2=""; text=":"; }, { tp="text"; x1=""; x2=""; text=":"; }, { tp="text"; name="dec"; x1=20; Update=1; Fmt="decfmt"; ystep=1-lastres; }, { tp="text"; name="oct"; x1=""; Update=1; Fmt="octfmt"; }, { tp="text"; name="hex"; x1=""; Update=1; Fmt="hexfmt"; }, { tp="text"; name="raw"; x1=""; Update=1; Fmt="rawfmt"; }, { tp="text"; name="hfl"; x1=""; Update=1; Fmt="hflfmt"; }, { tp="text"; name="dbh"; x1=""; Update=1; Fmt="dbhfmt"; }, { tp="text"; x1=5; text=M.mLabelStatus; }, { tp="text"; name="status"; x1=13; text='ok'; Update=1; ystep=0; }, { tp="butt"; name="btnCalc"; centergroup=1; text=M.mButtonCalc; default=1; }, { tp="butt"; name="btnIns"; centergroup=1; text=M.mButtonInsert; }, { tp="butt"; name="btnCopy"; centergroup=1; text=M.mButtonCopy; }, } local dPos, dItems = sDialog.Indexes(items) local curlang = nil local compiled = nil local result = 0 local active_item = dItems.dec local userlib, strhelp = loadhelp() local environ = setmetatable({}, { __index = function(t,k) return userlib and userlib[k] or math[k] or _G[k] end }) local cfunction = function(c) return environ[c] end local keys = { Enter="btnCalc"; Ins="btnIns"; F5 = "btnCopy"; } local pg if python then python.execute [[from math import *]] python.execute [[ def my_eval(txt): try: return eval(txt), None except Exception as E: return None, str(E) ]] pg = python.globals() end function dItems.btnCalc.Action(handle) if tonumber(result) then SendMessage(handle, F.DM_SETTEXT, dPos.calc, result) end return KEEP_DIALOG_OPEN end function dItems.btnCopy.Action() far.CopyToClipboard(getdata(active_item)) return KEEP_DIALOG_OPEN end function dItems.btnIns.Action() far.MacroPost(("print %q"):format(getdata(active_item))) end local function format(item, res) if type(res) == 'number' then local fmt = getdata(dItems[item.Fmt]) local base = tonumber(fmt:match("^#(%d+)")) if base and base>=2 and base <=36 then setdata(item, tobase(res, base)) else local ok,str = pcall(string.format, fmt, res) setdata(item, ok and str or M.mErrorFormat) end elseif curlang == "Python" then local fmt = getdata(dItems[item.Fmt]) local s = ("%q %% %s"):format(fmt, res) local s2 = pg.my_eval(s) setdata(item, s2[0] or "<error>") end end local function setdbh(item, res) -- Bits: -- 63: sign (s) -- 52-62: exponent (e) -- 0-51: mantissa (m) if type(res) == 'number' then local ok,str = pcall(string.format, "%A", res) if ok then if res == 0 then res = str:find("^%-") and "8" or "0" else res = string.format("%X", bit.band(tonumber(str:match("P([%-%+%d]+)$") or "0") + (str:find("^%-") and 3071 or 1023), 0xFFF))..(str:match("%.(%x+)P") or "") end res = (res..string.rep("0",16-#res)):gsub("%x%x","%1 "):sub(1,-2) else res = M.mErrorFormat end setdata(item, res) end end local function reset() setdata(dItems.dec, '') setdata(dItems.oct, '') setdata(dItems.hex, '') setdata(dItems.raw, '') setdata(dItems.hfl, '') setdata(dItems.dbh, '') setdata(dItems.status, nil) compiled = nil result = 0 end local function compile(handle) local str = SendMessage(handle, F.DM_GETTEXT, dPos.calc) if not str:find("%S") then str = "0" end if curlang == "Lua" then -- add parentheses to avoid tail call (that gives better error messages) compiled = loadstring('return ('..str..')') or loadstring(str) elseif curlang == "C" then compiled = loadstring(( [[ local getvar = ... local calc = require "shmuz.c_calc" return calc.expr("%s", getvar)]] ):format(str)) elseif curlang == "Python" then local txt = ("str(%s)"):format(str) local res = pg.my_eval(txt) if res[0] then compiled = res[0] else local txt = res[1]:match("(.-)%s*%(<string") setdata(dItems.status, txt or res[1]) end end if curlang ~= "Python" then if compiled then setfenv(compiled, environ) else setdata(dItems.status, M.mErrorCompile) end end end local function call() if curlang=="Python" then result = compiled else if curlang=="Lua" then result = compiled() elseif curlang=="C" then result = compiled(cfunction) end if type(result)=='function' then setdata(dItems.status, M.mErrorSubcall) elseif not tonumber(result) then setdata(dItems.status, M.mErrorCall) end end end local function form() if curlang ~= "Python" then result = tonumber(result) or '' end format(dItems.dec, result) format(dItems.oct, result) format(dItems.hex, result) format(dItems.raw, result) format(dItems.hfl, result) setdbh(dItems.dbh, result) setdata(dItems.status, 'ok') end local chain={reset, compile, call, form} local function do_chain(handle) local ok, msg for _,f in ipairs(chain) do ok, msg = pcall(f, handle) if not ok or getdata(dItems.status) then break end end if not ok then reset() msg = type(msg)=="string" and msg or "error message is not a string" msg = string.match(msg, ".*:%d+: (.*)") or msg setdata(dItems.status, msg) end for i,v in ipairs(items) do if v.Update then SendMessage(handle, F.DM_SETTEXT, i, getdata(items[i])) end end end local function set_language(handle) curlang = SendMessage(handle,F.DM_GETCHECK,dPos.lng_lua)==1 and "Lua" or SendMessage(handle,F.DM_GETCHECK,dPos.lng_c)==1 and "C" or "Python" end items.keyaction = function(handle,p1,key) if key == "F1" then far.Message(strhelp, M.mHelpDlgTitle, nil, 'l') else local name = keys[key] if name then SendMessage(handle, F.DM_CLOSE, dPos[name]) end end end items.proc = function(handle,msg,p1,p2) if msg==F.DN_INITDIALOG then set_language(handle) do_chain(handle) ---------------------------------------------------------------------------- elseif msg==F.DN_EDITCHANGE then setdata(items[p1], p2[10]) if p1==dPos.calc then do_chain(handle) else local fmt=items[p1].Fmt if fmt then format(dItems[fmt], result) SendMessage(handle, F.DM_SETTEXT, dPos[fmt], getdata(dItems[fmt])) end end ---------------------------------------------------------------------------- elseif msg==F.DN_BTNCLICK then if p2 ~= 0 then local btn = items[p1] if btn.Item then active_item = dItems[btn.Item] elseif btn.name:find("^lng") then set_language(handle) SendMessage(handle, F.DM_SETFOCUS, dPos.calc) do_chain(handle) end end ---------------------------------------------------------------------------- elseif msg==F.DN_HOTKEY then if enable_custom_hotkeys then local n = tonumber(p2.UnicodeChar) if n and n>=0 and n<=lastres then if n==0 then SendMessage(handle, F.DM_SETFOCUS, dPos.calc) else SendMessage(handle, F.DM_SETCHECK, dPos.decrad+n-1, 1) SendMessage(handle, F.DM_SETFOCUS, dPos.decfmt+n-1) end return HOTKEY_IS_DONE end end ---------------------------------------------------------------------------- elseif msg==F.DN_CLOSE and p1>=1 then local btn=items[p1] if btn.Action then return btn.Action(handle) end end end if not enable_custom_hotkeys then for _,v in ipairs(items) do if v.text and v.text:match("^&[0-"..lastres.."]$") then v.text=v.text:sub(2) end end end sDialog.Run(items) end -- local function calculator() local Lang = { English = { mMainDlgTitle = "Lua Calculator"; mLabelStatus = "Status:"; mButtonCalc = "Calculate"; mButtonInsert = "Insert (Ins)"; mButtonCopy = "Copy (F5)"; mHelpDlgTitle = "Functions:"; mNoFuncAvail = "<No user functions available>"; mErrorCall = "Error: call"; mErrorCompile = "Error: compile"; mErrorSubcall = "Error: subcall"; mErrorFormat = "Error: format"; mSyntax = "Syntax:"; }; Russian = { mMainDlgTitle = "Lua-калькулятор"; mLabelStatus = "Статус:"; mButtonCalc = "Вычислить"; mButtonInsert = "Вставить (Ins)"; mButtonCopy = "Скопировать (F5)"; mHelpDlgTitle = "Функции:"; mNoFuncAvail = "<Нет пользовательских функций>"; mErrorCall = "Ошибка: вызов"; mErrorCompile = "Ошибка: компиляция"; mErrorSubcall = "Ошибка: подвызов"; mErrorFormat = "Ошибка: формат"; mSyntax = "Синтаксис:"; }; } Macro { description="Lua Calc"; area="Common"; key="CtrlAltF4"; id="1A26B745-1722-49F4-8075-2B2CA6C5487A"; -- the condition function is not used here for its intended purpose; rather we take -- advantage of the fact that lua_State here is "main" (not that of a coroutine) condition = function() local ok, py = pcall(require, "python") python = ok and py return true end; action=function() -- mf.acall: 3.0.4003=bad; 3.0.4005=good M = Lang[win.GetEnv("FARLANG")] or Lang.English local _ = mf.acall and mf.acall(calculator) or calculator() end; } |