From 364d9899014b18bc94664403b8259d321c612a92 Mon Sep 17 00:00:00 2001 From: emil-e Date: Sun, 8 Mar 2026 13:46:56 +0100 Subject: [PATCH] Add detailed context usage indicator mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend `agent-shell-show-context-usage-indicator` from a boolean to a three-way choice: nil (hidden), t (bar, existing default), or `detailed` which shows a numeric format like "➤ 29k/200k (29%)". The detailed indicator reuses the existing `agent-shell--format-number-compact` helper and the same color thresholds (success/warning/error at 60%/85%) as the bar indicator. The entire string except the ➤ prefix is propertized with the appropriate face for coherent coloring. --- agent-shell-usage.el | 82 ++++++++++++++++++++++++++------------ agent-shell.el | 24 ++++++----- tests/agent-shell-tests.el | 58 +++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 35 deletions(-) diff --git a/agent-shell-usage.el b/agent-shell-usage.el index a1b8ba9..f278637 100644 --- a/agent-shell-usage.el +++ b/agent-shell-usage.el @@ -47,12 +47,17 @@ context window usage, and cost information after each agent response." :group 'agent-shell) (defcustom agent-shell-show-context-usage-indicator t - "Non-nil to show the context usage indicator in the header and mode line. + "Whether and how to show the context usage indicator. -The indicator displays context window usage as a vertical bar character, -color-coded from green (low) to yellow (high) to red (critical). +When set to t, displays a vertical bar character indicating +fill level. When set to `detailed', displays a numeric format +like \"➤ 29k/200k (29%%)\". When nil, no indicator is shown. + +Color-coded: green (low), yellow (high), red (critical). Only appears when the ACP server provides usage information." - :type 'boolean + :type '(choice (const :tag "Hidden" nil) + (const :tag "Bar" t) + (const :tag "Detailed" detailed)) :group 'agent-shell) (cl-defun agent-shell--save-usage (&key state acp-usage) @@ -191,9 +196,49 @@ When MULTILINE is non-nil, format as right-aligned labeled rows." 'font-lock-face 'font-lock-comment-face) cost)))) +(defun agent-shell--context-usage-face (percentage) + "Return the face for context usage at PERCENTAGE. +Green for normal, yellow for warning, red for critical." + (cond + ((>= percentage 85) 'error) + ((>= percentage 60) 'warning) + (t 'success))) + +(defun agent-shell--context-usage-indicator-bar (usage context-used context-size) + "Return a bar indicator for context USAGE. +CONTEXT-USED and CONTEXT-SIZE are token counts." + (let* ((percentage (/ (* 100.0 context-used) context-size)) + (indicator (cond + ((>= percentage 100) "█") + ((>= percentage 87.5) "▇") + ((>= percentage 75) "▆") + ((>= percentage 62.5) "▅") + ((>= percentage 50) "▄") + ((>= percentage 37.5) "▃") + ((>= percentage 25) "▂") + ((> percentage 0) "▁") + (t nil)))) + (when indicator + (propertize indicator + 'face (agent-shell--context-usage-face percentage) + 'help-echo (agent-shell--format-usage usage))))) + +(defun agent-shell--context-usage-indicator-detailed (usage context-used context-size) + "Return a detailed indicator for context USAGE. +CONTEXT-USED and CONTEXT-SIZE are token counts. +Format: \"29k/200k (29%)\"." + (let ((percentage (/ (* 100.0 context-used) context-size))) + (propertize (format "%s/%s (%.0f%%%%)" + (agent-shell--format-number-compact context-used) + (agent-shell--format-number-compact context-size) + percentage) + 'face (agent-shell--context-usage-face percentage) + 'help-echo (agent-shell--format-usage usage)))) + (defun agent-shell--context-usage-indicator () - "Return a single character indicating context usage percentage. -Uses Unicode vertical block characters to show fill level. + "Return a string indicating context usage percentage. +Dispatches to bar or detailed indicator based on +`agent-shell-show-context-usage-indicator'. Only returns an indicator if enabled and usage data is available." (when-let* ((agent-shell-show-context-usage-indicator) ((agent-shell--usage-has-data-p (map-elt (agent-shell--state) :usage))) @@ -201,26 +246,11 @@ Only returns an indicator if enabled and usage data is available." (context-used (map-elt usage :context-used)) (context-size (map-elt usage :context-size)) ((> context-size 0))) - (let* ((percentage (/ (* 100.0 context-used) context-size)) - ;; Unicode vertical block characters from empty to full - (indicator (cond - ((>= percentage 100) "█") ; Full - ((>= percentage 87.5) "▇") - ((>= percentage 75) "▆") - ((>= percentage 62.5) "▅") - ((>= percentage 50) "▄") - ((>= percentage 37.5) "▃") - ((>= percentage 25) "▂") - ((> percentage 0) "▁") - (t nil))) ; Return nil for no usage - (face (cond - ((>= percentage 85) 'error) ; Red for critical - ((>= percentage 60) 'warning) ; Yellow/orange for warning - (t 'success)))) ; Green for normal - (when indicator - (propertize indicator - 'face face - 'help-echo (agent-shell--format-usage usage)))))) + (pcase agent-shell-show-context-usage-indicator + ('detailed + (agent-shell--context-usage-indicator-detailed usage context-used context-size)) + (_ + (agent-shell--context-usage-indicator-bar usage context-used context-size))))) (provide 'agent-shell-usage) ;;; agent-shell-usage.el ends here diff --git a/agent-shell.el b/agent-shell.el index c4d0c12..3e2ad6c 100644 --- a/agent-shell.el +++ b/agent-shell.el @@ -2909,7 +2909,8 @@ BINDINGS is a list of alists defining key bindings to display, each with: (concat " ➤ " (map-elt header-model :session-id)) "") (if (map-elt header-model :context-indicator) - (concat " " (map-elt header-model :context-indicator)) + (concat (if (> (length (map-elt header-model :context-indicator)) 1) " ➤ " " ") + (map-elt header-model :context-indicator)) "") (if (map-elt header-model :busy-indicator-frame) (map-elt header-model :busy-indicator-frame) @@ -3001,17 +3002,22 @@ BINDINGS is a list of alists defining key bindings to display, each with: (dx . "8")) (map-elt header-model :mode-name)))) (when (map-elt header-model :context-indicator) - (let* (;; Extract the face from the propertized string - (face (get-text-property 0 'face (map-elt header-model :context-indicator))) - ;; Get the foreground color from the face - (color (if face - (face-attribute face :foreground nil t) - (face-attribute 'default :foreground)))) + (when (> (length (map-elt header-model :context-indicator)) 1) + ;; Add separator arrow (dom-append-child text-node (dom-node 'tspan - `((fill . ,color) + `((fill . ,(face-attribute 'default :foreground)) (dx . "8")) - (substring-no-properties (map-elt header-model :context-indicator)))))) + "➤"))) + ;; Add context indicator + (dom-append-child text-node + (dom-node 'tspan + `((fill . ,(face-attribute + (or (get-text-property 0 'face (map-elt header-model :context-indicator)) + 'default) + :foreground nil t)) + (dx . "8")) + (format-mode-line (map-elt header-model :context-indicator))))) (when (map-elt header-model :busy-indicator-frame) (dom-append-child text-node (dom-node 'tspan diff --git a/tests/agent-shell-tests.el b/tests/agent-shell-tests.el index ec82ca5..791ad75 100644 --- a/tests/agent-shell-tests.el +++ b/tests/agent-shell-tests.el @@ -1924,5 +1924,63 @@ code block content (should-not responded) (should (equal (map-elt state :last-entry-type) "session/request_permission")))))) +;;; Tests for agent-shell-show-context-usage-indicator + +(ert-deftest agent-shell--context-usage-indicator-bar-test () + "Test `agent-shell--context-usage-indicator' bar mode." + (let ((agent-shell--state + (list (cons :buffer (current-buffer)) + (cons :usage (list (cons :context-used 50000) + (cons :context-size 200000) + (cons :total-tokens 50000)))))) + (cl-letf (((symbol-function 'agent-shell--state) + (lambda () agent-shell--state))) + (let ((agent-shell-show-context-usage-indicator t)) + (let ((result (agent-shell--context-usage-indicator))) + (should result) + (should (= (length (substring-no-properties result)) 1)) + (should (eq (get-text-property 0 'face result) 'success))))))) + +(ert-deftest agent-shell--context-usage-indicator-detailed-test () + "Test `agent-shell--context-usage-indicator' detailed mode." + (let ((agent-shell--state + (list (cons :buffer (current-buffer)) + (cons :usage (list (cons :context-used 30000) + (cons :context-size 200000) + (cons :total-tokens 30000)))))) + (cl-letf (((symbol-function 'agent-shell--state) + (lambda () agent-shell--state))) + (let ((agent-shell-show-context-usage-indicator 'detailed)) + (let ((result (agent-shell--context-usage-indicator))) + (should result) + (should (string-match-p "30k/200k" (substring-no-properties result))) + (should (string-match-p "15%%" (substring-no-properties result))) + (should (eq (get-text-property 0 'face result) 'success))))))) + +(ert-deftest agent-shell--context-usage-indicator-detailed-warning-test () + "Test `agent-shell--context-usage-indicator' detailed mode with warning face." + (let ((agent-shell--state + (list (cons :buffer (current-buffer)) + (cons :usage (list (cons :context-used 140000) + (cons :context-size 200000) + (cons :total-tokens 140000)))))) + (cl-letf (((symbol-function 'agent-shell--state) + (lambda () agent-shell--state))) + (let ((agent-shell-show-context-usage-indicator 'detailed)) + (let ((result (agent-shell--context-usage-indicator))) + (should (eq (get-text-property 0 'face result) 'warning))))))) + +(ert-deftest agent-shell--context-usage-indicator-nil-test () + "Test `agent-shell--context-usage-indicator' returns nil when disabled." + (let ((agent-shell--state + (list (cons :buffer (current-buffer)) + (cons :usage (list (cons :context-used 50000) + (cons :context-size 200000) + (cons :total-tokens 50000)))))) + (cl-letf (((symbol-function 'agent-shell--state) + (lambda () agent-shell--state))) + (let ((agent-shell-show-context-usage-indicator nil)) + (should-not (agent-shell--context-usage-indicator)))))) + (provide 'agent-shell-tests) ;;; agent-shell-tests.el ends here