feat: dynamic menus

This commit is contained in:
relikd
2026-02-07 11:56:25 +01:00
parent 8053110349
commit d514bfb610
4 changed files with 93 additions and 18 deletions

View File

@@ -60,7 +60,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
itm.representedObject = entry
itm.image = entry.icon()
if entry.isDir {
if entry.isDir || entry.action == .Dynamic {
itm.submenu = NSMenu(title: entry.url.path)
itm.submenu?.delegate = self
} else if entry.action != .Inactive {
@@ -77,7 +77,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
@objc private func menuItemCallback(sender: NSMenuItem) {
let entry = sender.representedObject as! Entry
let cmd = Exec(file: entry.url, args: nil)
let cmd = Exec(file: entry.url, env: ["ACTION": "click", "ITEM": entry.title])
if entry.action == .Text {
cmd.editor()
} else {
@@ -184,14 +184,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
struct Exec {
let file: URL
var args: [String]? = nil
var env: [String: String]? = nil
private func prepare() -> Process {
let proc = Process()
// proc.environment = ["HOME": "$HOME"]
proc.environment = self.env
proc.currentDirectoryURL = self.file.deletingLastPathComponent()
proc.executableURL = self.file
proc.arguments = args
return proc
}
@@ -224,9 +223,13 @@ struct Exec {
}
/// run command and return output
func read() -> String {
let data = toPipe().fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) ?? ""
func readData() -> Data {
return toPipe().fileHandleForReading.readDataToEndOfFile()
}
/// run command and return output
func readString() -> String {
return String(data: readData(), encoding: .utf8) ?? ""
}
}
@@ -236,6 +239,14 @@ struct Exec {
func listDir(_ path: String) -> [Entry] {
let target = URL(fileURLWithPath: path)
var rv: [Entry] = []
// dynamic menu
if !target.hasDirectoryPath {
Exec(file: target, env: ["ACTION": "list"]).readString().enumerateLines { line, stop in
rv.append(Entry(target, title: line))
}
return rv
}
// else: static menu
for url in (try? FileManager.default.contentsOfDirectory(at: target, includingPropertiesForKeys: [.isDirectoryKey, .isExecutableKey])) ?? [] {
if url.hasDirectoryPath || FileManager.default.isExecutableFile(atPath: url.path) {
rv.append(Entry(url))
@@ -253,6 +264,7 @@ enum ActionFlag {
case Verbose
case Inactive
case Ignore
case Dynamic
static func from(_ filename: inout String) -> Self {
for (key, flag) in [
@@ -260,6 +272,7 @@ enum ActionFlag {
"[verbose]": .Verbose,
"[inactive]": .Inactive,
"[ignore]": .Ignore,
"[dyn]": .Dynamic,
] {
if filename.contains(key) {
filename = filename
@@ -277,21 +290,25 @@ struct Entry {
let url: URL
let title: String
let action: ActionFlag
let hasDynParent: Bool
var isDir: Bool { url.hasDirectoryPath }
init(_ url: URL) {
init(_ url: URL, title: String? = nil) {
self.hasDynParent = title != nil
self.url = url
// TODO: remove file extension?
var fname = url.lastPathComponent
var fname = title ?? url.lastPathComponent
var customOrder = 100
var idx = fname.firstIndex { !$0.isWholeNumber } ?? fname.startIndex
// sort order
if let order = Int(fname[..<idx]) {
customOrder = order
idx = fname[idx..<fname.endIndex].firstIndex { !$0.isWhitespace } ?? idx
idx = fname[idx..<fname.endIndex].firstIndex { !$0.isPunctuation } ?? idx
fname = String(fname[idx..<fname.endIndex])
if !hasDynParent {
var idx = fname.firstIndex { !$0.isWholeNumber } ?? fname.startIndex
// sort order
if let order = Int(fname[..<idx]) {
customOrder = order
idx = fname[idx..<fname.endIndex].firstIndex { !$0.isWhitespace } ?? idx
idx = fname[idx..<fname.endIndex].firstIndex { !$0.isPunctuation } ?? idx
fname = String(fname[idx..<fname.endIndex])
}
}
self.order = customOrder
self.action = ActionFlag.from(&fname)
@@ -311,6 +328,9 @@ struct Entry {
if img == nil {
img = NSImage(named: NSImage.folderName)
}
} else if action == .Dynamic || hasDynParent {
let cmd = Exec(file: self.url, env: ["ACTION": "icon", "ITEM": hasDynParent ? self.title : ""])
img = NSImage(data: cmd.readData())
}
img?.size = NSMakeSize(16, 16)
return img