Skip to content

Commit

Permalink
Feature: Allow setting custom menu actions in the textview
Browse files Browse the repository at this point in the history
  • Loading branch information
nanashili committed Jun 22, 2024
1 parent f291929 commit ad537a1
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 18 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
"version" : "1.0.4"
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
"version" : "1.1.1"
}
},
{
Expand Down
5 changes: 0 additions & 5 deletions Sources/AuroraEditorTextView/AuroraEditorTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@

import SwiftUI

// swiftlint:disable:next line_length
@available(*,
unavailable,
renamed: "AuroraEditorSourceEditor",
message: "AuroraEditorTextView has moved to https://github.com/AuroraEditor/AuroraEditorSourceEditor, please update any dependencies to use this new repository URL.")
struct AuroraEditorTextView: View {
var body: some View {
EmptyView()
Expand Down
51 changes: 51 additions & 0 deletions Sources/AuroraEditorTextView/TextView/Menu/CustomMenuItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// CustomMenuItem.swift
//
//
// Created by Nanashi Li on 2024/06/21.
//

import ObjectiveC

/// A concrete implementation of ``MenuItemProvider``.
///
/// This struct provides a convenient way to create menu items, including separators.
public struct CustomMenuItem: MenuItemProvider {
/// The title of the menu item.
public let title: String

/// The action to be performed when the menu item is selected.
public let action: Selector?

/// The key equivalent for the menu item.
public let keyEquivalent: String

/// Indicates whether this item represents a separator in the menu.
public let isSeparator: Bool

/// Creates a new CustomMenuItem.
///
/// - Parameters:
/// - title: The title of the menu item. Defaults to an empty string for separators.
/// - action: The action to be performed when the menu item is selected. Defaults to `nil`.
/// - keyEquivalent: The key equivalent for the menu item. Defaults to an empty string.
/// - isSeparator: Whether this item should be a separator. Defaults to `false`.
public init(title: String,
action: Selector? = nil,
keyEquivalent: String = "",
isSeparator: Bool = false) {
self.title = title
self.action = action
self.keyEquivalent = keyEquivalent
self.isSeparator = isSeparator
}

/// Creates a separator menu item.
///
/// This is a convenience method for creating a separator menu item.
///
/// - Returns: A CustomMenuItem configured as a separator.
public static func separator() -> CustomMenuItem {
return CustomMenuItem(title: "", isSeparator: true)
}
}
33 changes: 33 additions & 0 deletions Sources/AuroraEditorTextView/TextView/Menu/MenuItemProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// MenuItemProvider.swift
//
//
// Created by Nanashi Li on 2024/06/21.
//

import ObjectiveC

/// Represents a single menu item in a context menu.
///
/// This protocol defines the essential properties of a menu item, including its title, action,
/// key equivalent, and whether it's a separator.
public protocol MenuItemProvider {
/// The title of the menu item.
var title: String { get }

/// The action to be performed when the menu item is selected.
///
/// - Note: This property is optional. If `nil`, the menu item will be disabled.
var action: Selector? { get }

/// The key equivalent for the menu item.
///
/// This is a string representing the keyboard shortcut for the menu item.
/// For example, "cmd+c" for copy.
var keyEquivalent: String { get }

/// Indicates whether this item represents a separator in the menu.
///
/// If `true`, this item will be displayed as a separator line in the menu.
var isSeparator: Bool { get }
}
21 changes: 21 additions & 0 deletions Sources/AuroraEditorTextView/TextView/Menu/MenuItemsSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// MenuItemsSource.swift
//
//
// Created by Nanashi Li on 2024/06/21.
//

/// A source of menu items for a TextView.
///
/// Conform to this protocol to provide custom menu items for a TextView.
///
/// - Important: This protocol inherits from AnyObject, which means only class types can conform to it.
///
/// - Note: The implementing type is responsible for creating and returning an array of ``MenuItemProvider`` objects.
public protocol MenuItemsSource: AnyObject {
/// Provides an array of menu items for the given TextView.
///
/// - Parameter textView: The TextView requesting the menu items.
/// - Returns: An array of ``MenuItemProvider`` objects representing the menu items.
func menuItems(for textView: TextView) -> [MenuItemProvider]
}
60 changes: 49 additions & 11 deletions Sources/AuroraEditorTextView/TextView/TextView+Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,61 @@
// TextView+Menu.swift
// AuroraEditorTextView
//
// Created by Khan Winter on 8/21/23.
// Created by Nanashi Li on 6/20/24.
//

import AppKit

extension TextView {
override public func menu(for event: NSEvent) -> NSMenu? {
guard event.type == .rightMouseDown else { return nil }
/**
A computed property that returns an array of default menu items.
These items are always included at the end of the context menu.

let menu = NSMenu()
- Returns: An array of MenuItemProvider objects representing Cut, Copy, and Paste actions.
*/
private var defaultMenuItems: [MenuItemProvider] {
return [
CustomMenuItem(title: "Cut", action: #selector(cut(_:)), keyEquivalent: "x"),
CustomMenuItem(title: "Copy", action: #selector(copy(_:)), keyEquivalent: "c"),
CustomMenuItem(title: "Paste", action: #selector(paste(_:)), keyEquivalent: "v")
]
}

menu.items = [
NSMenuItem(title: "Cut", action: #selector(cut(_:)), keyEquivalent: "x"),
NSMenuItem(title: "Copy", action: #selector(undo(_:)), keyEquivalent: "c"),
NSMenuItem(title: "Paste", action: #selector(undo(_:)), keyEquivalent: "v")
]
/**
Overrides the default menu creation for the TextView.
This method is called when the user right-clicks on the TextView.

return menu
}
- Parameter event: The NSEvent that triggered the menu creation.
- Returns: An NSMenu object containing both custom and default menu items, or nil if the event is not a right mouse click.
*/
override public func menu(for event: NSEvent) -> NSMenu? {
guard event.type == .rightMouseDown else { return nil }

let menu = NSMenu()

var menuItems: [MenuItemProvider] = []

if let source = menuItemsSource {
menuItems = source.menuItems(for: self)

if !menuItems.isEmpty {
menuItems.append(CustomMenuItem.separator())
}
}

menuItems.append(contentsOf: defaultMenuItems)

for item in menuItems {
if item.isSeparator {
menu.addItem(NSMenuItem.separator())
} else {
let menuItem = NSMenuItem(title: item.title,
action: item.action,
keyEquivalent: item.keyEquivalent)
menu.addItem(menuItem)
}
}

return menu
}
}
3 changes: 3 additions & 0 deletions Sources/AuroraEditorTextView/TextView/TextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AppKit
import Version_Control

// Disabling file length and type body length as the methods and variables contained in this file cannot be moved
// to extensions.
Expand Down Expand Up @@ -201,6 +202,8 @@ public class TextView: NSView, NSTextContent {
}
}

weak var menuItemsSource: MenuItemsSource?

open var contentType: NSTextContentType?

/// The text view's delegate.
Expand Down

0 comments on commit ad537a1

Please sign in to comment.