aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/eat-config.el10
-rw-r--r--modules/modeline-config.el98
-rw-r--r--tests/test-modeline-config-eat-state.el94
3 files changed, 191 insertions, 11 deletions
diff --git a/modules/eat-config.el b/modules/eat-config.el
index d66507c4..e059fe36 100644
--- a/modules/eat-config.el
+++ b/modules/eat-config.el
@@ -112,11 +112,19 @@ not recognize (which would later trip (cl-assert charset) on write)."
;; ------------------------------- eat package ---------------------------------
+(defun cj/--eat-clear-mode-line-process ()
+ "Drop eat's buffer-local [mode]:status `mode-line-process'.
+The modeline's own eat segment (cj/--modeline-eat-state in
+modeline-config.el) renders the input mode and process state as icons
+on the left, so eat's text form would be a duplicate."
+ (setq mode-line-process nil))
+
(use-package eat
:ensure t
:commands (eat)
:hook ((eat-mode . cj/turn-off-chrome-for-term)
- (eat-mode . cj/--eat-tame-scroll))
+ (eat-mode . cj/--eat-tame-scroll)
+ (eat-mode . cj/--eat-clear-mode-line-process))
:custom
;; Close the EAT buffer when its shell exits.
(eat-kill-buffer-on-exit t)
diff --git a/modules/modeline-config.el b/modules/modeline-config.el
index 4cc7fb29..be7f72e5 100644
--- a/modules/modeline-config.el
+++ b/modules/modeline-config.el
@@ -16,22 +16,30 @@
;; Simple, minimal modeline built on Emacs 30's own right-alignment.
;; Segments are pure helpers wired in with thin :eval forms.
;;
-;; Left side:
+;; Layout principle: the LEFT side is Emacs information -- everything
+;; about this buffer and this Emacs (identity, state, position, VC,
+;; process). The RIGHT side is a systray for package indicators
+;; (recording, flycheck counts, the misc-info icons).
+;;
+;; Left side, in order:
;; - Padding space (optional taller bar via `cj/modeline-height-factor')
;; - Major-mode icon (nerd-icons; falls back to the mode name in
;; terminal frames or when nerd-icons is absent)
+;; - eat terminal state: input-mode + process icons with hover text
+;; (replaces eat's own [semi-char]:run mode-line-process)
;; - Modified dot / read-only lock
;; - Buffer name (click to cycle buffers)
;; - Remote @host tag for TRAMP buffers
;; - Narrow tag when the buffer is narrowed (click to widen)
+;; - VC branch colored by state (click for diffs)
;; - Line/column and percentage; selection info while the region is active
;; - MACRO tag while a keyboard macro is recording
+;; - Process state for non-eat buffers (comint/compilation via
+;; `mode-line-process'; eat buffers get theirs cleared in eat-config)
;;
-;; Right side:
+;; Right side (the systray):
;; - Recording indicator (video-audio-recording capture)
;; - Flycheck error/warning counts (click to list errors)
-;; - Process state (eat/comint/compilation via `mode-line-process')
-;; - VC branch colored by state (click for diffs)
;; - Misc info (chime notifications, weather, etc.)
;;
;; Glyphs are nerd-icons private-use codepoints or plain unicode shapes
@@ -234,6 +242,75 @@ characters selected)."
'face 'error
'help-echo "Recording keyboard macro\nF4 or C-x ) to stop"))))
+;; ----------------------------- Eat State Segment ------------------------------
+
+(defconst cj/--modeline-eat-mode-help
+ '((semi-char . "most keys go to the terminal; Emacs keeps C-x, M-x, and the F-keys")
+ (char . "raw: nearly every key goes to the terminal")
+ (line . "line editing in Emacs; RET sends the line")
+ (emacs . "all keys are Emacs; terminal output is read-only"))
+ "Hover-text description per eat input mode.")
+
+(defun cj/--modeline-eat-input-mode ()
+ "Return the current eat input mode as a symbol."
+ (cond ((bound-and-true-p eat--semi-char-mode) 'semi-char)
+ ((bound-and-true-p eat--char-mode) 'char)
+ ((bound-and-true-p eat--line-mode) 'line)
+ (t 'emacs)))
+
+(defun cj/--modeline-eat-switch-map (mode)
+ "Return a mode-line keymap switching away from eat input MODE.
+Mirrors eat's own mouse-1/2/3 assignments for each mode."
+ (let ((map (make-sparse-keymap))
+ (targets (pcase mode
+ ('semi-char '(eat-char-mode eat-line-mode eat-emacs-mode))
+ ('char '(eat-semi-char-mode eat-line-mode eat-emacs-mode))
+ ('line '(eat-semi-char-mode eat-emacs-mode eat-char-mode))
+ (_ '(eat-semi-char-mode eat-line-mode eat-char-mode)))))
+ (define-key map [mode-line down-mouse-1] (nth 0 targets))
+ (define-key map [mode-line down-mouse-2] (nth 1 targets))
+ (define-key map [mode-line down-mouse-3] (nth 2 targets))
+ map))
+
+(defun cj/--modeline-eat-state ()
+ "Input-mode + process icons for eat terminal buffers, or nil elsewhere.
+Replaces eat's own [semi-char]:run `mode-line-process' text (cleared in
+eat-config): a keyboard glyph colored quiet for semi-char and warning
+for the other modes, then a running/exited indicator. Hover text
+explains each; the keyboard glyph clicks through eat's mode switches."
+ (when (derived-mode-p 'eat-mode)
+ (let* ((mode (cj/--modeline-eat-input-mode))
+ (mode-face (if (eq mode 'semi-char) 'shadow 'warning))
+ (icons-p (and (display-graphic-p) (fboundp 'nerd-icons-faicon)))
+ (mode-glyph (or (and icons-p
+ (ignore-errors
+ (nerd-icons-faicon "nf-fa-keyboard_o" :face mode-face)))
+ (propertize (upcase (substring (symbol-name mode) 0 1))
+ 'face mode-face)))
+ (proc (get-buffer-process (current-buffer)))
+ (live (and proc (process-live-p proc)))
+ (proc-glyph (if live
+ (or (and icons-p
+ (ignore-errors
+ (nerd-icons-faicon "nf-fa-play" :face 'success)))
+ (propertize "run" 'face 'success))
+ (or (and icons-p
+ (ignore-errors
+ (nerd-icons-faicon "nf-fa-power_off" :face 'error)))
+ (propertize "exit" 'face 'error)))))
+ (concat " "
+ (propertize mode-glyph
+ 'mouse-face 'mode-line-highlight
+ 'help-echo (format "Input mode: %s -- %s\nmouse-1/2/3: switch input modes"
+ mode
+ (alist-get mode cj/--modeline-eat-mode-help))
+ 'local-map (cj/--modeline-eat-switch-map mode))
+ " "
+ (propertize proc-glyph
+ 'help-echo (if live
+ "Terminal process: running"
+ "Terminal process exited"))))))
+
;; ------------------------------ Flycheck Segment ------------------------------
(defvar cj/--modeline-flycheck-glyphs nil
@@ -409,18 +486,23 @@ Shows only in active window.")
(setq-default mode-line-format
'("%e" ; Error message if out of memory
- ;; LEFT SIDE
+ ;; LEFT SIDE -- Emacs information: identity, state, position, VC, process
(:eval (cj/--modeline-padding))
(:eval (cj/--modeline-mode-icon))
+ (:eval (cj/--modeline-eat-state))
" "
(:eval (cj/--modeline-buffer-status))
cj/modeline-buffer-name
(:eval (cj/--modeline-remote-host))
(:eval (cj/--modeline-narrow-indicator))
" "
+ cj/modeline-vc-branch
+ " "
(:eval (cj/--modeline-position-info))
(:eval (cj/--modeline-macro-indicator))
- ;; RIGHT SIDE (using Emacs 30 built-in right-align)
+ " "
+ mode-line-process
+ ;; RIGHT SIDE -- the package systray (using Emacs 30 built-in right-align)
;; Order: leftmost to rightmost as they appear in the list
mode-line-format-right-align
(:eval (when (fboundp 'cj/recording-modeline-indicator)
@@ -432,10 +514,6 @@ Shows only in active window.")
(bound-and-true-p flycheck-mode))
(cj/--modeline-flycheck-status)))
" "
- mode-line-process
- " "
- cj/modeline-vc-branch
- " "
cj/modeline-misc-info
" "))
diff --git a/tests/test-modeline-config-eat-state.el b/tests/test-modeline-config-eat-state.el
new file mode 100644
index 00000000..7d20c681
--- /dev/null
+++ b/tests/test-modeline-config-eat-state.el
@@ -0,0 +1,94 @@
+;;; test-modeline-config-eat-state.el --- eat input-mode/process segment -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for `cj/--modeline-eat-state', the left-side segment that shows
+;; an eat terminal's input mode and process state as icons with
+;; explanatory hover text (replacing eat's own [semi-char]:run
+;; mode-line-process). eat isn't installed under `make test', so the
+;; tests fake an eat buffer: `major-mode' set to `eat-mode' and the
+;; input-mode flags defvar'd here. Batch has no graphic display, so the
+;; glyphs exercise their letter/text fallbacks.
+
+;;; Code:
+
+(require 'ert)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+(require 'modeline-config)
+
+(defvar eat--semi-char-mode nil)
+(defvar eat--char-mode nil)
+(defvar eat--line-mode nil)
+
+(defmacro test-eat-state--with-fake-eat-buffer (&rest body)
+ "Run BODY in a temp buffer masquerading as an eat-mode buffer."
+ `(with-temp-buffer
+ (setq major-mode 'eat-mode)
+ ,@body))
+
+(ert-deftest test-modeline-eat-state-nil-outside-eat ()
+ "Boundary: non-eat buffers get no segment."
+ (with-temp-buffer
+ (should-not (cj/--modeline-eat-state))))
+
+(ert-deftest test-modeline-eat-state-semi-char-quiet-face ()
+ "Normal: semi-char (the default mode) renders in the quiet shadow face."
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--semi-char-mode t)
+ (let ((s (cj/--modeline-eat-state)))
+ (should (stringp s))
+ (should (text-property-any 0 (length s) 'face 'shadow s)))))
+
+(ert-deftest test-modeline-eat-state-char-mode-warning-face ()
+ "Normal: char mode (raw keys) renders in the warning face."
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--char-mode t)
+ (let ((s (cj/--modeline-eat-state)))
+ (should (stringp s))
+ (should (text-property-any 0 (length s) 'face 'warning s)))))
+
+(ert-deftest test-modeline-eat-state-input-mode-detection ()
+ "Normal: the four input-mode flags map to the right symbols."
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--semi-char-mode t)
+ (should (eq (cj/--modeline-eat-input-mode) 'semi-char)))
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--char-mode t)
+ (should (eq (cj/--modeline-eat-input-mode) 'char)))
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--line-mode t)
+ (should (eq (cj/--modeline-eat-input-mode) 'line)))
+ (test-eat-state--with-fake-eat-buffer
+ (should (eq (cj/--modeline-eat-input-mode) 'emacs))))
+
+(ert-deftest test-modeline-eat-state-dead-process-error-face ()
+ "Error: no live process renders the exited indicator in the error face."
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--semi-char-mode t)
+ (let ((s (cj/--modeline-eat-state)))
+ (should (text-property-any 0 (length s) 'face 'error s)))))
+
+(ert-deftest test-modeline-eat-state-live-process-success-face ()
+ "Normal: a live buffer process renders the running indicator, no error face."
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--semi-char-mode t)
+ (let ((proc (start-process "test-eat-state" (current-buffer) "sleep" "10")))
+ (unwind-protect
+ (let ((s (cj/--modeline-eat-state)))
+ (should (text-property-any 0 (length s) 'face 'success s))
+ (should-not (text-property-any 0 (length s) 'face 'error s)))
+ (set-process-query-on-exit-flag proc nil)
+ (kill-process proc)))))
+
+(ert-deftest test-modeline-eat-state-hover-text-explains ()
+ "Normal: the input-mode glyph carries explanatory help-echo."
+ (test-eat-state--with-fake-eat-buffer
+ (setq-local eat--semi-char-mode t)
+ (let* ((s (cj/--modeline-eat-state))
+ (pos (text-property-not-all 0 (length s) 'help-echo nil s)))
+ (should pos)
+ (should (string-match-p "semi-char" (get-text-property pos 'help-echo s))))))
+
+(provide 'test-modeline-config-eat-state)
+;;; test-modeline-config-eat-state.el ends here