diff options
Diffstat (limited to 'menubar/model.swift')
| -rw-r--r-- | menubar/model.swift | 45 |
1 files changed, 32 insertions, 13 deletions
diff --git a/menubar/model.swift b/menubar/model.swift index e4d179c..3ed1a7e 100644 --- a/menubar/model.swift +++ b/menubar/model.swift @@ -93,8 +93,14 @@ struct Row: Equatable { /// The pane's working directory, home as "~". var path: String = "" var importance: Importance = .normal + /// The draft in the agent's input box, as one line (`@dingger_draft`). + var draft: String = "" + /// The user looks at this pane: its window is active in an attached session. + var attended: Bool = false var location: String { "\(session) · \(window)" } + /// A draft left behind: where the user looks, they may be typing it. + var forgottenDraft: Bool { !draft.isEmpty && !attended } var waiting: Bool { status == .blocked || status == .done } /// Only an agent you have seen can be marked unread (done again). var canMarkUnread: Bool { status == .idle } @@ -105,8 +111,9 @@ struct Row: Equatable { return name.count > 80 ? String(name.prefix(79)) + "…" : name } - /// The title's text as plain text: `! ` marks a high agent. - var markedLabel: String { (importance == .high ? "! " : "") + label } + /// The title's text as plain text: `! ` marks a high agent, `✎ ` a + /// forgotten draft. + var markedLabel: String { (importance == .high ? "! " : "") + (forgottenDraft ? "✎ " : "") + label } /// Dim second line: where it runs, which harness, whether it is low, /// and how long it waits. @@ -274,30 +281,35 @@ func runDinggerInLoginShell(_ args: [String]) -> (ok: Bool, output: String) { private let home = NSHomeDirectory() -/// Every agent pane: herdr's status priority; within a status, the more -/// important first, then done and blocked oldest first (the queue), +/// Every agent pane: herdr's status priority; idle agents with a forgotten +/// draft before the other idle ones; within a status, the more important +/// first, then done and blocked oldest first (the queue), /// working and idle most recent first (`@dingger_since`: when the pane /// entered its status); then by place. func fetchRows() -> [Row] { let format = [ "#{pane_id}", "#{@agent}", "#{@agent_state}", "#{@dingger_since}", "#{@agent_name}", "#{session_name}", "#{window_name}", "#{window_panes}", "#{pane_index}", "#{pane_current_path}", - "#{@dingger_importance}", + "#{@dingger_importance}", "#{window_active}", "#{session_attached}", "#{@dingger_draft}", ].joined(separator: "\t") guard let out = tmux(["list-panes", "-a", "-F", format]) else { return [] } + let lines = out.split(separator: "\n").map { + $0.split(separator: "\t", omittingEmptySubsequences: false).map(String.init) + } + // Looked at in any of its sessions, as the daemon counts it. + let attended = Set(lines.filter { $0.count >= 14 && $0[11] == "1" && $0[12] != "0" }.map { $0[0] }) // A pane in several sessions (a session group, a linked window) is // listed once, under its first session, as the daemon counts it. var listed = Set<String>() - let rows: [Row] = out.split(separator: "\n").compactMap { line in - let f = line.split(separator: "\t", omittingEmptySubsequences: false).map(String.init) - guard f.count >= 11, !f[1].isEmpty, listed.insert(f[0]).inserted else { return nil } + let rows: [Row] = lines.compactMap { f in + guard f.count >= 14, !f[1].isEmpty, listed.insert(f[0]).inserted else { return nil } let status = Status(rawValue: f[2]) ?? .unknown guard status != .unknown else { return nil } let window = (Int(f[7]) ?? 1) > 1 ? "\(f[6]).\(f[8])" : f[6] return Row(paneId: f[0], agent: f[1], name: f[4], status: status, session: f[5], window: window, since: Int(f[3]) ?? Int.max, path: f[9].hasPrefix(home) ? "~" + f[9].dropFirst(home.count) : f[9], - importance: Importance(option: f[10])) + importance: Importance(option: f[10]), draft: f[13], attended: attended.contains(f[0])) } return rows.sorted(by: listedBefore) } @@ -305,6 +317,7 @@ func fetchRows() -> [Row] { /// The list order (see `fetchRows`). func listedBefore(_ a: Row, _ b: Row) -> Bool { if a.status.priority != b.status.priority { return a.status.priority > b.status.priority } + if a.status == .idle, a.forgottenDraft != b.forgottenDraft { return a.forgottenDraft } if a.importance != b.importance { return a.importance.rawValue > b.importance.rawValue } if a.since != b.since { // Int.max: never stamped; last either way. @@ -357,12 +370,15 @@ func plainTitle(for rows: [Row]) -> String { title(for: rows).string } -/// The title's characters before the name: the status symbol and the -/// importance column. +/// The title's characters before the name: the status symbol, the +/// importance column, and the draft mark. func rowTitlePrefix(_ row: Row) -> String { - "\(row.status.symbol) " + importanceMark(row) + " " + "\(row.status.symbol) " + importanceMark(row) + " " + draftMark(row) } +/// `✎ ` before the name of an agent with a forgotten draft. +private func draftMark(_ row: Row) -> String { row.forgottenDraft ? "✎ " : "" } + /// The importance column: `!` for a high agent, a blank otherwise, so every /// name starts in the same place. private func importanceMark(_ row: Row) -> String { row.importance == .high ? "!" : " " } @@ -371,7 +387,7 @@ private func importanceMark(_ row: Row) -> String { row.importance == .high ? "! let lowImportanceAlpha: CGFloat = 0.5 /// Row title: the colored status symbol, the importance column (a peach `!` -/// for a high agent), then the agent's name. Unnamed agents show their +/// for a high agent), `✎` for a forgotten draft, then the agent's name. Unnamed agents show their /// harness, dimmed, so it never competes with real names; a low agent's name /// is dimmed. func rowTitle(_ row: Row, primary: NSColor = .labelColor, @@ -385,6 +401,9 @@ func rowTitle(_ row: Row, primary: NSColor = .labelColor, string: importanceMark(row) + " ", attributes: [.font: NSFont.monospacedSystemFont(ofSize: font.pointSize, weight: .heavy), .foregroundColor: herdrPeach])) + if row.forgottenDraft { + text.append(NSAttributedString(string: draftMark(row), attributes: [.font: font, .foregroundColor: primary])) + } let labelColor = row.name.isEmpty ? secondary : row.importance == .low ? primary.withAlphaComponent(lowImportanceAlpha) : primary text.append(NSAttributedString( |
