You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Monaco Editor在macOS的WKWebView中使用原生系统上下文菜单?

实现Monaco Editor在WKWebView中显示macOS原生右键菜单的方案

我之前也碰到过一模一样的问题!VSCode里的Monaco之所以能弹出系统原生菜单,核心是它没有让Web端的Monaco自己渲染菜单,而是在Native层拦截了右键事件,拿到Monaco提供的菜单元数据后,转成系统原生菜单再弹出。咱们在WKWebView里可以复刻这个思路,分三步就能搞定:

1. 拦截Web端Monaco的默认右键事件,传递菜单数据

首先在Web端的Monaco初始化代码里,要覆盖它的默认右键行为——阻止内置菜单渲染,同时把当前编辑器的上下文菜单数据通过JSBridge传给Native层。

示例代码(Web端):

// 初始化编辑器
const editor = monaco.editor.create(document.getElementById('editor-container'), {
  value: '// 测试内容',
  language: 'javascript'
});

// 拦截右键事件
window.addEventListener('contextmenu', (e) => {
  e.preventDefault(); // 阻止Monaco显示内置Web菜单
  
  // 获取当前编辑器的上下文菜单配置
  const menuItems = editor.getActionContextMenuItems();
  // 通过WKWebView的消息通道把数据传给Native
  window.webkit.messageHandlers.monacoContextMenu.postMessage(menuItems);
});

2. 在Native层接收数据,构建原生NSMenu

在macOS的Swift代码里,实现WKScriptMessageHandler来接收Web端传过来的菜单数据,然后把这些数据转换成NSMenu和NSMenuItem,最后在鼠标位置弹出菜单。

示例代码(Swift):

class MonacoContextMenuHandler: NSObject, WKScriptMessageHandler {
    weak var webView: WKWebView?
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let menuItems = message.body as? [[String: Any]], let webView = webView else { return }
        
        // 创建原生菜单
        let systemMenu = NSMenu()
        for itemData in menuItems {
            // 解析菜单数据
            guard let title = itemData["label"] as? String,
                  let commandId = itemData["id"] as? String else { continue }
            
            let menuItem = NSMenuItem(title: title, action: nil, keyEquivalent: "")
            menuItem.isEnabled = itemData["enabled"] as? Bool ?? true
            menuItem.representedObject = commandId // 存储Monaco命令ID,后续触发用
            
            // 处理系统级命令(剪切/复制/粘贴),优先用系统默认行为
            switch commandId {
            case "editor.action.clipboardCutAction":
                menuItem.action = #selector(NSText.cut(_:))
            case "editor.action.clipboardCopyAction":
                menuItem.action = #selector(NSText.copy(_:))
            case "editor.action.clipboardPasteAction":
                menuItem.action = #selector(NSText.paste(_:))
            default:
                menuItem.action = #selector(onMenuItemClicked(_:))
            }
            
            systemMenu.addItem(menuItem)
            
            // 添加分隔符
            if itemData["separator"] as? Bool ?? false {
                systemMenu.addItem(NSMenuItem.separator())
            }
        }
        
        // 在鼠标位置弹出原生菜单
        NSApplication.shared.popUpContextMenu(systemMenu, with: NSApp.currentEvent!, for: webView)
    }
    
    @objc private func onMenuItemClicked(_ sender: NSMenuItem) {
        guard let commandId = sender.representedObject as? String, let webView = webView else { return }
        // 通知Web端执行对应的Monaco命令
        webView.evaluateJavaScript("monaco.editor.getActiveEditor().trigger('contextMenu', '\(commandId)')")
    }
}

// 记得在WKWebView配置里注册这个处理器
let config = WKWebViewConfiguration()
let menuHandler = MonacoContextMenuHandler()
menuHandler.webView = yourWebView
config.userContentController.add(menuHandler, name: "monacoContextMenu")

3. 细节优化

  • 菜单位置准确性:用NSApplication.shared.popUpContextMenu时,传入NSApp.currentEvent能确保菜单在鼠标右键的位置弹出,和系统行为一致。
  • 动态菜单更新:每次右键都要重新获取Monaco的菜单数据,不要缓存——因为菜单会根据选中内容、编辑器状态动态变化。
  • 权限与兼容性:确保WKWebView允许JSBridge通信,并且Monaco的版本和你调用的API匹配(比如getActionContextMenuItems是稳定API,不用担心版本兼容问题)。

内容的提问来源于stack exchange,提问作者nielsbot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:49:36