using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Unity.Pipeline.Commands;
using UnityEditor;
namespace Unity.Pipeline.Editor.Commands
{
///
/// Executes an Editor menu item by its path (e.g. "Assets/Reimport All"), or — when called with
/// no path — lists the available menu items. Backs the unity menu CLI command (CLI-110):
/// unity menu lists options, unity menu "<path>" executes one. The CLI only
/// forwards the request; all logic lives here, per the two-commands design.
///
/// Named MenuItemCommand (not MenuCommand) to avoid colliding with the built-in
/// type, which would make unqualified references ambiguous
/// in any file that also imports UnityEditor.
///
/// Execution wraps , which returns false
/// when the item could not be invoked — either because the path does not exist or because the
/// item is currently disabled (its validation function returned false). The API does not
/// distinguish those cases, so a failure is reported as "not found or disabled"; the CLI maps a
/// failed result to its "menu item does not exist" exit code.
///
/// Listing reflects the actual Editor menu via the internal
/// UnityEditor.Menu.GetMenuItems API (accessed by reflection, mirroring
/// ). Unlike scanning [MenuItem] attributes, this also
/// covers menus Unity defines natively (much of GameObject/…, Assets/Create/…,
/// File/…, etc.), which carry no attribute and would otherwise be missed.
///
/// Note: some menu items are heavy, show a modal confirmation, or trigger a domain reload/quit
/// (which tears the server down before it can reply). That is the nature of the invoked item;
/// such cases are a known limitation of driving arbitrary menus over a request/response call.
///
public static class MenuItemCommand
{
[CliCommand("menu", "Execute an Editor menu item by path, or list available items when no path is given", MainThreadRequired = true)]
public static MenuResponse ExecuteMenu(
[CliArg("path", "Menu item path to execute, e.g. \"Assets/Reimport All\". Omit to list available menu items.")] string path = "")
{
// No path → discovery mode: return the available menu items instead of executing.
if (string.IsNullOrWhiteSpace(path))
{
List items;
try
{
items = DiscoverMenuItems();
}
catch (Exception ex)
{
return MenuResponse.Fail(null, $"Failed to list menu items: {ex.Message}");
}
return new MenuResponse
{
Success = true,
Items = items,
Message = $"{items.Count} menu item(s) available."
};
}
bool executed;
try
{
executed = EditorApplication.ExecuteMenuItem(path);
}
catch (Exception ex)
{
return MenuResponse.Fail(path, $"Failed to execute menu item '{path}': {ex.Message}");
}
if (!executed)
return MenuResponse.Fail(path, $"Menu item '{path}' was not found or is currently disabled.");
return new MenuResponse
{
Success = true,
Path = path,
Message = $"Executed menu item '{path}'"
};
}
const BindingFlags k_All = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
// Reflection handles for the internal UnityEditor.Menu.GetMenuItems(string, bool, bool) API,
// which returns an array of internal UnityEditor.ScriptingMenuItem. Resolved once and cached;
// we read each item's `path` and `isSeparator` member (property or field, depending on the
// Unity version). See FocusEditorCommand for the same reflect-into-internal-API approach.
static MethodInfo s_GetMenuItems;
static Func