Skip to content

Instantly share code, notes, and snippets.

@johnd0e
Last active October 28, 2024 02:41
Show Gist options
  • Select an option

  • Save johnd0e/5110ddfb3291928a7f484cd38f23ff87 to your computer and use it in GitHub Desktop.

Select an option

Save johnd0e/5110ddfb3291928a7f484cd38f23ff87 to your computer and use it in GitHub Desktop.
[Far macro] Lua Explorer „Advanced“

2.5

  • Поддержка массивов: значения с числовыми ключами отображаются в начале, ключи сортируются по возрастанию и выравниваются по правому краю.
  • Поддержка отображения возвращаемых значений и sparse-массивов (с nil).
  • Бинарные гуиды отображаются с псевдотипом #guid в текстовом виде, так же и редактируются.
  • Исправлено отображение длинных 16-ричных значений (например FCF_STYLEMASK)
  • Для числовых значений "похожих" на Unix time ('сейчас' ± год) отображается соответствующая дата.
  • Прочие косметические улучшения.

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.5";
author = "jd";
url = "https://forum.farmanager.com/viewtopic.php?f=60&t=7988";
id = "C61B1E8D-71D4-445C-85A6-35EA1D5B6EF3";
licence = [[
based on Lua Explorer by EGez https://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',
bin_guid = '#guid',
};
}
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 = [[
Shortcuts:
F4  Edit selected object
Del  Delete selected object
Ins  Add an object to current table
Ctrl+M  Show metatable
Ctrl+Shift+M  Show metatable (parent)
for functions:
Enter  Call function (params prompted)
F3  Show some function info
Shift+F3  Show jit.util.funcinfo (LuaJIT)
Alt+F4  Open function definition (if available) in editor
for threads:
F3  Show traceback info
Toggles:
Ctrl+F  Show/hide functions
Ctrl+T  Toggle sort by type
Ctrl+I  Toggle ignore case
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() or #val==16 and val==('\0'):rep(16) then
local guid = win.Uuid(val)
if guid then
val = guid:upper()
t = O.bin_guid
if mode=='edit' then
return ('win.Uuid"%s"'):format(val),t
end
else
local utf_8, err = win.Utf16ToUtf8(win.MultiByteToWideChar(val,win.GetACP()));
val = utf_8 or err
t = O.bin_string
if mode=='edit' then
return '-- '..val,t
end
end
end
if val:match'%z' or mode=='edit' then
val = ('%q'):format(val)
elseif mode~='view' and ((mode~='list' and mode~='key') or val=='' or val:sub(-1,-1)==' ') then
val = '"'..val..'"'
end
return val,t
elseif t=='number' then
local int = math.modf(val)==val and math.abs(val)~=math.huge
if not int or mode=='key' then
return tostring(val),t
end
local long = val / 0x100000000
long = math.modf(long)~=0 and ('%x'):format(long)
local hexFmt = '0x%08x'
local longFmt = long and '0x'..long..'%08x'
if mode=='edit' then
local fmt = (longFmt or '0x%x')..' --[[ %s ]]'
return fmt:format(val,val),t
end
local now = os.time()
local sYear = 365*24*60*60
if val>now-sYear and val<now+sYear then
local fmt = (longFmt or hexFmt)..' (%s, %s)'
return fmt:format(val, val, os.date('%c',val)),t
end
local fmt = (longFmt or hexFmt)..' (%s)'
return fmt:format(val,val),t
elseif t=='userdata' then
local isBit64 = bit64.type(val)
if isBit64 then
val = tostring(val):lower()
if mode=='edit' then
return ('bit64.new(%s)'):format(val),isBit64
else
return ('%18s (%s)'):format(val,isBit64),t
end
end
val = tostring(val)
else
val = tostring(val)
end
if mode=='list' then
for _,_type in ipairs{'function','table','thread','userdata'} do
if _type==t then
val = val:gsub(('^%s: '):format(t), '') --remove type from value
break
end
end
end
return val,t
end
-- create sorted menu items with associated keys
local LMtables = {}
for k,v in pairs(_G) do
if type(v)=='table' and k:match'^%u' then
LMtables[v] = true
end
end
-- make menu item for far.Menu(...)
local key_w = 30
-- https://learn.microsoft.com/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions
local item_fmt = ('%%-%s.%ss'):format(key_w,key_w)..'%s%-8s │%-25s'
local item_numkey_fmt = ('%%%ss '):format(key_w-1)..'│%-8s │%-25s'
local function makeMenuItems(obj,poskey)
local max = nil
local function makeItem(key, sval, vt)
local k,kt = valfmt(key,'key')
local text
if kt=='number' then
text = item_numkey_fmt:format(k, vt, sval)
if key>0 then
max = max and math.max(key,max) or key
end
else
local border = k:len()<=key_w and '│' or '┤'
text = item_fmt:format(k, border, vt, sval)
end
return {
key = key,
keytype = kt,
text = text,
type = vt,
checked = O.chars[vt],
selected= key==poskey,
}
end
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 ;)
--?? use list of APanel Area BM CmdLine Dlg Drv Editor Far Help Menu Mouse Object PPanel Panel Plugin Viewer
if LMtables[obj] and getmetatable(obj) then
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
local limit = 100
local n = rawget(obj,'n')
n = type(n)=='number' and n
if max and (max<10 or n and n>=max) then
for i=1, n or max do
if obj[i]==nil then
if limit==0 then
table.insert(items, makeItem(i, "Maximal sparse items count reached!", "nil"))
break
end
table.insert(items, makeItem(i, "", "nil"))
limit = limit-1
end
end
end
table.sort(items, function(v1, v2)
if (v1.keytype=='number')~=(v2.keytype=='number') then
return v1.keytype=='number'
elseif v1.keytype=='number' then -- actually both are numbers
return v1.key<v2.key
elseif 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
local function removeValue(obj, key, title)
local message = ('%s is a `%s`, do you want to remove it?')
:format(valfmt(key), type(obj[key]))
if 1==far.Message(message, 'REMOVE: '..title,';YesNo','w') then
obj[key] = nil
end
end
-- edit object at obj[key]
local function editValue(obj, key, title)
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,'key'),t)
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
-- 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)
local options = {}
if type(title)=='table' then
options = title
title = options.title or options.kpath or ''
assert(type(title)=='string', 'title must ne string')
else
options.kpath = title
title = title or ''
end
if action then
if not brkeys[action] then return end
brkeys[action](obj, options.key, options.kpath, options.path)
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; value,otype = valfmt(obj,'view')
if title~='' then
title = title:gsub('&','&&',1)..": "
end
far.Message(value, title..otype, nil, type(value)=='string' and 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, options.key)
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) or nil -- index==0 if item is breakkey and obj is empty
local childtitle = title
if key~=nil then
if type(key)=='string' and key:match'^[%a_][%w_]-$' then
childtitle = title..'.'..key
else
childtitle = title..('[%s]'):format(type(key)=='string' and ('"%s"'):format(key) or tostring(key))
end
end
if item.key~=nil then
if select(2, process(obj[key], childtitle))=='quit' then
return nil, 'quit'
end
elseif item.action and (key~=nil or item.nokey) then -- .nokey means that action does not require key
local ret = item.action(obj, key, childtitle, title)
if ret=='break' then
return
elseif ret=='quit' then
return nil, 'quit'
end
end
end
-- until the user is bored and goes back ;)
until not item
end
brkeys = {
{BreakKey='Ctrl+Insert', action=function(obj, key)
far.CopyToClipboard ((valfmt(obj[key]))) --todo escape slashes etc
end},
{BreakKey='CtrlShift+Insert', action=function(_, key)
far.CopyToClipboard ((valfmt(key,'key')))
end},
{BreakKey='CtrlAlt+Insert', action=function(_, key, kpath)
far.CopyToClipboard (kpath:gsub('^_G%.','')..(valfmt(key,'key')))
end},
{BreakKey='Alt+F4', action=function(obj, key)
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 editValue(obj, key, kpath)
end},
{BreakKey='Ctrl+F', action=function() omit['function'] = not omit['function'] end; nokey=true},
{BreakKey='Ctrl+T', action=function() O.tables_first = not O.tables_first end; nokey=true},
{BreakKey='Ctrl+I', action=function() O.ignore_case = not O.ignore_case end; nokey=true},
{BreakKey='Ctrl+M', action=function(obj, key, kpath)
local mt = debug.getmetatable(obj[key])
return mt and process(mt, 'METATABLE: '..kpath)
end; nokey=true; name='mt'},
{BreakKey='CtrlShift+M', action=function(obj,_,_,path)
return process(obj,path,'mt')
end; nokey=true},
{BreakKey='DELETE', action=function(obj, key, kpath)
return removeValue(obj, key, kpath)
end},
{BreakKey='INSERT', action=function(obj, key, kpath)
insertValue(obj, kpath:sub(1, -(#tostring(key) + 2)))
end; nokey=true},
{BreakKey='F1', action=function() nfo:help() end; nokey=true},
{action=function(obj)
local addbrkeys = obj
for i=1,#addbrkeys do
local bk = addbrkeys[i]
local BreakKey = bk.BreakKey
local pos
for j=1,#brkeys do if brkeys[j].BreakKey==BreakKey then pos = j; 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
local funcinfo = require('jit.util').funcinfo
table.insert(brkeys, {BreakKey='Shift+F3', action=function(obj, key, kpath)
if type(obj[key])=='function' then
process(funcinfo(obj[key]), 'jit.util.funcinfo: '..kpath)
end
end;})
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 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
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 _,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 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 showTraceback
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
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 last
local addbrkeys = {
{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
lua_explorer(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
lua_explorer(env, 'getfenv: '..kpath)
end
end
end; name='env'},
{BreakKey='F9', action=function()
lua_explorer(debug.getregistry(), 'debug.getregistry:')
end; name='registry'; nokey=true},
{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='F10', action=function(obj, key, _, path)
last = {obj=obj, key=key, title=path}
return 'quit'
end},
}
lua_explorer(addbrkeys,nil,'addBrKeys')
Macro { description="Lua Explorer: recall";
area="Common"; key="CtrlF10:Hold";
id="36043A1B-CC3F-43CE-B8A2-4E861C8E52E7";
action=function()
if last then
lua_explorer(last.obj, last)
else
lua_explorer(_G,'_G')
end
end;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment