chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
import CodexBarCore
|
||||
import Foundation
|
||||
|
||||
enum MenuBarDisplayText {
|
||||
static func percentText(window: RateWindow?, showUsed: Bool) -> String? {
|
||||
guard let window else { return nil }
|
||||
let percent = showUsed ? window.usedPercent : window.remainingPercent
|
||||
return UsageFormatter.percentString(percent)
|
||||
}
|
||||
|
||||
static func paceText(pace: UsagePace?) -> String? {
|
||||
guard let pace else { return nil }
|
||||
let deltaValue = Int(abs(pace.deltaPercent).rounded())
|
||||
if deltaValue == 0 { return "0%" }
|
||||
let sign = pace.deltaPercent >= 0 ? "+" : "-"
|
||||
return "\(sign)\(deltaValue)%"
|
||||
}
|
||||
|
||||
/// Combined "session · weekly" menu-bar text shared by providers that expose both a
|
||||
/// session (5h) and weekly (7d) lane, e.g. Codex and Claude.
|
||||
static func combinedSessionWeeklyPercentText(
|
||||
sessionWindow: RateWindow?,
|
||||
weeklyWindow: RateWindow?,
|
||||
showUsed: Bool,
|
||||
resetTimeDisplayStyle: ResetTimeDisplayStyle = .countdown,
|
||||
showsResetTimeWhenExhausted: Bool = false,
|
||||
now: Date = .init())
|
||||
-> String?
|
||||
{
|
||||
var parts: [String] = []
|
||||
if let sessionWindow,
|
||||
let session = self.laneValueText(
|
||||
window: sessionWindow,
|
||||
showUsed: showUsed,
|
||||
resetTimeDisplayStyle: resetTimeDisplayStyle,
|
||||
showsResetTimeWhenExhausted: showsResetTimeWhenExhausted,
|
||||
now: now)
|
||||
{
|
||||
parts.append("\(self.sessionWindowLabel(window: sessionWindow)) \(session)")
|
||||
}
|
||||
if let weeklyWindow,
|
||||
let weekly = self.laneValueText(
|
||||
window: weeklyWindow,
|
||||
showUsed: showUsed,
|
||||
resetTimeDisplayStyle: resetTimeDisplayStyle,
|
||||
showsResetTimeWhenExhausted: showsResetTimeWhenExhausted,
|
||||
now: now)
|
||||
{
|
||||
parts.append("W \(weekly)")
|
||||
}
|
||||
return parts.isEmpty ? nil : parts.joined(separator: " · ")
|
||||
}
|
||||
|
||||
private static func laneValueText(
|
||||
window: RateWindow,
|
||||
showUsed: Bool,
|
||||
resetTimeDisplayStyle: ResetTimeDisplayStyle,
|
||||
showsResetTimeWhenExhausted: Bool,
|
||||
now: Date) -> String?
|
||||
{
|
||||
if let resetText = self.exhaustedResetText(
|
||||
window: window,
|
||||
enabled: showsResetTimeWhenExhausted,
|
||||
style: resetTimeDisplayStyle,
|
||||
now: now)
|
||||
{
|
||||
return resetText
|
||||
}
|
||||
return self.percentText(window: window, showUsed: showUsed)
|
||||
}
|
||||
|
||||
private static func sessionWindowLabel(window: RateWindow) -> String {
|
||||
guard let minutes = window.windowMinutes, minutes > 0 else { return "S" }
|
||||
guard minutes.isMultiple(of: 60) else { return "\(minutes)m" }
|
||||
return "\(minutes / 60)h"
|
||||
}
|
||||
|
||||
static func displayText(
|
||||
mode: MenuBarDisplayMode,
|
||||
percentWindow: RateWindow?,
|
||||
pace: UsagePace? = nil,
|
||||
showUsed: Bool,
|
||||
resetTimeDisplayStyle: ResetTimeDisplayStyle = .countdown,
|
||||
showsResetTimeWhenExhausted: Bool = false,
|
||||
now: Date = .init()) -> String?
|
||||
{
|
||||
if mode != .resetTime,
|
||||
showsResetTimeWhenExhausted,
|
||||
let percentWindow,
|
||||
percentWindow.remainingPercent <= 0
|
||||
{
|
||||
if let resetText = self.exhaustedResetText(
|
||||
window: percentWindow,
|
||||
enabled: true,
|
||||
style: resetTimeDisplayStyle,
|
||||
now: now)
|
||||
{
|
||||
return resetText
|
||||
}
|
||||
// Smart mode cannot replace an exhausted percentage unless the reset is concrete, future,
|
||||
// and schedulable. Preserve the quota signal in pace/both modes too; a pace from another
|
||||
// combined lane must not hide that this displayed lane is already exhausted.
|
||||
return self.percentText(window: percentWindow, showUsed: showUsed)
|
||||
}
|
||||
switch mode {
|
||||
case .percent:
|
||||
return self.percentText(window: percentWindow, showUsed: showUsed)
|
||||
case .pace:
|
||||
// Pace can be temporarily unavailable near a reset or when a provider omits window metadata.
|
||||
// Keep the selected quota visible instead of collapsing the status item to an icon-only state.
|
||||
return self.paceText(pace: pace)
|
||||
?? self.percentText(window: percentWindow, showUsed: showUsed)
|
||||
case .both:
|
||||
guard let percent = percentText(window: percentWindow, showUsed: showUsed) else { return nil }
|
||||
// Fall back to percent-only when pace is unavailable (e.g. Copilot)
|
||||
guard let paceText = Self.paceText(pace: pace) else { return percent }
|
||||
return "\(percent) · \(paceText)"
|
||||
case .resetTime:
|
||||
guard let percentWindow else { return nil }
|
||||
return self.resetTimeText(window: percentWindow, style: resetTimeDisplayStyle, now: now)
|
||||
?? self.percentText(window: percentWindow, showUsed: showUsed)
|
||||
}
|
||||
}
|
||||
|
||||
/// "↻ …" reset text for a window, or nil when it carries no usable reset metadata.
|
||||
static func resetTimeText(
|
||||
window: RateWindow,
|
||||
style: ResetTimeDisplayStyle,
|
||||
now: Date) -> String?
|
||||
{
|
||||
if let resetsAt = window.resetsAt {
|
||||
let description = switch style {
|
||||
case .countdown:
|
||||
UsageFormatter.resetCountdownDescription(from: resetsAt, now: now)
|
||||
case .absolute:
|
||||
UsageFormatter.resetDescription(from: resetsAt, now: now)
|
||||
}
|
||||
return "↻ \(description)"
|
||||
}
|
||||
if let resetDescription = self.resetMetadataText(window.resetDescription) {
|
||||
return "↻ \(resetDescription)"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Smart-mode replacement: when enabled and the quota is exhausted (0% remaining, regardless of
|
||||
/// whether the display shows used or remaining), surface the reset time instead of a dead percent.
|
||||
///
|
||||
/// Requires a concrete, still-future `resetsAt`. The smart option only replaces the percent when it
|
||||
/// has a reset time it can both render as a live countdown/clock AND hand to the refresh scheduler,
|
||||
/// so the lane keeps ticking and flips back to the percentage once the reset passes. Windows with
|
||||
/// only textual reset metadata (`resetDescription`, no `resetsAt`) or an already-elapsed reset can't
|
||||
/// be scheduled, so they keep showing the percent instead of freezing on stale reset text.
|
||||
private static func exhaustedResetText(
|
||||
window: RateWindow?,
|
||||
enabled: Bool,
|
||||
style: ResetTimeDisplayStyle,
|
||||
now: Date) -> String?
|
||||
{
|
||||
guard enabled, let window, window.remainingPercent <= 0 else { return nil }
|
||||
guard let resetsAt = window.resetsAt, resetsAt > now else { return nil }
|
||||
return self.resetTimeText(window: window, style: style, now: now)
|
||||
}
|
||||
|
||||
private static func resetMetadataText(_ description: String?) -> String? {
|
||||
guard let description else { return nil }
|
||||
let trimmed = description.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return nil }
|
||||
|
||||
// RateWindow.resetDescription predates provider-specific detail fields and is also used for
|
||||
// request/token summaries. Only trust phrases that explicitly describe reset timing.
|
||||
let normalized = trimmed.lowercased()
|
||||
let resetPrefixes = [
|
||||
"reset ", "resets ", "in ", "today ", "today,", "tomorrow ", "tomorrow,", "next ",
|
||||
"expire ", "expires ", "refill ", "refills ",
|
||||
]
|
||||
let exactResetDescriptions = ["today", "tomorrow", "expired", "now", "soon"]
|
||||
return exactResetDescriptions.contains(normalized) || resetPrefixes.contains(where: normalized.hasPrefix)
|
||||
? trimmed
|
||||
: nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user