Repository :
https://github.com/FarGroup/FarManager
On branch : master
Link :
https://github.com/FarGroup/FarManager/commit/5180a058d3781dda1bdb3633084efd59d5706937
>---------------------------------------------------------------
commit 5180a058d3781dda1bdb3633084efd59d5706937
Author: Michael Z. Kadaner <
MKad...@users.noreply.github.com>
Date: Sun Oct 5 20:00:21 2025 -0400
gh-1006: Menu.SetItemExtendedData
1. gh-1006: Macro API to set menu item extended data: `Menu.SetItemExtendedData`.
2. Updated `Edit.SyncEditorAndFindAllMenu.lua` to demonstrate usage of this API.
>---------------------------------------------------------------
5180a058d3781dda1bdb3633084efd59d5706937
enc/changelog | 4 +
enc/enc_lua/macroapi_manual.en.tsi | 2 +
enc/enc_lua/macroapi_manual.pl.tsi | 2 +
enc/enc_lua/macroapi_manual.ru.tsi | 2 +
enc/enc_rus/meta/defs/macroopcode.html | 5 +
.../meta/macro/macrocmd/prop_func/menus.html | 27 +++++
.../Macros/Edit.SyncEditorAndFindAllMenu.lua | 61 ++++++++--
far/changelog | 7 ++
far/dialog.cpp | 1 +
far/editor.cpp | 111 +++++++++++++++--
far/editor.hpp | 1 +
far/macro.cpp | 3 +-
far/macroapi.cpp | 132 ++++++++++++++++-----
far/macroopcode.hpp | 1 +
far/plugin.hpp | 1 +
far/vbuild.m4 | 2 +-
far/vmenu.cpp | 21 +++-
far/vmenu.hpp | 10 +-
plugins/common/unicode/plugin.hpp | 1 +
plugins/luamacro/_globalinfo.lua | 4 +-
plugins/luamacro/api.lua | 1 +
plugins/luamacro/changelog | 4 +
plugins/luamacro/luafar/lf_luamacro.c | 16 +++
plugins/luamacro/luafar/lf_version.h | 2 +-
plugins/luamacro/macrotest.lua | 1 +
25 files changed, 358 insertions(+), 64 deletions(-)
diff --git a/enc/changelog b/enc/changelog
index 141df20c8..b2ae449b8 100644
--- a/enc/changelog
+++ b/enc/changelog
@@ -1,3 +1,7 @@
+MZK 2026-04-18 10:27:01-04:00
+
+1. gh-1006: Описание Menu.SetItemExtendedData.
+
MZK 2025-11-21 20:33:06-05:00
1. Уточнение описания Menu.GetItemExtendedData.
diff --git a/enc/enc_lua/macroapi_manual.en.tsi b/enc/enc_lua/macroapi_manual.en.tsi
index 2eb01de1c..ada15be79 100644
--- a/enc/enc_lua/macroapi_manual.en.tsi
+++ b/enc/enc_lua/macroapi_manual.en.tsi
@@ -1000,6 +1000,7 @@ article id=53 dt=Text ctime=3553252746 mtime=
3607109038 nm=Menu
#_ Select
#_ Show
#_ GetItemExtendedData
+#_ SetItemExtendedData
#_
#_@@@
#_{filterstr}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.FilterStr
@@ -1010,6 +1011,7 @@ article id=53 dt=Text ctime=3553252746 mtime=
3607109038 nm=Menu
#_{id}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#
Menu.Info.Id
#_{itemstatus}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.ItemStatus
#_{select}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.Select
+#_{setitemextendeddata}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.SetItemExtendedData
#_{show}: $(ENC_URL)/macro/macrocmd/prop_func/general.html#Menu.Show
#_{value}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.Value
#_
diff --git a/enc/enc_lua/macroapi_manual.pl.tsi b/enc/enc_lua/macroapi_manual.pl.tsi
index 3a4aebaec..5bc51273b 100644
--- a/enc/enc_lua/macroapi_manual.pl.tsi
+++ b/enc/enc_lua/macroapi_manual.pl.tsi
@@ -998,6 +998,7 @@ article id=53 dt=Text ctime=3553252746 mtime=
3607109038 nm=Menu
#_ Select
#_ Show
#_ GetItemExtendedData
+#_ SetItemExtendedData
#_
#_@@@
#_{filterstr}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.FilterStr
@@ -1008,6 +1009,7 @@ article id=53 dt=Text ctime=3553252746 mtime=
3607109038 nm=Menu
#_{id}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#
Menu.Info.Id
#_{itemstatus}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.ItemStatus
#_{select}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.Select
+#_{setitemextendeddata}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.SetItemExtendedData
#_{show}: $(ENC_URL)/macro/macrocmd/prop_func/general.html#Menu.Show
#_{value}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.Value
#_
diff --git a/enc/enc_lua/macroapi_manual.ru.tsi b/enc/enc_lua/macroapi_manual.ru.tsi
index 6c5120380..f8cae42c8 100644
--- a/enc/enc_lua/macroapi_manual.ru.tsi
+++ b/enc/enc_lua/macroapi_manual.ru.tsi
@@ -992,6 +992,7 @@ article id=53 dt=Text ctime=3553252746 mtime=
3607109106 nm=Menu
#_ Select
#_ Show
#_ GetItemExtendedData
+#_ SetItemExtendedData
#_
#_@@@
#_{filterstr}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.FilterStr
@@ -1002,6 +1003,7 @@ article id=53 dt=Text ctime=3553252746 mtime=
3607109106 nm=Menu
#_{id}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#
Menu.Info.Id
#_{itemstatus}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.ItemStatus
#_{select}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.Select
+#_{setitemextendeddata}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.SetItemExtendedData
#_{show}: $(ENC_URL)/macro/macrocmd/prop_func/general.html#Menu.Show
#_{value}: $(ENC_URL)/macro/macrocmd/prop_func/menus.html#Menu.Value
#_
diff --git a/enc/enc_rus/meta/defs/macroopcode.html b/enc/enc_rus/meta/defs/macroopcode.html
index 8e022701f..a80d45a9a 100644
--- a/enc/enc_rus/meta/defs/macroopcode.html
+++ b/enc/enc_rus/meta/defs/macroopcode.html
@@ -468,6 +468,11 @@
<td>0x80C6B</td>
<td>T=Menu.GetItemExtendedData([hDlg,][Pos])</td>
</tr>
+ <tr>
+ <td>MCODE_F_MENU_SETEXTENDEDDATA</td>
+ <td>0x80C6C</td>
+ <td>B=Menu.SetItemExtendedData([hDlg,][Pos,]T)</td>
+ </tr>
<tr>
<td>MCODE_F_DLG_SETFOCUS</td>
<td>0x80C57</td>
diff --git a/enc/enc_rus/meta/macro/macrocmd/prop_func/menus.html b/enc/enc_rus/meta/macro/macrocmd/prop_func/menus.html
index a725a8945..9d4c22340 100644
--- a/enc/enc_rus/meta/macro/macrocmd/prop_func/menus.html
+++ b/enc/enc_rus/meta/macro/macrocmd/prop_func/menus.html
@@ -296,6 +296,33 @@ $End</code></pre>
<pre class="codesample"><code>F11 $if(menu.select("Advanced compare",0) > 0) Enter $end</code></pre>
</td>
</tr>
+ <tr>
+ <td>B=<dfn><a name="menu.setitemextendeddata">menu.setitemextendeddata</a></dfn>([hDlg,][Pos,]T)</td>
+ <td><p>Устанавливает дополнительные данные, связанные с указанным (<var>Pos</var>) или текущим (если параметр <var>Pos</var> не указан) элементом меню.</p>
+ <p>Необязательный параметр <var>hDlg</var> идентифицирует меню, с которым выполняется операция. Если этот параметр не указан, то операция выполняется с текущим объектом.</p>
+ <p>Возвращает <var>nil</var>, если:</p>
+ <ul class="nomarker">
+ <li>Объект, с которым выполняется операция, не является меню.</li>
+ <li>Параметр <var>Pos</var> не валиден (меньше или равен 0 или больше количества пунктов в меню).</li>
+ <li>Меню, с которым выполняется операция, не поддерживает дополнительные данные.</li>
+ <li>Последний параметр не является таблицей.</li>
+ </ul>
+ <p>Таблица <var>T</var> содержит дополнительные данные, которые должны быть установлены.</p>
+ <p>Если дополнительные данные были успешно установлены, то возвращается <var>True</var>.
+ Если таблица содержит данные, несовместимые с меню, для которого эта функция вызвана, то возвращается <var>False</var>.</p>
+ <p>Количество значений в таблице, ключи и типы этих значений зависят от меню, для которого эта функция вызвана.</p>
+ <p>Если некоторое меню принимает определённый набор значений (ключи и типы), то гарантируется, что это меню будет продолжать работать с этим набором значений и будущем.</p>
+ <p>Например, для меню всех вхождений образца поиска во встроенном редакторе ожидаются три числа:
+ <table class="cont2 params lite tiny">
+ <col class="value">
+ <col>
+ <tr><th><var>Ключ</var></th><th>Описание</th></tr>
+ <tr><td>Line</td><td>Номер строки в исходном файле, где найден образец</td></tr>
+ <tr><td>Position</td><td>Позиция образца в строке<td></td></tr>
+ <tr><td>Length</td><td>Длина найденного образца</td></tr>
+ </table>
+ </td>
+ </tr>
</table>
<h3>Замечания</h3>
diff --git a/extra/Addons/Macros/Edit.SyncEditorAndFindAllMenu.lua b/extra/Addons/Macros/Edit.SyncEditorAndFindAllMenu.lua
index 90fe57956..e068d395f 100644
--- a/extra/Addons/Macros/Edit.SyncEditorAndFindAllMenu.lua
+++ b/extra/Addons/Macros/Edit.SyncEditorAndFindAllMenu.lua
@@ -12,10 +12,16 @@
-- The color used to highlight the pattern.
local HighlightColor = 0x5f
--
--- If true, after menu is opened, selects the menu item corresponding
--- to the first search pattern found below the cursor position.
+-- If true, after the menu is opened, selects the menu item corresponding
+-- to the first occurrence of the search pattern following the cursor position.
+-- If all occurrences of the search pattern precede the cursor position,
+-- wraps around and selects the first menu item.
local PositionMenuOnStart = true
--
+-- If true, and the cursor is not on a line containing an occurrence of the search pattern,
+-- after the menu is opened, inserts the current line as an "anchor" menu item.
+local InsertAnchorItem = true
+--
-- If true, highlights the search patterns in the text while the user moves around the menu items.
-- If false, highlights the search pattern on Ctrl+Enter.
local TrackMenu = true
@@ -46,17 +52,13 @@ local function LowerBound(N, Target, GetData, Less)
end
end
- if Lo == N + 1 then
- return 1 -- all keys < Target -- wrap around
- else
- return Lo
- end
+ return Lo
+
end
local function CoordinatesLess(A, B)
if A.Line ~= B.Line then return A.Line < B.Line end
- if A.Position ~= B.Position then return A.Position < B.Position end
- return A.Length < B.Length
+ return A.Position + A.Length < B.Position
end
local function AddColor()
@@ -105,14 +107,49 @@ local function HighlighText()
editor.Redraw(EditorInfo.EditorID);
end
+-- Will be removed when items do not contain found coordinates inline
+local function GetLineNumberFixedColumns(FarDialogEvent, LineNumber)
+ local ItemText = FarDialogEvent.hDlg:send(F.DM_LISTGETITEM, 1, 1).Text
+
+ local LineNumberColumnWidth = 0
+ if ItemText then
+ LineNumberColumnWidth = #(ItemText:match("^(%s*%d+)%s") or "")
+ end
+
+ return string.format("%" .. LineNumberColumnWidth .. "d ", LineNumber)
+end
+
local function SetupMenuAndEditor(FarDialogEvent)
local SelectPos = 1
- if PositionMenuOnStart then
- SelectPos = LowerBound(FarDialogEvent.hDlg:send(F.DM_LISTINFO, 1).ItemsNumber,
+
+ if PositionMenuOnStart or InsertAnchorItem then
+ -- Find the item corresponding to the first occurrence
+ -- of the search pattern following the cursor position.
+ local ItemsNumber = FarDialogEvent.hDlg:send(F.DM_LISTINFO, 1).ItemsNumber
+ SelectPos = LowerBound(ItemsNumber,
{ Line = EditorInfo.CurLine, Position = EditorInfo.CurPos, Length = 0 },
function(I) return Menu.GetItemExtendedData(FarDialogEvent.hDlg, I) end,
CoordinatesLess)
- FarDialogEvent.hDlg:send(F.DM_LISTSETCURPOS, 1, { SelectPos = SelectPos })
+
+ if InsertAnchorItem then
+ if SelectPos == ItemsNumber + 1 -- All occurrences precede the cursor position
+ -- or the current editor line does not containing an occurrence
+ or Menu.GetItemExtendedData(FarDialogEvent.hDlg, SelectPos).Line ~= EditorInfo.CurLine then
+ FarDialogEvent.hDlg:send(F.DM_LISTINSERT,
+ 1,
+ { Index = SelectPos,
+ Text = GetLineNumberFixedColumns(FarDialogEvent, EditorInfo.CurLine) .. Editor.GetStr(EditorInfo.CurLine) })
+ Menu.SetItemExtendedData(FarDialogEvent.hDlg, SelectPos, { Line = EditorInfo.CurLine, Position = EditorInfo.CurPos, Length = 0 })
+ ItemsNumber = ItemsNumber + 1
+ end
+ end
+
+ if PositionMenuOnStart then
+ if SelectPos == ItemsNumber + 1 then -- All occurrences precede the cursor position
+ SelectPos = 1; -- Wrap around to the first menu item
+ end
+ FarDialogEvent.hDlg:send(F.DM_LISTSETCURPOS, 1, { SelectPos = SelectPos })
+ end
end
LastSeenItemData = Menu.GetItemExtendedData(FarDialogEvent.hDlg, SelectPos)
if TrackMenu then HighlighText() end
diff --git a/far/changelog b/far/changelog
index a226efcd2..cc431c75f 100644
--- a/far/changelog
+++ b/far/changelog
@@ -1,3 +1,10 @@
+--------------------------------------------------------------------------------
+MZK 2026-04-18 10:27:01-04:00 - build 6678
+
+1. gh-1006: Macro API to set menu item extended data: Menu.SetItemExtendedData.
+
+2. Updated Edit.SyncEditorAndFindAllMenu.lua to demonstrate usage of this API.
+
--------------------------------------------------------------------------------
drkns 2026-04-13 21:37:42+01:00 - build 6677
diff --git a/far/dialog.cpp b/far/dialog.cpp
index 2bf8dc0e2..dc500eff4 100644
--- a/far/dialog.cpp
+++ b/far/dialog.cpp
@@ -2247,6 +2247,7 @@ long long Dialog::VMProcess(int OpCode,void *vParam,long long iParam)
case MCODE_F_MENU_FILTERSTR:
case MCODE_V_MENU_HORIZONTALALIGNMENT:
case MCODE_F_MENU_GETEXTENDEDDATA:
+ case MCODE_F_MENU_SETEXTENDEDDATA:
{
if (GetDropDownOpened() || Items[m_FocusPos].Type == DI_LISTBOX)
{
diff --git a/far/editor.cpp b/far/editor.cpp
index ac0198616..8057f6c1e 100644
--- a/far/editor.cpp
+++ b/far/editor.cpp
@@ -3437,6 +3437,86 @@ namespace
return static_cast<short>(std::log10(num)) + 1;
}
+ VMenu::extended_item_data coord_to_extended_data(const FindCoord Coords)
+ {
+ return {
+ { L"Line", Coords.Line + 1 },
+ { L"Position", Coords.Pos + 1 },
+ { L"Length", Coords.SearchLen },
+ };
+ }
+
+ std::optional<FindCoord> coord_from_extended_data(const VMenu::extended_item_data& ExtendedData, const long long LinesSize)
+ {
+ const auto AsString{
+ [](const FarMacroValue& Value) -> std::optional<string>
+ {
+ switch (Value.Type)
+ {
+ case FMVT_STRING: return Value.String;
+ case FMVT_MBSTRING: return encoding::utf8_or_ansi::get_chars(Value.MBString);
+ default: return std::nullopt;
+ }
+ }
+ };
+ const auto AsInt{
+ [](const FarMacroValue& Value, const long long LowerBound, const long long UpperBound) -> std::optional<int>
+ {
+ const auto IntOrNullopt
+ {
+ [LowerBound, UpperBound](long long Integer)
+ {
+ return in_closed_range(LowerBound, Integer, UpperBound) ? std::optional<int>{ Integer } : std::nullopt;
+ }
+ };
+ switch (Value.Type)
+ {
+ case FMVT_INTEGER: return IntOrNullopt(Value.Integer);
+ case FMVT_DOUBLE: return IntOrNullopt(Value.Double);
+ default: return std::nullopt;
+ }
+ }
+ };
+
+ std::optional<int> MaybeLine;
+ std::optional<int> MaybePos;
+ std::optional<int> MaybeSearchLen;
+
+ for (const auto& [Key, Value] : ExtendedData)
+ {
+ const auto KeyStr{ AsString(Key) };
+ if (!KeyStr) return std::nullopt;
+
+ if (KeyStr == L"Line"sv)
+ {
+ MaybeLine = AsInt(Value, 1, LinesSize);
+ if (MaybeLine) continue;
+ return std::nullopt;
+ }
+ if (KeyStr == L"Position"sv)
+ {
+ MaybePos = AsInt(Value, 1, std::numeric_limits<int>::max());
+ if (MaybePos) continue;
+ return std::nullopt;
+ }
+ if (KeyStr == L"Length"sv)
+ {
+ MaybeSearchLen = AsInt(Value, 0, std::numeric_limits<int>::max());
+ if (MaybeSearchLen) continue;
+ return std::nullopt;
+ }
+ }
+
+ if (!(MaybeLine && MaybePos && MaybeSearchLen)) return std::nullopt;
+
+ return FindCoord
+ {
+ .Line = *MaybeLine - 1,
+ .Pos = *MaybePos - 1,
+ .SearchLen = *MaybeSearchLen
+ };
+ }
+
class find_all_list
{
public:
@@ -3463,7 +3543,7 @@ namespace
m_MaxFoundPos = std::max(m_MaxFoundPos, FoundCoords.Pos);
}
- void make_ready()
+ void make_ready(const Editor& ParentEditor)
{
m_MenuY2 = std::min(ScrY, m_MenuY1 + std::min(static_cast<int>(m_Menu->size()), 10) + 2);
@@ -3494,16 +3574,27 @@ namespace
},
segment::ray(ItemTextStart)
);
- m_Menu->ListBox().RegisterExtendedDataProvider([](const menu_item_ex& Item)
+ m_Menu->ListBox().RegisterExtendedDataProvider(
+ [](const menu_item_ex& Item, VMenu::extended_item_data& ExtendedData)
{
- const auto Coord{ std::any_cast<FindCoord>(Item.ComplexUserData) };
+ if (const auto* Coord{ std::any_cast<FindCoord>(&Item.ComplexUserData) })
+ {
+ ExtendedData = coord_to_extended_data(*Coord);
+ return true;
+ }
- return VMenu::extended_item_data{
- { L"Line", Coord.Line + 1 },
- { L"Position", Coord.Pos + 1 },
- { L"Length", Coord.SearchLen },
- };
- });
+ return false;
+ },
+ [&](menu_item_ex& Item, const VMenu::extended_item_data& ExtendedData)
+ {
+ if (const auto Coord{ coord_from_extended_data(ExtendedData, ParentEditor.GetSize()) })
+ {
+ Item.ComplexUserData = *Coord;
+ return true;
+ }
+ return false;
+ }
+ );
}
void toggle_zoom()
@@ -3903,7 +3994,7 @@ void Editor::DoSearchReplace(const SearchReplaceDisposition Disposition)
if(FindAllList && MatchFound)
{
- FindAllList->make_ready();
+ FindAllList->make_ready(*this);
enum class save_to_new_editor { none, all, matching_filter };
auto SaveToNewEditor{ save_to_new_editor::none };
diff --git a/far/editor.hpp b/far/editor.hpp
index 57f3f868f..e4bf46e9b 100644
--- a/far/editor.hpp
+++ b/far/editor.hpp
@@ -76,6 +76,7 @@ public:
void SetStartPos(int LineNum, int CharNum);
bool IsModified() const;
long long GetCurPos(bool file_pos = false, bool add_bom = false) const;
+ size_t GetSize() const { return Lines.size(); }
int EditorControl(int Command, intptr_t Param1, void *Param2);
void SetOptions(const Options::EditorOptions& Options);
void SetTabSize(int NewSize);
diff --git a/far/macro.cpp b/far/macro.cpp
index e0f1716d3..0c93db359 100644
--- a/far/macro.cpp
+++ b/far/macro.cpp
@@ -182,7 +182,8 @@ static_assert(MCODE_F_KEYMACRO == 0x80C68); // Набор прост
static_assert(MCODE_F_FAR_GETCONFIG == 0x80C69); // V=Far.GetConfig(Key,Name)
static_assert(MCODE_F_MACROSETTINGS == 0x80C6A); // Диалог редактирования макроса
static_assert(MCODE_F_MENU_GETEXTENDEDDATA == 0x80C6B); // T=Menu.GetItemExtendedData([hDlg,][Pos])
-static_assert(MCODE_F_LAST == 0x80C6C); // marker
+static_assert(MCODE_F_MENU_SETEXTENDEDDATA == 0x80C6C); // B=Menu.SetItemExtendedData([hDlg,][Pos,]T)
+static_assert(MCODE_F_LAST == 0x80C6D); // marker
// булевые переменные - различные состояния
static_assert(MCODE_C_AREA_OTHER == 0x80400); // Режим копирования текста с экрана, вертикальные меню
diff --git a/far/macroapi.cpp b/far/macroapi.cpp
index 3105ce291..80a9af1de 100644
--- a/far/macroapi.cpp
+++ b/far/macroapi.cpp
@@ -145,9 +145,11 @@ public:
void PushError(const wchar_t* str) const;
void PushValue(long long Int) const;
void PushValue(const TVar& Var) const;
+ void PushValue(const FarMacroValue& Value) const { SendValue(Value); }
void SetField(const FarMacroValue &Key, const FarMacroValue &Value, int Pos) const;
int StackGetTop() const;
- TVar GetTable(int pos, const TVar &Key) const;
+ FarMacroValue GetTable(int pos, const FarMacroValue& Key) const;
+ std::pair<FarMacroValue, FarMacroValue> Next(int pos) const;
template<typename T> requires std::integral<T> || std::is_enum_v<T>
void PushValue(T const Value) const
@@ -222,6 +224,7 @@ public:
private:
void fattrFuncImpl(int Type) const;
void SendValue(const FarMacroValue &val) const;
+ void SendValueN(const FarMacroValue *vals, size_t size) const;
FarMacroCall* mData;
};
@@ -231,6 +234,11 @@ void FarMacroApi::SendValue(const FarMacroValue &val) const
mData->Callback(mData->CallbackData, const_cast<FarMacroValue*>(&val), 1);
}
+void FarMacroApi::SendValueN(const FarMacroValue *vals, size_t size) const
+{
+ mData->Callback(mData->CallbackData, const_cast<FarMacroValue*>(vals), size);
+}
+
void FarMacroApi::PushError(const wchar_t *str) const
{
FarMacroValue val(FMVT_ERROR);
@@ -271,14 +279,20 @@ int FarMacroApi::StackGetTop() const
return val.Integer;
}
-TVar FarMacroApi::GetTable(int pos, const TVar &Key) const
+FarMacroValue FarMacroApi::GetTable(int pos, const FarMacroValue& Key) const
{
- pos = pos > 0 ? pos : StackGetTop() + 1 + pos;
FarMacroValue Res(FMVT_GETTABLE, pos);
- PushValue(Key);
+ SendValue(Key);
SendValue(Res);
StackPop(1);
- return Convert2TVar(Res);
+ return Res;
+}
+
+std::pair<FarMacroValue, FarMacroValue> FarMacroApi::Next(int pos) const
+{
+ std::array Res{ FarMacroValue{ FMVT_NEXT, pos }, FarMacroValue{} };
+ SendValueN(Res.data(), Res.size());
+ return Res;
}
std::vector<TVar> FarMacroApi::parseParams(size_t Count) const
@@ -343,6 +357,41 @@ static bool is_active_panel_code(intptr_t const Code)
}
}
+static Dialog* get_optional_dialog_param(std::vector<TVar>::const_iterator& ParamIter, const FARMACROAREA Area, const window_ptr CurrentWindow)
+{
+ if (ParamIter->isDialog())
+ return (ParamIter++)->asDialog();
+
+ if (IsMenuOrDialogArea(Area) && CurrentWindow)
+ return dynamic_cast<Dialog*>(CurrentWindow.get());
+
+ return {};
+}
+
+static std::optional<long long> get_optional_integer_param(std::vector<TVar>::const_iterator& ParamIter)
+{
+ switch (ParamIter->type())
+ {
+ using enum TVar::Type;
+
+ case Integer:
+ case Double:
+ case String:
+ return (ParamIter++)->asInteger();
+
+ default:
+ return {};
+ }
+}
+
+static std::optional<long long> get_optional_table_pos_param(std::vector<TVar>::const_iterator& ParamIter)
+{
+ if (ParamIter->isTable())
+ return (ParamIter++)->asInteger();
+
+ return {};
+}
+
void KeyMacro::CallFar(intptr_t CheckCode, FarMacroCall* Data)
{
os::fs::attributes FileAttr = INVALID_FILE_ATTRIBUTES;
@@ -1204,31 +1253,63 @@ void KeyMacro::CallFar(intptr_t CheckCode, FarMacroCall* Data)
case MCODE_F_MENU_GETEXTENDEDDATA: // T=Menu.GetItemExtendedData([hDlg,][Pos])
{
const auto Params{ api.parseParams(2) };
- auto Nidx = 0;
- Dialog* Dlg{};
- if(Params[0].isDialog())
+ auto ParamIter{ Params.cbegin() };
+ auto Dlg{ get_optional_dialog_param(ParamIter, GetArea(), CurrentWindow) };
+ const auto N{ get_optional_integer_param(ParamIter).value_or(0) - 1 };
+
+ if (!Dlg)
{
- Nidx = 1;
- Dlg = Params[0].asDialog();
+ api.PushNil();
+ return;
}
- else if(IsMenuOrDialogArea(GetArea()) && CurrentWindow)
+
+ if (VMenu::extended_item_data ExtendedData; Dlg->VMProcess(CheckCode, &ExtendedData, N) == 1)
{
- Dlg = dynamic_cast<Dialog*>(CurrentWindow.get());
+ api.PushTable();
+ for (const auto& [Key, Value] : ExtendedData)
+ {
+ // Here (-3) is the position of the table on Lua stack after SetField will have pushed Key and Value
+ api.SetField(Key, Value, -3);
+ }
+ return;
}
- if (Dlg)
+
+ api.PushNil();
+ return;
+ }
+
+ case MCODE_F_MENU_SETEXTENDEDDATA: // B=Menu.SetItemExtendedData([hDlg,][Pos,]T)
+ {
+ const auto Params{ api.parseParams(3) };
+ auto ParamIter{ Params.cbegin() };
+ auto Dlg{ get_optional_dialog_param(ParamIter, GetArea(), CurrentWindow) };
+ const auto N{ get_optional_integer_param(ParamIter).value_or(0) - 1 };
+ const auto TPos{ get_optional_table_pos_param(ParamIter) };
+
+ if (!Dlg || !TPos)
{
- const auto N{ Params[Nidx].isUnknown() ? -1 : Params[Nidx].asInteger() - 1 };
+ api.PushNil();
+ return;
+ }
- if (VMenu::extended_item_data ExtendedData; Dlg->VMProcess(CheckCode, &ExtendedData, N) == 1)
- {
- api.PushTable();
- for (const auto& [Key, Value] : ExtendedData)
- {
- api.SetField(Key, Value, -3);
- }
- return;
- }
+ VMenu::extended_item_data ExtendedData;
+
+ api.PushNil();
+ while (true)
+ {
+ auto KeyValue{ api.Next(*TPos) };
+ if (KeyValue.first.Type == FMVT_NIL) break;
+ api.StackPop(2);
+ api.PushValue(KeyValue.first);
+ ExtendedData.emplace_back(KeyValue);
}
+
+ if (const auto Ret{ Dlg->VMProcess(CheckCode, &ExtendedData, N) }; Ret >= 0)
+ {
+ api.PushBoolean(!!Ret);
+ return;
+ }
+
api.PushNil();
return;
}
@@ -1508,11 +1589,6 @@ void FarMacroApi::maxFunc() const
{
const auto Params = parseParams(2);
PushValue(std::max(Params[0], Params[1]));
-/*
- const auto &tbl = Params[0];
- if (tbl.isTable())
- PushValue(GetTable(tbl.asInteger(), TVar(L"foo")));
-*/
}
// n=mod(n1,n2)
diff --git a/far/macroopcode.hpp b/far/macroopcode.hpp
index 6900e115a..7c0dcaad2 100644
--- a/far/macroopcode.hpp
+++ b/far/macroopcode.hpp
@@ -160,6 +160,7 @@ enum MACRO_OP_CODE
MCODE_F_MACROSETTINGS, //
//----------------------------------------------------------------------------
MCODE_F_MENU_GETEXTENDEDDATA, // T=Menu.GetItemExtendedData([hDlg,][Pos])
+ MCODE_F_MENU_SETEXTENDEDDATA, // B=Menu.SetItemExtendedData([hDlg,][Pos,]T)
MCODE_F_LAST, // marker
diff --git a/far/plugin.hpp b/far/plugin.hpp
index 881b2e881..55cbf746d 100644
--- a/far/plugin.hpp
+++ b/far/plugin.hpp
@@ -1490,6 +1490,7 @@ enum FARMACROVARTYPE
FMVT_STACKGETTOP = 18,
FMVT_STACKSETTOP = 19,
FMVT_STACKPUSHVALUE = 20,
+ FMVT_NEXT = 21,
};
struct FarMacroValue
diff --git a/far/vbuild.m4 b/far/vbuild.m4
index d7147d5da..d4ae72d08 100644
--- a/far/vbuild.m4
+++ b/far/vbuild.m4
@@ -1 +1 @@
-6677
+6678
diff --git a/far/vmenu.cpp b/far/vmenu.cpp
index 6182ea95f..3a65a8568 100644
--- a/far/vmenu.cpp
+++ b/far/vmenu.cpp
@@ -1598,13 +1598,23 @@ long long VMenu::VMProcess(int OpCode, void* vParam, long long iParam)
}
case MCODE_F_MENU_GETEXTENDEDDATA:
{
- if (m_ExtendedDataProvider)
+ if (m_ExtendedDataGetter)
{
const auto ItemPos{ GetItemPosition(iParam) };
if (ItemPos == -1) return -1;
- *static_cast<extended_item_data*>(vParam) = m_ExtendedDataProvider(Items[ItemPos]);
- return 1;
+ return m_ExtendedDataGetter(Items[ItemPos], *static_cast<extended_item_data*>(vParam)) ? 1 : 0;
+ }
+ return -1;
+ }
+ case MCODE_F_MENU_SETEXTENDEDDATA:
+ {
+ if (m_ExtendedDataSetter)
+ {
+ const auto ItemPos{ GetItemPosition(iParam) };
+ if (ItemPos == -1) return -1;
+
+ return m_ExtendedDataSetter(Items[ItemPos], *static_cast<extended_item_data*>(vParam)) ? 1 : 0;
}
return -1;
}
@@ -3457,9 +3467,10 @@ std::any* VMenu::GetComplexUserData(int Position)
return &Items[ItemPos].ComplexUserData;
}
-void VMenu::RegisterExtendedDataProvider(extended_item_data_provider&& ExtendedDataProvider)
+void VMenu::RegisterExtendedDataProvider(extended_item_data_getter&& ExtendedDataGetter, extended_item_data_setter&& ExtendedDataSetter)
{
- m_ExtendedDataProvider = std::move(ExtendedDataProvider);
+ m_ExtendedDataGetter = std::move(ExtendedDataGetter);
+ m_ExtendedDataSetter = std::move(ExtendedDataSetter);
}
FarListItem *VMenu::MenuItem2FarList(const menu_item_ex *MItem, FarListItem *FItem)
diff --git a/far/vmenu.hpp b/far/vmenu.hpp
index 52e056ba4..7ee3fc740 100644
--- a/far/vmenu.hpp
+++ b/far/vmenu.hpp
@@ -232,7 +232,7 @@ public:
std::any* GetComplexUserData(int Position = -1);
const std::any* GetComplexUserData(int Position = -1) const
{
- return const_cast<VMenu*>(this)->GetComplexUserData();
+ return const_cast<VMenu*>(this)->GetComplexUserData(Position);
}
template<class T>
T* GetComplexUserDataPtr(int Position = -1)
@@ -247,8 +247,9 @@ public:
void SetComplexUserData(const std::any& Data, int Position = -1);
using extended_item_data = std::vector<std::pair<FarMacroValue, FarMacroValue>>;
- using extended_item_data_provider = std::function<extended_item_data(const menu_item_ex&)>;
- void RegisterExtendedDataProvider(extended_item_data_provider&& ExtendedDataProvider);
+ using extended_item_data_getter = std::function<bool(const menu_item_ex&, extended_item_data&)>;
+ using extended_item_data_setter = std::function<bool(menu_item_ex&, const extended_item_data&)>;
+ void RegisterExtendedDataProvider(extended_item_data_getter&& ExtendedDataGetter, extended_item_data_setter&& ExtendedDataSetter);
int GetSelectPos() const { return SelectPos; }
int GetLastSelectPosResult() const { return SelectPosResult; }
@@ -359,7 +360,8 @@ private:
std::unique_ptr<vmenu_horizontal_tracker> m_HorizontalTracker;
std::vector<vmenu_fixed_column_t> m_FixedColumns;
segment m_ItemTextSegment{ segment::ray() };
- extended_item_data_provider m_ExtendedDataProvider;
+ extended_item_data_getter m_ExtendedDataGetter;
+ extended_item_data_setter m_ExtendedDataSetter;
window_ptr CurrentWindow;
bool PrevCursorVisible{};
size_t PrevCursorSize{};
diff --git a/plugins/common/unicode/plugin.hpp b/plugins/common/unicode/plugin.hpp
index 1a9c111ed..f80d2d2a8 100644
--- a/plugins/common/unicode/plugin.hpp
+++ b/plugins/common/unicode/plugin.hpp
@@ -1392,6 +1392,7 @@ enum FARMACROVARTYPE
FMVT_STACKGETTOP = 18,
FMVT_STACKSETTOP = 19,
FMVT_STACKPUSHVALUE = 20,
+ FMVT_NEXT = 21,
};
struct FarMacroValue
diff --git a/plugins/luamacro/_globalinfo.lua b/plugins/luamacro/_globalinfo.lua
index 58ae5c528..121ded884 100644
--- a/plugins/luamacro/_globalinfo.lua
+++ b/plugins/luamacro/_globalinfo.lua
@@ -1,7 +1,7 @@
function export.GetGlobalInfo()
return {
- Version = { 3, 0, 0, 920 },
- MinFarVersion = { 3, 0, 0, 6632 },
+ Version = { 3, 0, 0, 921 },
+ MinFarVersion = { 3, 0, 0, 6678 },
Guid = win.Uuid("4EBBEFC8-2084-4B7F-94C0-692CE136894D"),
Title = "LuaMacro",
Description = "Far macros in Lua",
diff --git a/plugins/luamacro/api.lua b/plugins/luamacro/api.lua
index 8f3c16889..9add70ba9 100644
--- a/plugins/luamacro/api.lua
+++ b/plugins/luamacro/api.lua
@@ -294,6 +294,7 @@ Menu = {
Select = function(...) return MacroCallFar(0x80C1B, ...) end,
Show = function(...) return MacroCallFar(0x80C1C, ...) end,
GetItemExtendedData = function(...) return MacroCallFar(0x80C6B, ...) end,
+ SetItemExtendedData = function(...) return MacroCallFar(0x80C6C, ...) end,
}
SetProperties(Menu, {
diff --git a/plugins/luamacro/changelog b/plugins/luamacro/changelog
index 70ec5bb2c..7ee8f2c61 100644
--- a/plugins/luamacro/changelog
+++ b/plugins/luamacro/changelog
@@ -1,3 +1,7 @@
+MZK 2026-04-18 10:27:01-04:00 - build 921
+
+1. gh-1006: Macro API to set menu item extended data: Menu.SetItemExtendedData.
+
shmuel 2026-04-18 00:28:31+03:00 - build 920
1. LuaFAR: refactoring.
diff --git a/plugins/luamacro/luafar/lf_luamacro.c b/plugins/luamacro/luafar/lf_luamacro.c
index 8155a8417..81870c958 100644
--- a/plugins/luamacro/luafar/lf_luamacro.c
+++ b/plugins/luamacro/luafar/lf_luamacro.c
@@ -201,6 +201,22 @@ static void WINAPI MacroCallFarCallback(void *Data, struct FarMacroValue *Val, s
}
break;
+ case FMVT_NEXT:
+ if (pos >= 1 && pos <= top - 1 && lua_istable(L, pos))
+ {
+ if (lua_next(L, pos))
+ {
+ ConvertLuaValue(L, -2, &Val[0]); // key
+ ConvertLuaValue(L, -1, &Val[1]); // value
+ }
+ else
+ {
+ Val[0].Type = FMVT_NIL; // No more elements
+ Val[1].Type = FMVT_NIL;
+ }
+ }
+ break;
+
case FMVT_STACKPOP:
if (param > 0 && (top - param) >= cbdata->start_stack)
lua_pop(L, param);
diff --git a/plugins/luamacro/luafar/lf_version.h b/plugins/luamacro/luafar/lf_version.h
index aa00436d0..42d9e18a9 100644
--- a/plugins/luamacro/luafar/lf_version.h
+++ b/plugins/luamacro/luafar/lf_version.h
@@ -1,3 +1,3 @@
#include <farversion.hpp>
-#define PLUGIN_BUILD 920
+#define PLUGIN_BUILD 921
diff --git a/plugins/luamacro/macrotest.lua b/plugins/luamacro/macrotest.lua
index 081dd3e11..74467ee04 100644
--- a/plugins/luamacro/macrotest.lua
+++ b/plugins/luamacro/macrotest.lua
@@ -1157,6 +1157,7 @@ function MT.test_Menu()
assert_func(Menu.GetValue)
assert_func(Menu.ItemStatus)
assert_func(Menu.Select)
+ assert_func(Menu.SetItemExtendedData)
assert_func(Menu.Show)
end