Skip to content

Instantly share code, notes, and snippets.

@johnd0e

johnd0e/changelog.md

Last active Nov 11, 2019
Embed
What would you like to do?
[Far macro] Lua Explorer „Advanced“

2.4

  • Бинарные строки (непредставимые в utf-8) отображаются с псевдотипом #string, в кодировке Ansi. Редактирование тоже поддерживается.
  • Символы помечающие таблицы и функции теперь настраиваются в options.
  • Восстановлена работа jit.util.funcinfo (Shift-F3)

2.3 (2013+)

  • При вставке в таблицу, и редактировании значений теперь введённые параметры проверяются на корректность (количество и значение~=nil)
  • Функции Lua для работы с массивами не рассчитаны на то, что элементами массива могут быть значиния типа nil Теперь это учитывается. (При отображении возвращаемых функциями значений, возможно ещё где-то)
  • Символ с кодом 0 в ключах и значениях теперь заменяется на '\000'
  • Ctrl-I управляет режимом сортировки "различать регистр", начальное значение в опции ignore_case.
  • Не пытаемся работать с upvalues C-функций.
  • При просмотре объектов типа thread улучшено форматирование traceback.
  • mf.msgbox -> far.Message (для возможности использования в luafar плагинах)
  • Экспериментально: расширение возможностей скрипта, путём добавления новых действий в виде BreakKeys. le(actions,nil,'addBrKeys')
    • actions - таблица - список действий, в том же формате, что и штатная brkeys (см.)
    • (вторым параметром обычно передаётся заголовок, в данном случае не имеет смысла)
    • 'addBrKeys' команда для импорта.
  • Из скрипта убрана работа с локальными переменными функций. Действия могут быть добавлены отдельным скриптом se.lua.
  • Поддержка cfgscript / ScriptsBrowser http://forum.farmanager.com/viewtopic.php?f=15&t=10418

2.2 (Вт 21 май, 2013 16:18)

Обновление по случаю перехода на luajit.

  • [jit|5.2] При запуске функции показывается список её параметров. Актуально для lua-функций, а C-функции принимают в основном vararg (все, или по крайней мере большинство). vararg отображается тремя точками
  • [jit|5.2] При просмотре локальных переменных также показывается vararg, в виде таблицы '(*vararg)' Значения доступны для редактирования
  • При просмотре локальных переменных также добавляется ещё один псевдоэлемент - сама функция, находящаяся на заданном уровне стека вызовов (т.е. та самая, для которой показываются локальные переменные). Функция помещается в тот же список, что и переменные, как элемент с именем ('(*func: %s)'):format(info.name or '') Мне кажется это не лучший способ представления, если будут идеи получше, то переделаю.
  • Новый хоткей - Ctrl-T, переключает режим сортировки (по имени/по типу)
  • lua_explorer теперь не спотыкается при обработке 'не очень корректных значений' (Windows XP)
  • Пара других исправлений

2.1 (Пт 17 май, 2013 23:19) by John Doe

Изменений накопилось много, не знаю примет ли их EGez, в том или ином виде. На всякий случай выделю в отдельную тему: https://forum.farmanager.com/viewtopic.php?f=60&t=7988

И по случаю релиза небольшое, но важное для меня изменение: при вызове из собственных скриптов можно третьим параметром указать необходимое действие: 'upvalues', 'locals', 'env', 'mt', 'info', 'jitinfo', 'registry'


Lua Explorer „Advanced“

Представляет собой развитие замечательной утилиты Lua Explorer © EGez. Началось всё с небольшой модификации, позволяющей вызывать lua explorer из других скриптов. Но у автора свободного времени не было, а необходимость в изменениях у меня назрела. В частности потому что я начал применять lua explorer в Macro Browser alt. и других скриптах.

Не знаю примет ли EGez мои изменения, поэтому пока форк. В данный момент отличия от оригинального скрипта таковы:

  1. Скрипт преобразован в модуль, имя выбрано короткое "le", для быстрого доступа.

  2. Изменения

    • таблицы и функции в списке помечаются
    • Ключи и значения в списке закавычиваются только в случае пустой строки "", или наличии концевых пробелов
    • Обрезка чрезмерно длинных ключей
    • При просмотре многострочных значений текст больше не центрируется
    • Не проглатываются амперсанды в меню, и при просмотре значений (в заголовке) FMENU_AUTOHIGHLIGHT и FMENU_SHOWAMPERSAND почему-то несовместимы, поэтому 'автохоткеи' отключены.
    • В ряде мест внедрена защита от ошибок при вызове сторонних функций (через pcall)
      • при индексации таблиц с метаметодом __index
      • при редактировании значения, и вставке значения в таблицу (F4, Ins)
      • в параметрах при запуске функций (из списка по Enter)
    • Обработка статуса возвращаемого pcall при запуске функций.
    • При ошибке выдаётся соответствующее сообщение.
    • При отсутствии метатаблицы по Ctrl-M теперь не показывается message c nil
  3. Новая функциональность

    • Копирование в буфер:
      • значение: Ctrl-Ins
      • ключ: Ctrl-Shift-Ins
    • по F3 для объектов типа 'thread' показывается ebug.traceback
    • для функций: Ctrl-Up: просмотр и редактирование upvalues (полезно при вызове из собственных скриптов, в отладочных целях) Ctrl-Down: просмотр и редактирование окружения функций (environment). Используется debug.getfenv, что также позволяет получить окружение объектов типа userdata и thread Alt-F4: открытие модального редактора в месте определения функции
    • F9: debug.getregistry: http://www.lua.ru/doc/3.5.html
    • при вызове из собственных скриптов доступен просмотр и редактирование локальных переменных Ctrl-1 - Ctrl-9: выбор уровня стека вызовов Переходы по уровням не вложены, т.е. по Esc меню будет сразу закрыто. Для перехода в просмотр глобальной среды можно использовать хоткей Ctrl-G Более наглядный просмотр стека будет позже, отдельной утилитой (пока прототип)
    • при вызове из собственных скриптов можно указать необходимое действие: 'upvalues', 'locals', 'env', 'mt', 'info', 'jitinfo', 'registry'

Использование: a. Если вы не планируете вызывать lua explorer из скриптов, то достаточно положить файл в папку scripts, и макрос подхватится (Ctrl-Shift-F12) b. Для использования в качестве модуля скрипт необходимо положить в папку modules. Использовать можно так:

  • local lua_explorer = require"le"
    lua_explorer(sometable,"title")

    (title можно не указывать)

  • А файл с макросом для показа _G может выглядеть так

    Macro {
      description = "Lua Explorer";
      area="Common"; key="CtrlShiftF12";
      action=function()
        require"le"(_G,'_G')
      end
    }
  • Если нужно вызвать из меню пользователя, то можно так: lua: require"le"(_G,'_G')

c. Кроме того, для быстрого доступа может быть удобно создать глобальную переменную. Для этого в любом из скриптов в scripts (но лучше всего в_macroinit.lua) надо добавить строчку _G.le = require"le" Примечание: Это однозначно удобно для быстрого доступа в процессе отладки, но нежелательно, чтобы отлаженный скрипт или макрос полагался на глобальную переменную (это особенно важно при обмене макросами/скриптами)

Подробнее об использовании для отладки собственных скриптов:

  • Макрос из комплекта открывает глобальное окружение, исследуя структуру которого можно почерпнуть массу сведений. Но как уже сказано выше, lua explorer можно использовать для просмотра любой таблицы в ваших собственных скриптах. Для этого нужно указать эту таблицу в качестве аргумента при вызове функции. Второй аргумент - заголовок (опционально). Пример: lua_explorer(mf.GetMacroCopy(1),'1st loaded macro')

  • Кроме того есть третий (опциональный) параметр, в котором можно сразу указать необходимое действие. (При этом первый два параметра не всегда имеют смысл) Например:

    • lua_explorer(nil,nil,'locals') - открыть список текущих локальных переменных
    • lua_explorer(eval,'eval','upvalues') - открыть upvalues функции eval

    Примечание: если locals и upvalues в заданном контексте нет, то lua_explorer не запустится

    Полный список возможных действий: 'upvalues', 'locals', 'env', 'mt', 'info', 'jitinfo', 'registry'

    P.S. Свои пожелания уже почти полностью реализовал. Но идеи принимаются. Если кто пожертвует функцию для форматирования больших чисел, то включу (сам не сталкиваюсь).

2.x (Пт 17 май, 2013 11:28) by John Doe

  • Уровень стека (для информации о локальных переменных) теперь вычисляется относительно вызова функции 'process' Переходы по уровням больше не вложены
  • Хоткей Ctrl-G для открытия таблицы _G
  • Отображение амперсандов было исправлено не там где надо Также выяснилось что флаги меню FMENU_AUTOHIGHLIGHT и FMENU_SHOWAMPERSAND несовместимы. Возможно баг в меню. Отключил пока автохоткеи.
  • При запуске функций запрос параметров теперь тоже безопасный. (Функция та же что и при редактировании/вставке)
  • Обработка статуса возвращаемого pcall при запуске функций. При ошибке выдаётся соответствующее сообщение
  • Ключи и значения в списке закавычиваются только в случае пустой строки "", или наличии концевых пробелов
  • Обрезка чрезмерно длинных ключей
  • Копирование в буфер:
  • значение: Ctrl-Ins
  • ключ: Ctrl-Shift-Ins

2.x (16 май, 2013 13:47) by John Doe

(forked from 1.1.2)

  • Защита (pcall) от ошибок при индексации таблиц с метаметодом __index
  • Защита от ошибок при редактировании значения, и вставке значения в таблицу (F4, Ins)
  • При просмотре многострочных значений текст больше не центрируется
  • Не проглатываются амперсанды в меню и при просмотре значений (в заголовке)
  • При отсутствии метатаблицы по Ctrl-M теперь не показывается message c nil
  • таблицы и функции в списке помечаются
  • по F3 для объектов типа thread показывается debug.traceback
  • для функций:
    • Ctrl-Up: просмотр и редактирование upvalues (полезно при вызове из собственных скриптов, в отладочных целях)
    • Ctrl-Down: просмотр и редактирование окружения функций (environment). Используется debug.getfenv, что также позволяет получить окружение объектов типа userdata и thread
    • Alt-F4: открытие модального редактора в месте определения функции
  • F9: debug.getregistry: http://www.lua.ru/doc/3.5.html
  • при вызове из собственных скриптов доступен просмотр и редактирование локальных переменных Ctrl1 - Ctrl9: выбор уровня стека вызовов Более наглядный просмотр стека будет позже, отдельной утилитой (пока прототип)

1.x (Ср 18 сен, 2013 01:11)

Проект остановлен ввиду наступивших (хороших) перемен в жизни и связанным с ними изменением приоритетов. Возобновление работ над проектом в ближайшие годы на мой взгляд к сожалению не предвидится.

В связи с этим публикую код в том состоянии, в котором он пребывает на данный момент.

Помимо того, со спокойной совестью, могу назвать альтернативу, т.к. идея была подхвачена и развивается дальше: Lua Explorer „Advanced“ http://forum.farmanager.com/viewtopic.php?f=60&t=7988

Кто хочет свою альтернативу - дерзайте...

1.1.2 (Вс 03 фев, 2013 06:26)

  • Мелкое уточнение
  • Косметика

1.1.1 (Пт 14 дек, 2012 21:09)

  • Для редактирования квотим строковые значения с помощью ("%q"):format(str), а не собственного addslashes()
  • Другие мелкие изменения

1.1 (Сб 08 дек, 2012 16:54)

  • Добавлена возможность редактировать/удалять/добавлять объекты (F4/Del/Ins) (т.к. rawget и rawset не используются, операция может быть пресечена метатаблицами, как это делает Far API)
  • ... показать информацию о функции (debug.getinfo(...)/jit.util.funcinfo(...), F3/Shift+F3)
  • ... показать/скрыть функции (Ctrl+F)
  • ... показать метатаблицу (debug.getmetatable(...), Ctrl+M)
  • Числовые значения показываются в виде 0xHEX (DEC), для редактирования открываются в виде 0xHEX --[[ DEC ]]
  • Строковые значения, при открытии их для редактирования, приводятся в соответствующий вид (addslashes и т.д.)
  • Поддерживаются ключи любых типов
  • Ключи, так же, как и значения, форматируются соответственно их типу (тип ключа не указан за ненадобностью)
  • Добавлена история для полей ввода, идентифицируется по полному имени (пути) объекта. (напр. аргументы _G.akey будут показываться только для него, но не для _G._G.akey, _G.mf.akey и т.д.)
  • ... небольшая помощь по сочетаниям клавиш
  • Произведены различные мелкие изменения, оптимизация, рефакторинг

1.0 (Ср 28 ноя, 2012 00:01)

  • Пункты меню содержат ключ/имя, тип и текстовую репрезентацию значения.
  • Примитивные значения показываются сразу в меню, либо по выбору пункта в msgbox(...).
  • В каждую таблицу можно зайти как в меню.
  • Menu title показывает полный путь в виде Lua кода. (напр. _G.package.loaded.jit.util, но не x[1].y, а x.1.y, посмотрим, может еще прикручу)
  • Каждую функцию можно выполнить, со своими параметрами или без них.
    • Параметры интерпретируются как Lua код: loadstring('return ' .. prompt(...))().
    • ... т.е. могут быть константами типа 25, 0xff, "Lua!" или существующими переменными вроде APanel.Path, любой валидный Lua код!
    • Функции выполняются в защищенной среде pcall(...). (аналог try-catch)
    • Статус выполнения и возвращенные значения показываются в виде таблицы, т.е. меню, которое так же можно прошерстить. (1 = ОК/Ошибка, 2, ... = return values/текст ошибки в случае оной. См. pcall(...))
    • Выполнение функции можно отменить нажав Cancel/Esc в диалоге параметров.
  • Свойства фара, которые на самом деле являются функциями и живут в obj.properties, напр. APanel.properties.Empty(), но логически являются элементами obj, в частности APanel.Empty (Lua magic), также поддерживаются и отображаются как свойства, с типами и значениеми.

Вы пишете макросы? Вызывая данный скрипт макросом из макрообласти Common, можно напр. заранее изучить некоторые свойства диалогов, будь то Id, индекс кнопки OK, значения плагиновых панелей, и т.д. Думаю, это может сильно помочь вам в понимании/ловле жуков.

Вот, писал сначала для себя, но потом решил и со всеми поделиться.

С помощью данного скрипта можно изучать среду Lua в Far manager перемещаясь по структурам в виде системы вложенных меню, элементы которых содержат ключ/имя, тип и значение элементов текущей таблицы.

На данный момент реализованы следующие возможности:

  • Просмотр, редактирование, удаление и создание новых элементов.
  • Запуск любой функции с возможностью задать любые параметры.
  • В качестве параметров, ключей или новых значений можно использовать любой валидный код Lua, будь то константы или существующие в текущем контексте переменные.
  • История для параметров или редактируемых значений. Для каждой функции/переменной - своя.
  • Просмотр/редактирование мета-таблиц.
  • Информация о функциях.
  • Краткая помощь по клавишам.

Первый релиз:

  • Пункты меню содержат ключ/имя, тип и текстовую репрезентацию значения.
  • Примитивные значения показываются сразу в меню, либо по выбору пункта в msgbox(...).
  • В каждую таблицу можно зайти как в меню.
  • Menu title показывает полный путь в виде Lua кода. (напр. _G.package.loaded.jit.util, но не x[1].y, а x.1.y, посмотрим, может еще прикручу)
  • Каждую функцию можно выполнить, со своими параметрами или без них.
    • Параметры интерпретируются как Lua код: loadstring('return ' .. prompt(...))().
    • ... т.е. могут быть константами типа 25, 0xff, "Lua!" или существующими переменными вроде APanel.Path, любой валидный Lua код!
    • Функции выполняются в защищенной среде pcall(...). (аналог try-catch)
    • Статус выполнения и возвращенные значения показываются в виде таблицы, т.е. меню, которое так же можно прошерстить. (1 = ОК/Ошибка, 2, ... = return values/текст ошибки в случае оной. См. pcall(...))
    • Выполнение функции можно отменить нажав Cancel/Esc в диалоге параметров.
  • Свойства фара, которые на самом деле являются функциями и живут в obj.properties, напр. APanel.properties.Empty(), но логически являются элементами obj, в частности APanel.Empty (Lua magic), также поддерживаются и отображаются как свойства, с типами и значениеми.
  ╔════════════════════════════ _G.Editor  (17) ═════════════════════════════╗
  ║  "CurLine"                     │number  │0x00000001 (1)                  ║
  ║  "CurPos"                      │number  │0x00000001 (1)                  ║
  ║  "DelLine"                     │function│function: 0x00832988            ║
  ║  "FileName"                    │string  │"C:\Far3\Profile\Macros\lua_exp»║
  ║  "GetStr"                      │function│function: 0x008329a8            ║
  ║  "InsStr"                      │function│function: 0x008329c8            ║
  ║  "Lines"                       │number  │0x000000e8 (232)                ║
  ║  "Pos"                         │function│function: 0x008329e8            ║
  ║  "RealPos"                     │number  │0x00000001 (1)                  ║
  ║  "Sel"                         │function│function: 0x00832a08            ║
  ║  "SelValue"                    │string  │""                              ║
  ║  "Set"                         │function│function: 0x00832a28            ║
  ║  "SetStr"                      │function│function: 0x00832a48            ║
  ║  "SetTitle"                    │function│function: 0x00832a68            ║
  ║  "State"                       │number  │0x00000402 (1026)               ║
  ║  "Undo"                        │function│function: 0x00832a88            ║
  ║  "Value"                       │string  │"--[["                          ║
  ╚════════════════════════ F1, F3, F4, Del, Ctrl+M ═════════════════════════╝

Предложения по доработке и изменениям, как говорят немцы, sind herzlich willkommen.

ВНИМАНИЕ: О вызове некоторых функций. (Особенно новичкам читать обязательно!) Т.к. этот скрипт открывает возможность пощупать все потроха, доступные Lua, руками, которые, как нам всем известно, не редко чешутся, возникает некая вероятность, что вы загоните ваш Far в deadlock и вам придется его, беднягу, прибить. Чтобы добиться этого, всего-то можно попробовать запустить какую-нибудь функцию из контекста io, напр. читающую stdin, или же что-нибудь вроде debug.debug().

Более того, в контексте win доступны функции операционной системы, напр. для манипуляции реестром или файловой системой вашего компьютера, там можно много дров наломать.

... посему я должен сказать, что НЕ НЕСУ АБСОЛЮТНО НИКАКОЙ ОТВЕТСТВЕННОСТИ ЗА ВСЕ ТО, ЧТО ВЫ МОЖЕТЕ ИСПОРТИТЬ И НАТВОРИТЬ С ПОМОЩЬЮ ЭТОГО СКРИПТА, ВЫ ВСЕ ДЕЛАЕТЕ НА СВОЙ СТРАХ И РИСК И НЕ МОЖЕТЕ ОБВИНИТЬ МЕНЯ В ЧЕМ ЛИБО, ДАЖЕ ЕСЛИ ВЫ ЧТО-ТО ИСПОРТИЛИ В РЕЗУЛЬТАТЕ МОЕЙ ОШИБКИ.

Страшно звучит, правда? ;)

Как запускать:

Macro {
  area="Common"; key="CtrlAltD"; description="lua_explorer.lua";
  action = function()
    eval('@%FARPROFILE%\\Macros\\lua_explorer.lua')
  end;
}

или же просто: macro:post @lua_explorer.lua

Удачных экспериментов...

local Info = Info or package.loaded.regscript or function(...) return ... end --luacheck: ignore 113/Info
local nfo = Info { _filename or ...,
name = "Lua Explorer Advanced";
description = "Explore Lua environment in your Far manager";
version = "2.4";
author = "jd";
url = "http://forum.farmanager.com/viewtopic.php?f=60&t=7988";
id = "C61B1E8D-71D4-445C-85A6-35EA1D5B6EF3";
licence = [[
based on Lua Explorer by EGez http://forum.farmanager.com/viewtopic.php?f=15&t=7521
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
ANY USE IS AT YOUR OWN RISK.]];
help = function(nfo)
far.Message(nfo.helpstr, ('%s v%s'):format(nfo.name,nfo.version),nil,"l")
end;
options = {
tables_first = true,
ignore_case = true,
chars={
['table'] = '', --⁞≡•·÷»›►
['function'] = '˜', --ᶠ¨˝˜
},
bin_string = '#string',
};
}
if not nfo or nfo.disabled then return end
local O = nfo.options
assert(far,'This is LuaExplorer for Far manager')
local uuid = win.Uuid('7646f761-8954-42ca-9cfc-e3f98a1c54d3')
nfo.helpstr = [[
There are some keys available:
F1  Show this help
F4  Edit selected object
Del  Delete selected object
Ins  Add an object to current table
Ctrl+M  Show metatable
Ctrl+F  Show/hide functions
Ctrl+T  Toggle sort by type
Ctrl+I  Toggle ignore case
for functions:
Enter  Call function (params prompted)
F3  Show some function info
Shift+F3  Show some function info (LuaJIT required)
Alt+F4  Open function definition (if available) in editor
Ctrl+Up  Show upvalues (editable)
Ctrl+Down  Show environment (editable)
Copy to clipboard:
Ctrl+Ins  value
Ctrl+Shift+Ins  key
]]
local omit = {}
local brkeys = {}
-- format values for menu items and message boxes
local function valfmt(val,mode)
local t = type(val)
if t == 'string' then
if not val:utf8valid() then
local utf_8, err = win.Utf16ToUtf8(win.MultiByteToWideChar(val,win.GetACP()));
val = utf_8 or err
t = O.bin_string
end
if val:match'%z' or mode=='edit' then
val = ('%q'):format(val)
elseif mode~='view' and (mode~='list' or val=='' or val:sub(-1,-1)==" ") then
val = '"'..val..'"'
end
return val,t
elseif t == 'number' then
return (mode=='edit' and '0x%x --[[ %s ]]' or '0x%08x (%s)'):format(val, val),t
end
return tostring(val),t
end
-- make menu item for far.Menu(...)
local key_w = 30
local item_fmt = ('%%-%s.%ss'):format(key_w,key_w)..'%s%-8s │%-25s'
local function makeItem(key, sval, vt)
local k = valfmt(key,'list')
local border = k:len()<=key_w and '' or ''
return {
text = item_fmt:format(k, border, vt, sval),
key = key,
type = vt,
checked = O.chars[vt]
}
end
-- create sorted menu items with associated keys
local function makeMenuItems(obj)
local items = {}
-- grab all 'real' keys
for key in pairs(obj) do
local sval, vt = valfmt(obj[key],'list')
if not omit[vt] then
table.insert(items, makeItem(key, sval, vt))
end
end
-- Far uses some properties that in fact are functions in obj.properties
-- but they logically belong to the object itself. It's all Lua magic ;)
--if getmetatable(obj)=="access denied" then ...
local success,props = pcall(function()return obj.properties end)
--if not success then far.Message(props,'Error in __index metamethod',nil,'wl') end
if type(props) == 'table' and not rawget(obj, 'properties') then
--if type(obj.properties) == 'table' and not rawget(obj, 'properties') then
--todo use list of APanel Area BM CmdLine Dlg Drv Editor Far Help Menu Mouse Object PPanel Panel Plugin Viewer
for key in pairs(obj.properties) do
local sval, vt = valfmt(obj[key],'list')
if not omit[vt] then
table.insert(items, makeItem(key, sval, vt))
end
end
end
-- table.sort(items, function(v1, v2) return v1.text < v2.text end)
--[[
table.sort(items, function(v1, v2)
if O.tables_first and (v1.type=='table') ~= (v2.type=='table') then
return v1.type=='table'
else
return v1.text < v2.text
end
end)
--]]
---[[
table.sort(items, function(v1, v2)
if O.tables_first and v1.type~=v2.type then
return v1.type=='table' or v2.type~='table' and v1.type<v2.type
else
if O.ignore_case then
return v1.text:lower() < v2.text:lower()
else
return v1.text < v2.text
end
end
end)
--]]
return items
end
local function getres(stat,...)
return stat, stat and {...} or (...) and tostring(...) or '', select('#',...)
end
local function checknil(t,n)
for i=1,n do
if t[i]==nil then return true end
end
end
local function concat(t,delim,i,j) --custom concat (applies 'tostring' to each item)
--assert(delim and i and j)
local s = j>0 and tostring(t[i]) or ''
for ii=i+1,j do s = s..delim..tostring(t[ii]) end
return s
end
local function luaexp_prompt(Title,Prompt,Src,nArgs)
repeat
local expr = far.InputBox (nil, Title:gsub('&','&&',1), Prompt,
Title, Src, nil, nil, far.Flags.FIB_ENABLEEMPTY)
if not expr then break end
local f,err = loadstring('return '..expr)
if f then
local stat,res,n = getres(pcall(f))
if stat then
if not nArgs or (nArgs==n and not checknil(res,n)) then
return res, n, expr
else
err = ([[
%d argument(s) required
Expression entered: %q
Evaluated as %d arg(s): %s]]):format(nArgs,expr,n,concat(res,',',1,n))
end
else
err = res
end
end
far.Message(err,'Error',nil,'wl')
until false
end
-- edit or remove object at obj[key]
local function editValue(obj, key, title, del)
if del then
local message = ('%s is a %s, do you want to remove it?')
:format(valfmt(key), type(obj[key]):upper())
if 1 == far.Message(message, 'REMOVE: ' .. title,';YesNo','w') then
obj[key] = nil
end
else
local v, t = valfmt(obj[key], 'edit')
if t == 'table' or t == 'function' then v = '' end
local prompt = ('%s is a %s, type new value as Lua code'):format(valfmt(key),t:upper())
local res = luaexp_prompt('EDIT: ' .. title, prompt, v, 1)
if res then
if t==O.bin_string then
res[1] = win.WideCharToMultiByte(win.Utf8ToUtf16(res[1]),win.GetACP())
end
obj[key] = res[1]
end
end
end
-- add new element to obj
local function insertValue(obj, title)
local res = luaexp_prompt('INSERT: ' .. title,
'type the key and value comma separated as Lua code',nil,2)
if res then obj[res[1]] = res[2] end
end
local function getfParamsNames(f)
if not jit then return '...' end--check _VERSION>"Lua 5.1"
local info = debug.getinfo(f)
local params = {}
for i=1,(info.nparams or 1000) do
params[i] = debug.getlocal(f,i)
or ("<%i>"):format(i) -- C?
end
if info.isvararg then params[#params+1] = '...' end
local paramstr = #params>0 and table.concat(params,', ') or '<none>'
return paramstr,params
end
-- show a menu whose items are associated with the members of given object
local function process(obj, title, action)
title = type(title)=="string" and title or ''
if action and brkeys[action] then brkeys[action]({obj}, 1, title); return end
local mprops = {Id = uuid, Bottom = 'F1, F3, F4, Del, Ctrl+M',
Flags={FMENU_SHOWAMPERSAND=1,FMENU_WRAPMODE=1}}
local otype = type(obj)
local item, index
-- some member types, need specific behavior:
-- tables are submenus
-- functions can be called
if otype == 'function' then
local args,n,expr = luaexp_prompt('CALL:'..title,
('arguments: %s (type as Lua code or leave empty)')
:format(getfParamsNames(obj)))
if not args then return end
-- overwrite the function object with its return values
local stat,res = getres(pcall(obj, unpack(args,1,n)))
if not stat then
far.Message(('%s\n CALL: %s (%s)\n argument(s): %d'..
(n>0 and ', evaluated as: %s' or ''))
:format(res,title,expr,n,concat(args,',',1,n)),'Error',nil,'wl')
return
end
obj = res
title = ('%s(%s)'):format(title,expr)
-- other values are simply displayed in a message box
elseif otype ~= 'table' then
local value = valfmt(obj,'view')
far.Message(value, title:gsub('&','&&',1), nil, value:match'\n' and 'l' or '')
return
end
-- show this menu level again after each return from a submenu/function call ...
repeat
local items = makeMenuItems(obj)
mprops.Title = title .. ' (' .. #items .. ')' .. (omit['function'] and '*' or '')
item, index = far.Menu(mprops, items, brkeys)
mprops.SelectIndex = index
-- show submenu/call function ...
if item then
local key = item.key or (index > 0 and items[index].key)
local childtitle = (title~='' and title .. '.' or title) .. tostring(key)
if item.key ~= nil then
process(obj[key], childtitle)
elseif item.action then
if "break"==item.action(obj, key, childtitle) then return end
end
end
-- until the user is bored and goes back ;)
until not item
end
local function getAllUpvalues(f,n)
local upvalues = {}
for i=1,(n or 1000) do -- n - debug.getinfo(f).nups
local k,v = debug.getupvalue (f, i)
if not k then n = i-1; break end
upvalues[k] = v
end
return upvalues, n
end
local function syncUpvalues(f,t,n)
for i = (n or -1),n and 1 or -1000,-1 do --debug.getinfo(f).nups
local k,v = debug.getupvalue (f, i)
if not k then break end
if t[k]~=v then
assert(k == debug.setupvalue (f, i, t[k]))
end
end
end
brkeys = {
{BreakKey = 'F9', action = function(info)
process(debug.getregistry(), 'debug.getregistry:')
end; name = 'registry'},
{BreakKey = 'Ctrl+Insert', action = function(obj, key)
far.CopyToClipboard ((valfmt(obj[key]))) --todo escape slashes etc
end},
{BreakKey = 'CtrlShift+Insert', action = function(obj, key)
far.CopyToClipboard ((valfmt(key,'list')))
end},
{BreakKey = 'CtrlAlt+Insert', action = function(obj, key, kpath)
far.CopyToClipboard (kpath:gsub('^_G%.','')..(valfmt(key,'list')))
end},
{BreakKey = 'Ctrl+Up', action = function(obj, key, kpath)
local f = obj[key]
if type(f) == 'function' and debug.getinfo(f).what~='C' then --todo
local t,n = getAllUpvalues(f)
if n>0 then
process(t, 'upvalues: ' .. kpath)
syncUpvalues(f,t,n)
end
end
end; name = 'upvalues'},
{BreakKey = 'Ctrl+Down', action = function(obj, key, kpath)
local f = obj[key]; local t = type(f)
if t=='function' or t=='userdata' or t=='thread' then
local env = debug.getfenv(f)
if (env~=_G or 1==far.Message('Show global environment?','_G',';OkCancel'))
and env and next(env) then
process(env, 'getfenv: ' .. kpath)
end
end
end; name = 'env'},
{BreakKey = 'Ctrl+Right', action = function(obj, key, kpath)
local f = obj[key]
if type(f) == 'function' then
local args,t = getfParamsNames(f)
if args:len()>0 then
process(t, 'params (f): ' .. kpath)
local name = debug.getinfo(f).name
--far.Message(('%s (%s)'):format(name or kpath,args), 'params')
end
end
end; name = 'params'},
{BreakKey = 'Alt+F4', action = function(obj, key, kpath)
local f = obj[key]
if type(f) == 'function' then
local info = debug.getinfo(f,'S')
local filename = info.source:match("^@(.+)$")
if filename then
editor.Editor(filename,nil,nil,nil,nil,nil,nil,info.linedefined)
end
end
end; name = 'edit'},
{BreakKey = 'F3', action = function(obj, key, kpath)
local f = obj[key]
if type(f) == 'function' then
process(debug.getinfo(f), 'debug.getinfo: ' .. kpath)
elseif type(f) == 'thread' then
far.Message(debug.traceback(f,"level 0",0):gsub('\n\t','\n '),'debug.traceback: ' .. kpath,nil,"l")
--far.Show('debug.traceback: ' .. kpath .. debug.traceback(f,", level 0",0))
end
end; name = 'info'},
{BreakKey = 'F4', action = function(obj, key, kpath)
return key ~= nil and editValue(obj, key, kpath)
end},
{BreakKey = 'Ctrl+F', action = function() omit['function'] = not omit['function'] end},
{BreakKey = 'Ctrl+T', action = function() O.tables_first = not O.tables_first end},
{BreakKey = 'Ctrl+I', action = function() O.ignore_case = not O.ignore_case end},
{BreakKey = 'Ctrl+M', action = function(obj, key, kpath)
local mt = key ~= nil and debug.getmetatable(obj[key])
return mt and process(mt, 'METATABLE: ' .. kpath)
end; name = 'mt'},
{BreakKey = 'DELETE', action = function(obj, key, kpath)
return key ~= nil and editValue(obj, key, kpath, true)
end},
{BreakKey = 'INSERT', action = function(obj, key, kpath)
insertValue(obj, kpath:sub(1, -(#tostring(key) + 2)))
end},
{BreakKey = 'F1', action = function() nfo:help() end},
{action=function(obj, key)
local addbrkeys = obj[key]
for i=1,#addbrkeys do
local bk = addbrkeys[i]
local BreakKey = bk.BreakKey
local pos
for i=1,#brkeys do if brkeys[i].BreakKey==BreakKey then pos = i; break end end
if pos then
brkeys[pos] = bk
else
table.insert(brkeys,bk)
if bk.name then brkeys[bk.name] = bk.action end
end
end
return 'break'
end; name='addBrKeys'}
}
-- if LuaJIT is used, maybe we can show some more function info
if jit then
funcinfo = require('jit.util').funcinfo
table.insert(brkeys, {BreakKey = 'Shift+F3', action = function(obj, key, kpath)
if key ~= nil and type(obj[key]) == 'function' then
process(funcinfo(obj[key]), 'jit.util.funcinfo: ' .. kpath)
end
end; name = 'jitinfo'})
end
for i=1,#brkeys do
local bk = brkeys[i]; if bk.name then brkeys[bk.name] = bk.action end
end
nfo.execute = function()
process(_G,'')
--require"le"(_G,'_G')
end
if Macro then
Macro { description = "Lua Explorer";
area="Common"; key="CtrlShiftF12"; action=nfo.execute
}
elseif _filename then
process(_G,'')
else--if ...=="le" then
return process
end
-- it's possible to call via lua:, e.g. from user menu:
-- lua:dofile(win.GetEnv("FARPROFILE")..[[\Macros\scripts\le.lua]])(_G,'_G')
-- lua:require"le"(_G,'_G')
local lua_explorer = require"le"
local function getAllLocals(level)
local locals = {}
for i=1,1000 do
local k,v = debug.getlocal (level+1, i)
if not k then return locals, i-1 end
locals[k] = v
end
end
local function syncLocals(level,t,n)
level = level + 1
for i=n,1,-1 do
local k,v = debug.getlocal (level, i)
if t[k]~=v then
assert(k == debug.setlocal (level, i, t[k]))
end
end
end
local function getVararg(level)
local vararg = {}
for i=1,1000 do
local k,v = debug.getlocal (level+1, -i)
if not k then return vararg end--(*vararg)
vararg[i] = v
end
end
local function syncVararg(level,t)
for i=1,#t do
local k,v = debug.getlocal (level+1, -i)
if v~=t[i] then debug.setlocal (level+1, -i, t[i]) end
end
end
local function getLocalsAndParams(level)
local locals,n = getAllLocals(level+1)
local info = debug.getinfo(level+1)
local vararg = getVararg(level+1)
vararg = vararg[1] and vararg
locals['(*vararg)'] = locals['(*vararg)'] or vararg or nil
local info = debug.getinfo(level+1)
local name = ('(*func: %s)'):format(info.name or '<noname>')
locals[name] = locals[name] or info.func
return locals,n,vararg
end
local function showLocals(level,shift)
--far.Show(level,shift)
if not shift then
shift = 0
for i = 1,1000 do
local info = debug.getinfo(i,'f')
if not info then break end
if info.func==lua_explorer then shift = i
elseif info.func==showTraceback then shift = i
end
end
if shift>900 then return end
end
level = level + shift
local info = debug.getinfo(level,'')
if not info then mf.beep() return end
local locals,n,vararg = getLocalsAndParams(level)
if n>0 or vararg then
lua_explorer(locals, ('locals [%d]: %s'):format(level-shift,info.name or 'main chunk'))
syncLocals(level,locals,n)
if vararg then syncVararg(level,vararg) end
return 'break'
end
end
local function ExpandBreakKeys(bkeys)
for i=1,#bkeys do
local t = bkeys[i]
if t.BreakKey:match("%w%s+%w") then
local g = t.BreakKey:gmatch("%S+")
t.BreakKey = g()
for key in g do
local new = {}; for k,v in pairs(t) do new[k]=v end
new.BreakKey = key
table.insert(bkeys,new)
end
end
end
end
local function showTraceback(level,shift)
if level then showLocals(level,shift); return end
local TB_bkeys = {
{BreakKey = 'F3', action = function(getinfo)
lua_explorer(getinfo, 'getinfo:')
end},
{BreakKey = 'RETURN', action = function(info,shift)
showLocals(info.level,shift)
end},
{BreakKey = 'Ctrl+Numpad2 Ctrl+Down', action = function(getinfo)
local f = getinfo.func
if not f then return end
lua_explorer(f,nil,'env')
end},
{BreakKey = 'Ctrl+Numpad8 Ctrl+Up', action = function(getinfo)
local f = getinfo.func
if not f then return end
lua_explorer(f,nil,'upvalues')
end},
{BreakKey = 'Alt+F4', action = function(getinfo)
local FileName = getinfo.source:match"^@(.+)"
if not FileName then far.Show(getinfo.source); return end
local flags = far.Flags.EN_NONE
editor.Editor(FileName,nil,nil,nil,nil,nil,flags,getinfo.linedefined)
end},
}
ExpandBreakKeys(TB_bkeys)
local stack = {}
local str = debug.traceback("",0):match("stack traceback:\n(.+)"):gmatch("[^\n]+")
local shift = 0
for i=0,1000 do
local info = debug.getinfo(i)
if not info then break end
info.level = i
info.text = str()
stack[i+1] = info
if info.func==lua_explorer or info.func==showTraceback then shift = i end
end
for i = 1,shift+1 do stack[i].hidden = true end
--if shift>0 then stack[1].hidden = true end
local props = {
Title="Traceback of the call stack:",
Bottom="Enter: locals | CtrlUp: upvalues | CtrlDown: env | Other: see help (F1)",
Flags={FMENU_SHOWAMPERSAND=1,FMENU_WRAPMODE=1},
}
local pos,level --= shift
repeat
props.SelectIndex=pos
level,pos = far.Menu(props,stack,TB_bkeys)
if level then
if level.action then
if "break"==level.action(stack[pos],shift) then break end
end
end
until not level
--return 'break'
end
local addbrkeys = {
{BreakKey = 'Ctrl+1', action = function() return showLocals(1) end; name = 'locals'},
{BreakKey = 'Ctrl+2', action = function() return showLocals(2) end; name = 'locals2'},
{BreakKey = 'Ctrl+3', action = function() return showLocals(3) end; name = 'locals3'},
{BreakKey = 'Ctrl+4', action = function() return showLocals(4) end; name = 'locals4'},
{BreakKey = 'Ctrl+5', action = function() return showLocals(5) end; name = 'locals5'},
{BreakKey = 'Ctrl+6', action = function() return showLocals(6) end; name = 'locals6'},
{BreakKey = 'Ctrl+7', action = function() return showLocals(7) end; name = 'locals7'},
{BreakKey = 'Ctrl+8', action = function() return showLocals(8) end; name = 'locals8'},
{BreakKey = 'Ctrl+9', action = function() return showLocals(9) end; name = 'locals9'},
{BreakKey = 'Ctrl+0', action = function() return showLocals(0) end;},
{BreakKey = 'OEM_3', action = function() return showTraceback() end;},
{BreakKey = 'Ctrl+G', action = function() lua_explorer(_G,'_G'); return "break" end},
{BreakKey = 'CtrlShift+F', action = function() lua_explorer(far.Flags,'far.Flags') end},
{BreakKey = 'CtrlShift+V', action = function() lua_explorer(win.GetVirtualKeys(),'VK') end},
{BreakKey = 'CtrlShift+U', action = function() lua_explorer(win.Uuid(win.Uuid()):upper()) end},
{BreakKey = 'CtrlShift+M', action = function() lua_explorer(debug.getregistry()._LOADED,'_LOADED') end},
}
lua_explorer(addbrkeys,nil,'addBrKeys')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment