;; -*- lexical-binding: t; -*- ;; Aesthetic Computer Emacs Configuration, 2024.3.13.12.51 ;; Debug logging - persistent location that survives container rebuilds (defvar ac-debug-log-file "/workspaces/aesthetic-computer/.emacs-logs/emacs-debug.log") (defvar ac-perf-log-file "/workspaces/aesthetic-computer/.emacs-logs/emacs-perf.log") (defvar ac-profile-log-file "/workspaces/aesthetic-computer/.emacs-logs/emacs-profile.log") (defvar ac--startup-time (current-time)) (defvar ac--last-perf-time (current-time)) ;; Boot progress tracking (defvar ac--boot-buffer-name "boot" "Name of the boot progress buffer.") (defvar ac--boot-step-count 0 "Current step number in boot sequence.") (defvar ac--boot-total-steps 14 "Total expected steps: artery, fishy, + 10 tabs + cdp + finalize.") (defvar ac--boot-start-time nil "Time when boot started.") (defun ac-debug-log (message) "Log a debug message with timestamp." (with-temp-buffer (insert (format "[%s] %s\n" (format-time-string "%Y-%m-%d %H:%M:%S.%3N") message)) (append-to-file (point-min) (point-max) ac-debug-log-file))) (defun ac-perf-log (label) "Log performance timing since startup and since last checkpoint." (let* ((now (current-time)) (total-ms (round (* 1000 (float-time (time-subtract now ac--startup-time))))) (delta-ms (round (* 1000 (float-time (time-subtract now ac--last-perf-time)))))) (setq ac--last-perf-time now) (with-temp-buffer (insert (format "[%s] PERF: %s | +%dms (total: %dms)\n" (format-time-string "%Y-%m-%d %H:%M:%S.%3N") label delta-ms total-ms)) (append-to-file (point-min) (point-max) ac-perf-log-file)))) (defun ac-perf-session-start () "Mark the start of a new Emacs session in perf log." (with-temp-buffer (insert (format "\n=== NEW SESSION: %s ===\n" (format-time-string "%Y-%m-%d %H:%M:%S"))) (append-to-file (point-min) (point-max) ac-perf-log-file))) ;;; =================================================================== ;;; BOOT PROGRESS BUFFER ;;; =================================================================== (defface ac-boot-header-face '((t :foreground "#00ff00" :weight bold :height 1.3)) "Face for boot header.") (defface ac-boot-step-pending-face '((t :foreground "#666666")) "Face for pending boot steps.") (defface ac-boot-step-active-face '((t :foreground "#ffff00" :weight bold)) "Face for active boot step.") (defface ac-boot-step-done-face '((t :foreground "#00ff00")) "Face for completed boot steps.") (defface ac-boot-step-error-face '((t :foreground "#ff4444" :weight bold)) "Face for failed boot steps.") (defface ac-boot-progress-face '((t :foreground "#00aaff")) "Face for progress bar.") (defvar ac--boot-steps '(("artery" . "๐Ÿฉธ Artery dev server") ("status" . "๐Ÿ“ก Status monitors") ("stripe" . "๐Ÿ’ณ Stripe listeners") ("chat" . "๐Ÿ’ฌ Chat services") ("web 1/2" . "๐ŸŒ Web servers (1/2)") ("web 2/2" . "๐ŸŒ Web servers (2/2)") ("tests" . "๐Ÿงช Test runners") ("llm" . "๐Ÿค– LLM interface") ("mail" . "๐Ÿ“ฌ Mail sync") ("signal" . "๐Ÿ“ถ Signal messenger") ("top" . "๐Ÿ“Š System monitor") ("fishy" . "๐ŸŸ Fish shell") ("cdp" . "๐Ÿ”— CDP tunnel") ("finalize" . "โœจ Finalizing")) "Boot steps with descriptions.") (defvar ac--boot-step-status (make-hash-table :test 'equal) "Hash table tracking status of each boot step: pending, active, done, error.") (defun ac--boot-init () "Initialize the boot progress buffer." (setq ac--boot-start-time (current-time) ac--boot-step-count 0) (clrhash ac--boot-step-status) ;; Set all steps to pending (dolist (step ac--boot-steps) (puthash (car step) 'pending ac--boot-step-status)) ;; Create/get the boot buffer (let ((buf (get-buffer-create ac--boot-buffer-name))) (with-current-buffer buf (let ((inhibit-read-only t)) (erase-buffer) (ac--boot-render))) buf)) (defun ac--boot-render () "Render the boot progress buffer." (let ((inhibit-read-only t) (elapsed (if ac--boot-start-time (float-time (time-subtract (current-time) ac--boot-start-time)) 0))) (erase-buffer) (insert "\n") (insert (propertize " โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" 'face 'ac-boot-header-face)) (insert (propertize " โ•‘ ๐Ÿš€ AESTHETIC COMPUTER BOOT SEQUENCE โ•‘\n" 'face 'ac-boot-header-face)) (insert (propertize " โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" 'face 'ac-boot-header-face)) (insert "\n") ;; Progress bar (let* ((pct (if (> ac--boot-total-steps 0) (/ (* 100.0 ac--boot-step-count) ac--boot-total-steps) 0)) (bar-width 50) (filled (round (* bar-width (/ pct 100.0)))) (empty (- bar-width filled))) (insert (format " Progress: [%s%s] %d%% (%.1fs)\n\n" (propertize (make-string filled ?โ–ˆ) 'face 'ac-boot-progress-face) (make-string empty ?โ–‘) (round pct) elapsed))) ;; Step list (insert " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n") (dolist (step ac--boot-steps) (let* ((step-id (car step)) (step-desc (cdr step)) (status (gethash step-id ac--boot-step-status 'pending)) (indicator (pcase status ('pending " โ—‹") ('active " โ—") ('done " โœ“") ('error " โœ—"))) (face (pcase status ('pending 'ac-boot-step-pending-face) ('active 'ac-boot-step-active-face) ('done 'ac-boot-step-done-face) ('error 'ac-boot-step-error-face)))) (insert (propertize (format "%s %s\n" indicator step-desc) 'face face)))) (insert " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n\n") ;; Status message (let ((active-step (cl-find 'active (hash-table-values ac--boot-step-status)))) (if active-step (let ((active-id (cl-find-if (lambda (k) (eq (gethash k ac--boot-step-status) 'active)) (hash-table-keys ac--boot-step-status)))) (insert (propertize (format " โณ Currently: %s...\n" (cdr (assoc active-id ac--boot-steps))) 'face 'ac-boot-step-active-face))) (if (= ac--boot-step-count ac--boot-total-steps) (insert (propertize " โœ… Boot complete!\n" 'face 'ac-boot-step-done-face)) (insert " โธ Waiting...\n")))) (insert "\n Press 'q' to close this buffer, 'g' to refresh\n") (goto-char (point-min)))) (defun ac--boot-update-step (step-id status) "Update STEP-ID to STATUS and refresh display." (puthash step-id status ac--boot-step-status) (when (eq status 'done) (setq ac--boot-step-count (1+ ac--boot-step-count))) ;; Also log it (ac-debug-log (format "BOOT: %s -> %s" step-id status)) ;; Update buffer if visible (when-let ((buf (get-buffer ac--boot-buffer-name))) (with-current-buffer buf (ac--boot-render)) ;; Force redisplay to show progress (redisplay t))) (defun ac--boot-start-step (step-id) "Mark STEP-ID as active." (ac--boot-update-step step-id 'active)) (defun ac--boot-complete-step (step-id) "Mark STEP-ID as done." (ac--boot-update-step step-id 'done)) (defun ac--boot-error-step (step-id) "Mark STEP-ID as error." (ac--boot-update-step step-id 'error)) (defun ac-boot-show () "Show the boot progress buffer." (interactive) (let ((buf (get-buffer ac--boot-buffer-name))) (when buf (display-buffer buf '(display-buffer-pop-up-window))))) (define-derived-mode ac-boot-mode special-mode "AC-Boot" "Mode for viewing boot progress." (setq buffer-read-only t) (local-set-key (kbd "g") (lambda () (interactive) (ac--boot-render))) (local-set-key (kbd "q") 'quit-window)) ;;; =================================================================== ;;; PROFILING & DIAGNOSTICS ;;; =================================================================== (defvar ac--profiling-active nil "Whether CPU profiling is currently running.") (defun ac-profile-start () "Start CPU profiler and log to file." (interactive) (unless ac--profiling-active (profiler-start 'cpu) (setq ac--profiling-active t) (ac-debug-log "PROFILER: Started CPU profiling"))) (defun ac-profile-stop () "Stop profiler and write report to log file." (interactive) (when ac--profiling-active (let ((report (profiler-report-cpu))) (with-temp-buffer (insert (format "\n=== PROFILE REPORT: %s ===\n" (format-time-string "%Y-%m-%d %H:%M:%S"))) (profiler-report) (append-to-file (point-min) (point-max) ac-profile-log-file))) (profiler-stop) (setq ac--profiling-active nil) (ac-debug-log "PROFILER: Stopped, report written"))) (defun ac-profile-report () "Show profiler report interactively." (interactive) (profiler-report)) (defun ac-diagnose-eat () "Diagnose all eat terminal buffers - log their state." (interactive) (let ((eat-buffers (seq-filter (lambda (b) (with-current-buffer b (eq major-mode 'eat-mode))) (buffer-list)))) (ac-debug-log (format "DIAGNOSE: Found %d eat buffers" (length eat-buffers))) (dolist (buf eat-buffers) (with-current-buffer buf (let* ((proc (get-buffer-process buf)) (proc-status (if proc (process-status proc) 'no-process)) (buf-size (buffer-size))) (ac-debug-log (format " BUFFER: %s | size=%d | proc=%s" (buffer-name) buf-size proc-status))))) (message "Diagnosed %d eat buffers - see debug log" (length eat-buffers)))) (defun ac-diagnose-timers () "Log all active timers." (interactive) (ac-debug-log (format "DIAGNOSE: %d active timers" (length timer-list))) (dolist (timer timer-list) (ac-debug-log (format " TIMER: %s repeat=%s" (timer--function timer) (timer--repeat-delay timer)))) (message "Logged %d timers" (length timer-list))) (defun ac-diagnose-processes () "Log all active processes." (interactive) (let ((procs (process-list))) (ac-debug-log (format "DIAGNOSE: %d active processes" (length procs))) (dolist (proc procs) (ac-debug-log (format " PROC: %s | status=%s | buffer=%s" (process-name proc) (process-status proc) (if (process-buffer proc) (buffer-name (process-buffer proc)) "none")))) (message "Logged %d processes" (length procs)))) (defun ac-diagnose-all () "Run all diagnostics." (interactive) (ac-debug-log "=== FULL DIAGNOSTIC START ===") (ac-diagnose-eat) (ac-diagnose-timers) (ac-diagnose-processes) (ac-debug-log "=== FULL DIAGNOSTIC END ===") (message "Full diagnostic complete - see .emacs-logs/emacs-debug.log")) ;; Keybindings for diagnostics (global-set-key (kbd "C-c d e") 'ac-diagnose-eat) (global-set-key (kbd "C-c d t") 'ac-diagnose-timers) (global-set-key (kbd "C-c d p") 'ac-diagnose-processes) (global-set-key (kbd "C-c d a") 'ac-diagnose-all) (global-set-key (kbd "C-c d s") 'ac-profile-start) (global-set-key (kbd "C-c d x") 'ac-profile-stop) (global-set-key (kbd "C-c d r") 'ac-profile-report) (global-set-key (kbd "C-c d b") 'ac-boot-show) ; Show boot progress ;;; --- MCP State Awareness Functions --- ;; These functions provide structured state info for MCP tools (defun ac-mcp-get-state () "Return comprehensive emacs state as JSON-friendly alist for MCP tools. Call this to get awareness of current buffers, tabs, and processes." (let* ((current-buf (current-buffer)) (current-buf-name (buffer-name current-buf)) (current-tab (alist-get 'name (tab-bar--current-tab))) (all-tabs (mapcar (lambda (tab) (alist-get 'name tab)) (tab-bar-tabs))) (eat-buffers (seq-filter (lambda (b) (with-current-buffer b (eq major-mode 'eat-mode))) (buffer-list))) (eat-info (mapcar (lambda (buf) (with-current-buffer buf (let* ((proc (get-buffer-process buf)) (proc-status (if proc (symbol-name (process-status proc)) "no-process"))) (list (cons 'name (buffer-name buf)) (cons 'process proc-status) (cons 'alive (and proc (process-live-p proc))))))) eat-buffers))) (list (cons 'currentBuffer current-buf-name) (cons 'currentTab current-tab) (cons 'allTabs all-tabs) (cons 'eatBuffers eat-info)))) (defun ac-mcp-get-buffer-state (buffer-name) "Get detailed state of a specific buffer for MCP tools. Returns nil if buffer doesn't exist, otherwise returns alist with: - exists: always t - mode: major mode name - process: process status or nil - alive: t if process is live - modified: t if buffer has unsaved changes - size: buffer character count" (if-let ((buf (get-buffer buffer-name))) (with-current-buffer buf (let* ((proc (get-buffer-process buf)) (proc-status (when proc (symbol-name (process-status proc))))) (list (cons 'exists t) (cons 'mode (symbol-name major-mode)) (cons 'process proc-status) (cons 'alive (and proc (process-live-p proc))) (cons 'modified (buffer-modified-p)) (cons 'size (buffer-size))))) nil)) (defun ac-mcp-get-eat-status (buffer-name) "Get status of an eat terminal buffer. Returns: 'running, 'exited, 'not-eat, or 'not-found" (if-let ((buf (get-buffer buffer-name))) (with-current-buffer buf (if (eq major-mode 'eat-mode) (let ((proc (get-buffer-process buf))) (cond ((not proc) 'exited) ((process-live-p proc) 'running) (t 'exited))) 'not-eat)) 'not-found)) (defun ac-mcp-format-state () "Return emacs state as formatted string for MCP tools." (let* ((state (ac-mcp-get-state)) (current-buf (alist-get 'currentBuffer state)) (current-tab (alist-get 'currentTab state)) (tabs (alist-get 'allTabs state)) (eats (alist-get 'eatBuffers state))) (format "Tab: %s | Buffer: %s | Tabs: [%s] | Terminals: %s" current-tab current-buf (mapconcat #'identity tabs ", ") (mapconcat (lambda (e) (format "%s(%s)" (alist-get 'name e) (if (alist-get 'alive e) "โ—" "โ—‹"))) eats ", ")))) (defun ac-mcp-ensure-buffer-ready (buffer-name &optional restart-fn) "Ensure buffer exists and is usable. Returns status symbol. If buffer has dead process and RESTART-FN is provided, calls it. Returns: 'ready, 'restarted, 'dead, or 'not-found" (if-let ((buf (get-buffer buffer-name))) (with-current-buffer buf (if (eq major-mode 'eat-mode) (let ((proc (get-buffer-process buf))) (cond ((and proc (process-live-p proc)) 'ready) (restart-fn (funcall restart-fn) 'restarted) (t 'dead))) 'ready)) 'not-found)) ;;; --- Copilot Completion Notification --- ;; Flash current terminal when Copilot response is done (called via MCP) (defvar ac-notify-flash-active nil "Whether notification flash is currently active.") (defvar ac-notify-flash-timer nil "Timer for notification flashing.") (defvar ac-notify-flash-count 0 "Number of flash cycles completed.") (defvar ac-notify-original-bg nil "Original background color before flash.") (defvar ac-notify-flash-colors '("#1a3a1a" ; dark green "#3a1a3a" ; dark magenta "#1a1a3a" ; dark blue "#3a3a1a" ; dark yellow "#3a1a1a" ; dark red "#1a3a3a") ; dark cyan "Colors to cycle through during notification flash.") (defun ac-notify-stop-flash () "Stop the notification flash and restore original background." (interactive) (when ac-notify-flash-timer (cancel-timer ac-notify-flash-timer) (setq ac-notify-flash-timer nil)) (setq ac-notify-flash-active nil) (setq ac-notify-flash-count 0) ;; Restore original background (when ac-notify-original-bg (set-face-background 'default ac-notify-original-bg) (setq ac-notify-original-bg nil)) ;; Force redisplay (redisplay t)) (defun ac-notify-do-flash () "Perform one flash cycle with rainbow colors." (when ac-notify-flash-active (setq ac-notify-flash-count (1+ ac-notify-flash-count)) ;; Alternate between flash colors and original (if (= (mod ac-notify-flash-count 2) 1) ;; Pick a color from the list, cycling through (let* ((color-idx (mod (/ ac-notify-flash-count 2) (length ac-notify-flash-colors))) (flash-color (nth color-idx ac-notify-flash-colors))) (set-face-background 'default flash-color)) (set-face-background 'default ac-notify-original-bg)) (redisplay t) ;; Continue flashing (setq ac-notify-flash-timer (run-at-time 0.25 nil #'ac-notify-do-flash)))) ;; ๐ŸŽฏ Task State Management (for VS Code status bar integration) (defvar ac-task-state-file "/tmp/aesthetic-task-state.json" "Path to the task state file that VS Code watches.") (defun ac-task-set (status &optional label progress) "Set the task state. STATUS can be: idle, working, done, error. LABEL is optional description text. PROGRESS is optional percentage (0-100). VS Code status bar will update to reflect this state." (let* ((timestamp (truncate (* 1000 (float-time)))) (state `((status . ,status) (label . ,(or label status)) (progress . ,(or progress (if (equal status "done") 100 0))) (timestamp . ,timestamp)))) (with-temp-file ac-task-state-file (insert (json-encode state))) (message "๐ŸŽฏ Task: %s - %s" status (or label "")))) (defun ac-task-working (&optional label) "Set task status to working (yellow, spinning icon)." (interactive "sTask label: ") (ac-task-set "working" (or label "Working..."))) (defun ac-task-done (&optional label) "Set task status to done (green, flash effect)." (interactive "sTask label: ") (ac-task-set "done" (or label "Done!"))) (defun ac-task-error (&optional label) "Set task status to error (red)." (interactive "sTask label: ") (ac-task-set "error" (or label "Error"))) (defun ac-task-idle () "Clear task status (back to idle)." (interactive) (ac-task-set "idle" "")) (defun ac-task-clear () "Remove the task state file entirely." (interactive) (when (file-exists-p ac-task-state-file) (delete-file ac-task-state-file) (message "๐ŸŽฏ Task state cleared"))) (defun ac-notify-done (&optional message) "Notify completion by flashing the current buffer background. Flash continues until user clicks or presses a key. Called by MCP tools at the end of each response. Also updates VS Code task status bar to 'done'." (interactive) (let ((msg (or message "Done"))) ;; Also signal artery-tui (for when viewing that tab) (with-temp-file "/tmp/ac-copilot-done" (insert "done")) ;; Update VS Code task status bar (ac-task-done msg) ;; Start flashing in current buffer (unless ac-notify-flash-active (setq ac-notify-original-bg (face-background 'default nil t)) (setq ac-notify-flash-active t) (setq ac-notify-flash-count 0) (ac-notify-do-flash) ;; Stop flash on any input (mouse or keyboard) (add-hook 'pre-command-hook #'ac-notify-stop-flash)) (message "๐Ÿ”” %s" msg))) (ac-perf-session-start) (ac-debug-log "=== Starting Emacs Configuration Load ===") (ac-perf-log "Config load started") ;; Performance settings (ac-debug-log "Setting performance options...") (setq native-comp-async-report-warnings-errors nil native-comp-deferred-compilation nil native-comp-jit-compilation nil ; Disable JIT native compilation native-comp-async-jobs-number 1 ; Limit async jobs if any do run native-comp-async-query-on-exit nil ; Don't query on exit comp-async-report-warnings-errors nil ; Suppress comp warnings comp-deferred-compilation nil ; Also disable via comp- prefix x-gtk-use-system-tooltips nil mac-option-modifier 'meta warning-minimum-level :emergency ;; Terminal/process output throttling read-process-output-max (* 16 1024) ; 16KB chunks instead of 64KB process-adaptive-read-buffering t ; Let emacs batch reads redisplay-dont-pause nil ; Allow redisplay interruption fast-but-imprecise-scrolling t ; Faster scroll in terminals bidi-inhibit-bpa t ; Disable bidi for terminals bidi-paragraph-direction 'left-to-right) ; No bidi scanning ;; Kill any runaway native comp processes on startup (when (fboundp 'native-comp-available-p) (advice-add 'native-compile-async :override (lambda (&rest _) nil))) ; Completely disable async native comp (ac-debug-log "Performance settings loaded") (ac-perf-log "Performance settings loaded") (global-set-key [C-down-mouse-1] 'ignore) ;; Tab bar (tab-bar-mode t) (setq tab-bar-new-tab-choice "*scratch*" tab-bar-hints t tab-bar-format '(tab-bar-format-tabs tab-bar-separator) tab-bar-close-button-show nil tab-line-tab-max-width 20) ;; Custom colored tab bar - each tab gets its own background color (defun ac-tab-bar-tab-name-format (tab i) "Format TAB name with custom colors based on tab name." (let* ((current-p (eq (car tab) 'current-tab)) (tab-name (alist-get 'name tab)) ;; Define colors for each tab type (bg . fg) (tab-colors '(("artery" . ("#8B0000" . "#FFFFFF")) ; Dark red ("status" . ("#006400" . "#FFFFFF")) ; Dark green ("stripe" . ("#4B0082" . "#FFFFFF")) ; Indigo ("chat" . ("#00008B" . "#FFFFFF")) ; Dark blue ("web 1/2" . ("#008B8B" . "#FFFFFF")) ; Dark cyan ("web 2/2" . ("#2F4F4F" . "#FFFFFF")) ; Dark slate gray ("tests" . ("#8B4513" . "#FFFFFF")) ; Saddle brown ("llm" . ("#5B2C83" . "#FFFFFF")) ; Deep purple ("signal" . ("#3A76F0" . "#FFFFFF")) ; Signal blue ("fishy" . ("#005F73" . "#FFFFFF")))) ; Deep teal (colors (or (cdr (assoc tab-name tab-colors)) '("#333333" . "#FFFFFF"))) ; Default dark gray (bg-color (car colors)) (fg-color (cdr colors)) ;; Make current tab brighter (face-spec (if current-p `(:background ,bg-color :foreground ,fg-color :weight bold :box (:line-width 2 :color "#FFFFFF")) `(:background ,bg-color :foreground ,fg-color :weight normal)))) (propertize (format " %d:%s " i tab-name) 'face face-spec))) (setq tab-bar-tab-name-format-function #'ac-tab-bar-tab-name-format) ;; Display settings (setq scroll-step 1) (global-auto-revert-mode 1) (setq auto-revert-interval 5) (setq-default display-fill-column-indicator-column 80) (add-hook 'prog-mode-hook (lambda () (display-fill-column-indicator-mode 1))) ;; Line numbers (defun disable-line-numbers-in-modes () (when (or (derived-mode-p 'eshell-mode) (derived-mode-p 'eat-mode)) (display-line-numbers-mode -1))) (defun ac-optimize-eat-terminal () "Disable heavy modes in eat terminal buffers for better performance." ;; Disable line numbers (display-line-numbers-mode -1) ;; Disable evil-mode (use emacs keybindings in terminals) (when (and (boundp 'evil-local-mode) (fboundp 'evil-local-mode)) (evil-local-mode -1)) ;; Disable origami (code folding not needed in terminal) (when (and (boundp 'origami-mode) (fboundp 'origami-mode)) (origami-mode -1)) ;; Disable yascroll (scrollbar causes rendering overhead) (when (and (boundp 'yascroll-bar-mode) (fboundp 'yascroll-bar-mode)) (yascroll-bar-mode -1)) ;; Disable fill column indicator (when (fboundp 'display-fill-column-indicator-mode) (display-fill-column-indicator-mode -1))) ;; Auto-scroll eat terminals to bottom on new output. ;; Strategy: a single global idle timer scrolls all dirty visible buffers. ;; The hook just marks buffers dirty (cheap hash-set op, no timer churn). ;; This keeps overhead constant regardless of how many terminals produce output. (defvar ac--eat-dirty-bufs (make-hash-table :test 'eq) "Set of eat buffers that received output since last scroll.") (defvar ac--eat-scroll-idle-timer nil "Single global idle timer for eat auto-scroll.") (defun ac--eat-scroll-dirty () "Scroll all dirty eat buffers that have a visible window, then clear." (maphash (lambda (buf _) (when (buffer-live-p buf) (let ((window (get-buffer-window buf))) (when window (with-selected-window window (goto-char (point-max)) (recenter -1)))))) ac--eat-dirty-bufs) (clrhash ac--eat-dirty-bufs)) (defun ac-eat-auto-scroll () "Mark current eat buffer as needing scroll. Actual scroll happens on idle." (when (derived-mode-p 'eat-mode) (puthash (current-buffer) t ac--eat-dirty-bufs))) ;; Single idle timer โ€” fires 0.5s after Emacs goes idle, repeats every 1s (with-eval-after-load 'eat (add-hook 'eat-update-hook #'ac-eat-auto-scroll) (setq ac--eat-scroll-idle-timer (run-with-idle-timer 0.5 t #'ac--eat-scroll-dirty))) (add-hook 'eshell-mode-hook 'disable-line-numbers-in-modes) (add-hook 'eat-mode-hook 'disable-line-numbers-in-modes) (add-hook 'eat-mode-hook 'ac-optimize-eat-terminal) (add-hook 'image-mode-hook 'disable-line-numbers-in-modes) ;; Electric pairs (defun enable-electric-pairs () (setq-local electric-pair-pairs '((?\{ . ?\}) (?\( . ?\)) (?\[ . ?\]) (?' . ?') (?` . ?`))) (electric-pair-local-mode 1)) (add-hook 'prog-mode-hook 'enable-electric-pairs) ;; Auto-save (auto-save-visited-mode 1) (add-hook 'before-save-hook 'delete-trailing-whitespace) (defun auto-save-buffer () (when (or (derived-mode-p 'prog-mode) (derived-mode-p 'text-mode)) (save-buffer))) (when (window-system) (defvar auto-save-throttle-timer nil) (defun throttled-auto-save () (when auto-save-throttle-timer (cancel-timer auto-save-throttle-timer)) (setq auto-save-throttle-timer (run-with-timer 3 nil #'auto-save-buffer))) (add-hook 'focus-out-hook 'auto-save-buffer)) ;; Faces (custom-set-faces '(tab-bar ((t (:height 1.0)))) '(tab-bar-tab-inactive ((t (:inherit tab-bar-tab :inverse-video t))))) ;; UI settings (setq inhibit-startup-screen t eshell-banner-message "" initial-scratch-message nil) (global-display-line-numbers-mode) ;; Windows WSL (when (eq system-type 'windows-nt) (setq explicit-shell-file-name "C:/Windows/System32/bash.exe" shell-file-name explicit-shell-file-name) (setenv "SHELL" shell-file-name) (add-to-list 'exec-path "C:/Windows/System32")) ;; Mouse scrolling (global-set-key (kbd "") (lambda () (interactive) (scroll-down 1))) (global-set-key (kbd "") (lambda () (interactive) (scroll-up 1))) ;; Tab navigation (defun my-tab-next () (interactive) (tab-next)) (defun my-tab-previous () (interactive) (tab-previous)) (global-set-key (kbd "C-x ") 'my-tab-next) (global-set-key (kbd "C-x ") 'my-tab-previous) ;; GUI settings (when (display-graphic-p) (scroll-bar-mode -1) (fringe-mode 0)) (setq-default scroll-bar-mode 'right) (menu-bar-mode -1) (tool-bar-mode -1) ;; Mouse and bell (xterm-mouse-mode 1) (defun track-mouse (e)) (setq mouse-sel-mode t ring-bell-function 'ignore) ;; Backup directories (defvar my-backup-directory "~/.emacs.d/backups/") (unless (file-exists-p my-backup-directory) (make-directory my-backup-directory t)) (setq backup-directory-alist `(("." . ,my-backup-directory))) (defvar my-auto-save-directory "~/.emacs.d/auto-saves/") (unless (file-exists-p my-auto-save-directory) (make-directory my-auto-save-directory t)) (setq auto-save-file-name-transforms `((".*" ,my-auto-save-directory t))) (defvar my-lockfiles-directory "~/.emacs.d/lockfiles/") (unless (file-exists-p my-lockfiles-directory) (make-directory my-lockfiles-directory t)) (setq lock-file-name-transforms `((".*" ,my-lockfiles-directory t))) ;; Editor behavior (setq-default indent-tabs-mode nil tab-width 2 truncate-lines t) (setq vc-follow-symlinks t) (global-set-key (kbd "M-z") 'toggle-truncate-lines) (global-set-key (kbd "C-c z") 'toggle-truncate-lines) (global-set-key (kbd "C-=") 'text-scale-increase) (global-set-key (kbd "C--") 'text-scale-decrease) (add-hook 'after-init-hook (lambda () (setq-default truncate-lines t))) ;; Package management (setq package-enable-at-startup nil) ;; Straight.el bootstrap with timeout (ac-debug-log "Starting straight.el bootstrap") (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" (or (bound-and-true-p straight-base-dir) user-emacs-directory))) (bootstrap-version 7)) (unless (file-exists-p bootstrap-file) (ac-debug-log "Bootstrap file not found, downloading...") (condition-case err (with-timeout (10 (error "Bootstrap download timed out")) ; Reduced timeout (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (error (ac-debug-log (format "Failed to bootstrap straight.el: %s" err)) (message "Failed to bootstrap straight.el: %s" err)))) (when (file-exists-p bootstrap-file) (ac-debug-log "Loading bootstrap file") (load bootstrap-file nil 'nomessage))) (setq straight-use-package-by-default t straight-vc-git-default-clone-depth 1 ; Shallow clones for faster setup straight-check-for-modifications '(check-on-save find-when-checking)) ;; Auto-update packages in background (daemon only, not on client connect) (defun ac-update-packages-async () "Update all packages asynchronously in background subprocess." (ac-debug-log "Checking for package updates in background...") (let ((default-directory user-emacs-directory) (init-file (or user-init-file (expand-file-name "init.el" user-emacs-directory) (expand-file-name ".emacs" "~")))) (if (and init-file (file-exists-p init-file)) ;; Run in a subprocess so it doesn't block anything (start-process "straight-update" "*straight-update*" "emacs" "--batch" "-l" init-file "--eval" "(progn (straight-pull-all) (straight-rebuild-all))") (ac-debug-log "WARNING: Could not find init file for package update")))) ;; Manual update command (defun ac-update-packages () "Manually update all packages now." (interactive) (message "๐Ÿ”„ Updating packages...") (straight-pull-all) (straight-rebuild-all) (message "โœ… All packages updated")) ;; Only check for updates during daemon startup, not on every client connection ;; This prevents UI blocking when connecting emacsclient (when (daemonp) (run-with-timer 60 nil #'ac-update-packages-async)) (ac-debug-log "Straight.el configured, starting package loads") (ac-perf-log "Straight.el configured") ;; Completion (ac-debug-log "Loading vertico") (use-package vertico :init (vertico-mode)) (ac-debug-log "Vertico loaded") (ac-perf-log "Vertico loaded") (use-package savehist :init (savehist-mode)) ;; Org-mode (temporarily disabled due to cloning issues) ;; (condition-case nil ;; (progn ;; (use-package org ;; :defer t) ;; (global-set-key (kbd "C-c l") #'org-store-link) ;; (global-set-key (kbd "C-c a") #'org-agenda) ;; (global-set-key (kbd "C-c c") #'org-capture)) ;; (error (message "Failed to load org-mode, continuing without it..."))) ;; Enhanced completion (use-package emacs :init (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt) enable-recursive-minibuffers t) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (setq read-extended-command-predicate #'command-completion-default-include-p)) (use-package orderless :init (setq completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion))))) (use-package marginalia :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode)) ;;; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ;;; DISABLED/OPTIONAL PACKAGES ;;; โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ;; Auto dark theme (commented out) ;; (when (window-system) ;; (use-package auto-dark) ;; (setq auto-dark-dark-theme 'wombat ;; auto-dark-light-theme 'whiteboard) ;; (auto-dark-mode t)) ;; Frame-specific configuration (commented out) ;; (when (display-graphic-p) ;; (add-hook 'after-make-frame-functions ;; (lambda (frame) ;; Active packages (use-package yascroll :config (global-yascroll-bar-mode 1)) ;; NOTE: origami.el has a defface bug that calls (face-attribute 'highlight :background) ;; at load time, which returns 'unspecified' in daemon mode (no frame). ;; Defer loading until after a frame exists. (use-package origami :defer t :hook (server-after-make-frame . global-origami-mode)) (global-set-key (kbd "C-p") #'project-find-file) (global-set-key (kbd "C-x C-p") #'project-find-file) (global-set-key (kbd "M-p") #'project-find-file) (setq js-indent-level 2) (use-package s) (use-package dockerfile-mode) (use-package fish-mode) (use-package prettier-js :hook (js-mode . prettier-js-mode) :bind ("C-c p" . prettier-js)) (use-package xclip :config (xclip-mode 1)) (defun my/xclip-set-selection (orig-fun type data) (let ((clipboard-command (format "fish -c 'clipboard %s'" (shell-quote-argument data)))) (start-process-shell-command "clipboard" nil clipboard-command)) (funcall orig-fun type data)) (advice-add 'xclip-set-selection :around #'my/xclip-set-selection) ;; Evil mode (use-package evil :config (evil-mode 1) (setq-default evil-shift-width 2) (dolist (state '(normal insert visual motion emacs)) (evil-define-key state 'global (kbd "C-p") 'project-find-file))) (define-key evil-normal-state-map (kbd "C-u") 'evil-scroll-up) (defun my/evil-quit () (interactive) (let ((current-buffer (current-buffer)) (is-last-tab (eq 1 (length (tab-bar-tabs)))) (is-last-window (eq 1 (length (window-list))))) ;; Close window if not the last window in tab (unless is-last-window (delete-window)) ;; Kill buffer if not displayed elsewhere (unless (delq (selected-window) (get-buffer-window-list current-buffer nil t)) (kill-buffer current-buffer)) ;; Optional: Close tab if last window but not last tab ;; (when (and is-last-window (not is-last-tab)) (if is-last-window (if is-last-tab (message "Cannot close last window of last tab") (tab-bar-close-tab)) (delete-window)))) (with-eval-after-load 'evil (evil-ex-define-cmd "q[uit]" 'my/evil-quit)) (unless (display-graphic-p) (use-package evil-terminal-cursor-changer) (require 'evil-terminal-cursor-changer) (evil-terminal-cursor-changer-activate)) ;; Utilities (use-package restart-emacs) (setq restart-emacs-restore-frames t) (global-set-key (kbd "C-c C-r") 'restart-emacs) (global-set-key (kbd "C-c C-o") 'browse-url-at-point) ;; Mail (Gmail via mbsync + mu4e) (defvar ac-maildir "~/.mail" "Local Maildir root used by mu4e.") (defvar ac-mail-sync-command "mbsync ac-mail && mu index --quiet" "Command used to sync and index mail.") (add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e") (use-package mu4e :straight nil :defer t :commands (mu4e mu4e-compose-new) :init (setq mu4e-maildir ac-maildir mu4e-get-mail-command ac-mail-sync-command mu4e-update-interval 300 mu4e-change-filenames-when-moving t mu4e-headers-auto-update t mu4e-view-show-addresses t mu4e-compose-format-flowed t mu4e-sent-folder "/[Gmail]/Sent Mail" mu4e-drafts-folder "/[Gmail]/Drafts" mu4e-trash-folder "/[Gmail]/Trash" mu4e-refile-folder "/[Gmail]/All Mail" mu4e-maildir-shortcuts '(("/INBOX" . ?i) ("/[Gmail]/Sent Mail" . ?s) ("/[Gmail]/Drafts" . ?d) ("/[Gmail]/Trash" . ?t))) :config (setq mail-user-agent 'mu4e-user-agent send-mail-function 'sendmail-send-it message-send-mail-function 'message-send-mail-with-sendmail sendmail-program (or (executable-find "msmtp") "/usr/sbin/msmtp") message-sendmail-f-is-evil t message-sendmail-extra-arguments '("--read-envelope-from" "--read-recipients"))) (defun ac-mail-sync () "Run mail sync + indexing asynchronously." (interactive) (async-shell-command ac-mail-sync-command "mail-sync")) (defun ac-mail-open () "Open mu4e in a dedicated mail tab." (interactive) (if (and (fboundp 'ac--tab-exists-p) (ac--tab-exists-p "mail")) (tab-bar-switch-to-tab "mail") (tab-bar-new-tab) (tab-rename "mail")) (condition-case nil (mu4e) (error (message "mu4e not available โ€” opening mail tab with blast tools only") (ac-mail-blast-preview)))) (global-set-key (kbd "C-c m") 'ac-mail-open) (global-set-key (kbd "C-c M-m") 'ac-mail-sync) ;; Main project directory (must be defined before any expand-file-name usage) (defvar ac--directory-path "~/aesthetic-computer") ;; Email Blast integration (artery/email-blast.mjs) (defvar ac-mail-blast-script (expand-file-name "artery/email-blast.mjs" ac--directory-path) "Path to the email blast script.") (defun ac-mail-blast-preview () "Preview the email blast content in a buffer." (interactive) (let ((default-directory ac--directory-path) (buf (get-buffer-create "*ac-mail-blast*"))) (with-current-buffer buf (read-only-mode -1) (erase-buffer) (insert "โ”€โ”€ Aesthetic.Computer Email Blast โ”€โ”€\n\n") (insert "Commands (run with C-c M-b ):\n") (insert " p Preview email content\n") (insert " t Send test email (me@jas.life)\n") (insert " s Send blast (verified users)\n") (insert " r Resume interrupted blast\n") (insert " l List cached users\n\n") (insert "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n\n")) (switch-to-buffer buf) (shell-command (format "node %s --preview" (shell-quote-argument ac-mail-blast-script)) buf) (with-current-buffer buf (read-only-mode 1)))) (defun ac-mail-blast-test () "Send a test email to me@jas.life." (interactive) (let ((default-directory ac--directory-path)) (async-shell-command (format "node %s --test me@jas.life" (shell-quote-argument ac-mail-blast-script)) "*ac-mail-blast-test*"))) (defun ac-mail-blast-send () "Send email blast to verified users (with confirmation)." (interactive) (when (yes-or-no-p "Send email blast to all verified users? ") (let ((default-directory ac--directory-path)) (ac--ensure-mail-tab) (eat-exec (current-buffer) "blast" "node" nil (list ac-mail-blast-script "--send"))))) (defun ac-mail-blast-resume () "Resume an interrupted email blast." (interactive) (let ((default-directory ac--directory-path)) (ac--ensure-mail-tab) (eat-exec (current-buffer) "blast" "node" nil (list ac-mail-blast-script "--send" "--resume")))) (defun ac-mail-blast-list () "List cached email blast users." (interactive) (let ((default-directory ac--directory-path)) (async-shell-command (format "node %s --list" (shell-quote-argument ac-mail-blast-script)) "*ac-mail-blast-list*"))) (defun ac--ensure-mail-tab () "Switch to or create the mail tab." (if (and (fboundp 'ac--tab-exists-p) (ac--tab-exists-p "mail")) (tab-bar-switch-to-tab "mail") (tab-bar-new-tab) (tab-rename "mail"))) ;; Mail blast keybindings under C-c M-b prefix (define-prefix-command 'ac-mail-blast-map) (global-set-key (kbd "C-c M-b") 'ac-mail-blast-map) (define-key ac-mail-blast-map (kbd "p") 'ac-mail-blast-preview) (define-key ac-mail-blast-map (kbd "t") 'ac-mail-blast-test) (define-key ac-mail-blast-map (kbd "s") 'ac-mail-blast-send) (define-key ac-mail-blast-map (kbd "r") 'ac-mail-blast-resume) (define-key ac-mail-blast-map (kbd "l") 'ac-mail-blast-list) ;; Eat terminal - with performance optimizations for many terminals (use-package eat) (setq-default eat-shell "/usr/bin/fish" eat-term-name "xterm-256color") ;; Filter out unhandled OSC sequences (10/11/12 = fg/bg/cursor color) ;; These are sent by apps like Copilot CLI but eat doesn't handle them (defun ac--strip-osc-color-sequences (string) "Remove OSC 10/11/12 sequences that eat cannot process." ;; Simple pattern: ]10/11/12;... until whitespace (Copilot CLI sends malformed OSC) (replace-regexp-in-string "]1[012];[^[:space:]]+" "" string)) (defun ac--filter-eat-output (orig-fn process string) "Advice to filter unhandled OSC sequences before eat processes them." (funcall orig-fn process (ac--strip-osc-color-sequences string))) (advice-add 'eat--filter :around #'ac--filter-eat-output) ;; Performance: Aggressively limit eat overhead for 16+ terminals (setq eat-enable-directory-tracking nil ; Reduces overhead eat-enable-shell-command-history nil ; We use fish history eat-enable-shell-prompt-annotation nil ; Reduces processing eat-show-title-on-mode-line nil ; We name buffers ourselves eat-term-scrollback-size 8192 ; 8KB - minimal scrollback eat-enable-auto-line-mode nil ; Keep in semi-char mode eat-minimum-latency 0.2 ; Batch output updates (200ms) eat-maximum-latency 0.5 ; Max batch delay (500ms) eat-enable-blinking-text nil ; Disable blinking eat-enable-mouse nil) ; Disable mouse support ;; Disable evil mode in artery buffer ;; REMOVED: buffer-list-update-hook was causing performance issues ;; The hook fired constantly with 16+ terminals causing CPU spike ;; Just use eat-mode-hook with timer instead ;; Use eat-mode-hook with a timer to catch post-rename (add-hook 'eat-mode-hook (lambda () (run-with-timer 0.5 nil (lambda () (when (and (buffer-live-p (current-buffer)) (string-match-p "artery" (buffer-name))) (with-current-buffer (current-buffer) (when (and (boundp 'evil-mode) evil-mode) (evil-emacs-state)))))))) ;; Eat tab integration (temporarily disabled for debugging) ;; (defun eat-tab-change (original-fun &rest args) ;; (interactive) ;; (let ((result (apply original-fun args))) ;; (run-with-idle-timer 0.1 nil ;; (lambda () ;; (when (and (selected-frame) (frame-live-p (selected-frame))) ;; (condition-case err ;; (let ((current-tab-windows (window-list (selected-frame) 'never))) ;; (dolist (window current-tab-windows) ;; (when (and (windowp window) (window-live-p window)) ;; (with-current-buffer (window-buffer window) ;; (when (eq major-mode 'eat-mode) ;; (with-selected-window window ;; (end-of-buffer) ;; (when (bound-and-true-p evil-mode) ;; (evil-insert-state)))))))) ;; (error nil))))) ;; result)) ;; (with-eval-after-load 'eat ;; (advice-add 'tab-bar-select-tab :around #'eat-tab-change)) (defun kill-eat-processes () (interactive) (dolist (buffer (buffer-list)) (with-current-buffer buffer (when (eq major-mode 'eat-mode) (eat-kill-process))))) (add-hook 'kill-emacs-hook #'kill-eat-processes) (defun ac-restart-fishy () "Restart fish process in the fishy buffer if it died." (interactive) (if-let ((buf (get-buffer "๐ŸŸ-fishy"))) (with-current-buffer buf (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (eat-exec buf "fishy" "/usr/bin/fish" nil nil) (message "โœ… Fish restarted in ๐ŸŸ-fishy")) (message "No ๐ŸŸ-fishy buffer found"))) (defun ac-restart-site () "Restart the site dev server (npm run site) in the ๐ŸŒ-site buffer." (interactive) (if-let ((buf (get-buffer "๐ŸŒ-site"))) (with-current-buffer buf (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (let ((default-directory ac--directory-path)) (eat-exec buf "๐ŸŒ-site" "/usr/bin/fish" nil '("-c" "ac-site"))) (message "โœ… Site dev server restarted")) (message "No ๐ŸŒ-site buffer found - try switching to 'web 1/2' tab first"))) (defun ac-restart-session () "Restart the session server in the ๐Ÿ“‹-session buffer." (interactive) (if-let ((buf (get-buffer "๐Ÿ“‹-session"))) (with-current-buffer buf (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (let ((default-directory ac--directory-path)) (eat-exec buf "๐Ÿ“‹-session" "/usr/bin/fish" nil '("-c" "ac-session"))) (message "โœ… Session server restarted")) (message "No ๐Ÿ“‹-session buffer found"))) (defun ac-restart-redis () "Restart redis in the ๐Ÿ”ด-redis buffer." (interactive) (if-let ((buf (get-buffer "๐Ÿ”ด-redis"))) (with-current-buffer buf (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (let ((default-directory ac--directory-path)) (eat-exec buf "๐Ÿ”ด-redis" "/usr/bin/fish" nil '("-c" "ac-redis"))) (message "โœ… Redis restarted")) (message "No ๐Ÿ”ด-redis buffer found"))) (defun ac-restart-kidlisp () "Restart kidlisp test watcher in the ๐Ÿงช-kidlisp buffer." (interactive) (if-let ((buf (get-buffer "๐Ÿงช-kidlisp"))) (with-current-buffer buf (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (let ((default-directory ac--directory-path)) (eat-exec buf "๐Ÿงช-kidlisp" "/usr/bin/fish" nil '("-c" "ac-kidlisp"))) (message "โœ… KidLisp test watcher restarted")) (message "No ๐Ÿงช-kidlisp buffer found"))) (defun ac-restart-buffer (buffer-name command) "Generic restart function for any AC terminal buffer. BUFFER-NAME is the full buffer name (e.g., \"๐ŸŒ-site\"). COMMAND is the ac- fish command to run (e.g., \"site\")." (if-let ((buf (get-buffer buffer-name))) (with-current-buffer buf (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (let ((default-directory ac--directory-path)) (eat-exec buf buffer-name "/usr/bin/fish" nil (list "-c" (format "ac-%s" command)))) (message "โœ… %s restarted" buffer-name)) (message "Buffer %s not found" buffer-name))) (defun ac--fishy-sentinel (process event) "Auto-restart fishy when process exits." (when (and (string-match-p "\\(finished\\|exited\\|killed\\)" event) (buffer-live-p (process-buffer process)) (string= (buffer-name (process-buffer process)) "๐ŸŸ-fishy")) (run-with-timer 0.1 nil #'ac-restart-fishy))) ;; Hook into eat to watch fishy process (add-hook 'eat-mode-hook (lambda () (when (string= (buffer-name) "๐ŸŸ-fishy") (run-with-timer 0.5 nil (lambda () (when-let ((proc (get-buffer-process "๐ŸŸ-fishy"))) (set-process-sentinel proc #'ac--fishy-sentinel))))))) ;;; --- Aesthetic Computer Restart Functions --- (defvar ac--tab-names '("artery" "status" "stripe" "chat" "web 1/2" "web 2/2" "tests" "llm" "mail" "signal" "top" "fishy") "List of all AC tab names.") (defvar ac--buffer-prefixes '("๐Ÿฉธ" "๐Ÿ“ก" "โšก" "๐Ÿš‡" "๐Ÿ’ณ" "๐ŸŽซ" "๐Ÿค–" "๐Ÿง " "โฐ" "๐ŸŒ" "๐Ÿ“‹" "๐Ÿ”ด" "๐Ÿ”–" "๐Ÿงช" "๐Ÿ”ฅ" "๐Ÿ“ฆ") "Emoji prefixes used for AC buffers.") (defun ac-kill-all () "Kill all AC processes, buffers, and tabs. Use before ac-restart." (interactive) (message "๐Ÿ›‘ Killing all AC processes and buffers...") ;; 1. Stop artery bridge if running (when (and (boundp 'artery--process) artery--process (process-live-p artery--process)) (condition-case nil (artery-stop) (error nil))) ;; 2. Kill all eat processes first (sends SIGTERM) (kill-eat-processes) ;; 3. Kill all AC emoji-prefixed buffers (dolist (buffer (buffer-list)) (let ((name (buffer-name buffer))) (when (cl-some (lambda (prefix) (string-prefix-p prefix name)) ac--buffer-prefixes) (condition-case nil (progn ;; Kill any process attached to buffer (when (get-buffer-process buffer) (delete-process (get-buffer-process buffer))) (kill-buffer buffer)) (error nil))))) ;; 4. Kill artery-specific buffers (dolist (bufname '("๐Ÿฉธ-artery" "๐Ÿฉธ-daw-log" "๐Ÿฉธ-bridge" "๐Ÿฉธ-server" "*artery*" "*artery-daw*" "*artery-bridge*" "*artery-server*")) (when-let ((buf (get-buffer bufname))) (condition-case nil (progn (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (kill-buffer buf)) (error nil)))) ;; 5. Close all AC tabs except current, then close that (let ((current-tab (alist-get 'name (tab-bar--current-tab)))) ;; Close all other tabs first (dolist (tab-name ac--tab-names) (unless (string= tab-name current-tab) (condition-case nil (tab-bar-close-tab-by-name tab-name) (error nil)))) ;; Try to close current tab (might fail if it's the last one) (when (member current-tab ac--tab-names) (condition-case nil (tab-bar-close-tab) (error nil)))) ;; 6. Ensure we have at least scratch buffer (unless (get-buffer "*scratch*") (get-buffer-create "*scratch*")) (switch-to-buffer "*scratch*") (message "โœ… All AC processes and buffers killed. Run M-x ac-restart to start fresh.")) (defun ac-restart (&optional target-tab) "Restart all AC tabs and processes from scratch. Optional TARGET-TAB specifies which tab to land on (default: artery)." (interactive) (let ((target (or target-tab "artery"))) (message "๐Ÿ”„ Restarting Aesthetic Computer...") ;; Kill everything first (ac-kill-all) ;; Wait a moment for processes to fully terminate (run-with-timer 1 nil (lambda (tgt) (message "๐Ÿš€ Starting AC backend...") (aesthetic-backend tgt)) target))) (defun ac-restart-artery () "Restart just the artery tab/process." (interactive) (message "๐Ÿ”„ Restarting artery...") ;; Stop artery bridge (when (and (boundp 'artery--process) artery--process) (condition-case nil (artery-stop) (error nil))) ;; Kill artery buffers (dolist (bufname '("๐Ÿฉธ-artery" "๐Ÿฉธ-daw-log" "๐Ÿฉธ-bridge")) (when-let ((buf (get-buffer bufname))) (when (get-buffer-process buf) (delete-process (get-buffer-process buf))) (kill-buffer buf))) ;; Recreate artery tab (condition-case nil (tab-bar-close-tab-by-name "artery") (error nil)) (run-with-timer 0.5 nil (lambda () (tab-new) (tab-rename "artery") (let ((default-directory ac--directory-path)) (eat "fish -c 'ac-artery-dev'") (when (get-buffer "*eat*") (with-current-buffer "*eat*" (rename-buffer "๐Ÿฉธ-artery" t) (eat-semi-char-mode) (when (and (boundp 'evil-mode) evil-mode) (evil-emacs-state))))) (message "โœ… Artery restarted")))) ;; Keybindings for restart functions (global-set-key (kbd "C-c C-k") 'ac-kill-all) (global-set-key (kbd "C-c C-S-r") 'ac-restart) ;; Aesthetic functions (defun aesthetic () (interactive) (eshell) (insert "python3 -m http.server 8888") (eshell-send-input)) (setq confirm-kill-processes nil) ;; DISABLED: These hooks were causing CPU spikes with 16+ terminals ;; Each terminal output triggers filter functions, overwhelming emacs ;; Enable aggressive auto-scrolling for all shell/comint buffers (setq comint-scroll-to-bottom-on-input t comint-scroll-to-bottom-on-output t comint-move-point-for-output t comint-scroll-show-maximum-output t) (add-hook 'comint-mode-hook (lambda () (setq-local scroll-conservatively 101))) (add-hook 'shell-mode-hook (lambda () (setq-local scroll-conservatively 101))) ;; Main backend function (defvar ac--emoji-for-command '(("artery" . "๐Ÿฉธ") ("status" . "๐Ÿ“ก") ("url" . "โšก") ("tunnel" . "๐Ÿš‡") ("agent" . "โšก") ("stripe-print" . "๐Ÿ’ณ") ("stripe-ticket" . "๐ŸŽซ") ("chat-system" . "๐Ÿค–") ("chat-sotce" . "๐Ÿง ") ("chat-clock" . "โฐ") ("site" . "๐ŸŒ") ("session" . "๐Ÿ“‹") ("redis" . "๐Ÿ”ด") ("bookmarks" . "๐Ÿ”–") ("kidlisp" . "๐Ÿงช") ("oven" . "๐Ÿ”ฅ") ("silo" . "๐Ÿ—๏ธ") ("media" . "๐Ÿ“ฆ") ("llm" . "๐Ÿค–") ("top" . "๐Ÿ“Š") ("mail" . "๐Ÿ“ฌ") ("signal" . "๐Ÿ“ถ") ("crash-diary" . "๐Ÿ’ฅ") ("views" . "๐Ÿ–ผ๏ธ") ("boot" . "๐Ÿš€"))) (defun ac--tab-exists-p (tab-name) "Check if a tab with TAB-NAME already exists." (cl-find tab-name (tab-bar-tabs) :key (lambda (tab) (alist-get 'name tab)) :test #'string=)) (defun ac--buffer-exists-p (buffer-name) "Check if a buffer with BUFFER-NAME already exists and has a live process." (when-let ((buf (get-buffer buffer-name))) (and (buffer-live-p buf) (or (get-buffer-process buf) (> (buffer-size buf) 0))))) (cl-defun ac--create-split-tab (tab-name commands) "Create a new tab with split windows running the specified commands. Skips creation if tab already exists." ;; Guard: Skip if tab already exists (when (ac--tab-exists-p tab-name) (ac-debug-log (format "Tab '%s' already exists, skipping creation" tab-name)) (cl-return-from ac--create-split-tab nil)) (ac-debug-log (format "Creating tab: %s with commands: %s" tab-name commands)) (ac-perf-log (format "Creating tab: %s" tab-name)) (condition-case err (progn (tab-new) (tab-rename tab-name) (let ((default-directory ac--directory-path)) (delete-other-windows) ;; Calculate layout (let* ((window-height (window-height)) (min-height-per-window 8) (max-vertical-splits (/ window-height min-height-per-window)) (use-horizontal (< max-vertical-splits (length commands))) (cmd-index 0)) ;; Create windows and terminals - use eat-exec to avoid blocking (dolist (cmd commands) (unless (= cmd-index 0) (condition-case nil (if use-horizontal (progn (split-window-right) (other-window 1)) (progn (split-window-below) (other-window 1))) (error nil))) (let ((actual-cmd (cond ((string= cmd "bookmarks") "servers") ((string= cmd "llm") "llm-continue") ; Auto-resume last session (t cmd))) (buf-name (format "%s-%s" (or (cdr (assoc cmd ac--emoji-for-command)) "๐Ÿ”ง") cmd))) (ac-debug-log (format "Running command: ac-%s" actual-cmd)) ;; Create eat buffer directly without blocking (condition-case err (let ((buf (generate-new-buffer buf-name))) (with-current-buffer buf (eat-mode) (eat-exec buf buf-name "/usr/bin/fish" nil (list "-c" (format "ac-%s" actual-cmd)))) (switch-to-buffer buf) ;; Yield to let Emacs process events (prevents freeze) (redisplay t)) (error (ac-debug-log (format "Error starting ac-%s in tab %s: %s" actual-cmd tab-name err))))) (setq cmd-index (1+ cmd-index))) (balance-windows) (other-window 1)))) (error (message "Error creating tab %s: %s" tab-name err)))) (defvar ac--startup-lock-file "/tmp/emacs-backend-startup.lock" "Lock file indicating aesthetic-backend is starting (crash monitor will be gentle).") (defvar ac--backend-initializing nil "Whether aesthetic-backend is currently initializing.") (defvar ac--backend-complete nil "Whether aesthetic-backend completed initialization.") (defvar ac--backend-retry-count 0 "Number of aesthetic-backend retries attempted this session.") (defvar ac--backend-max-retries 1 "Maximum number of aesthetic-backend retries.") (cl-defun aesthetic-backend (target-tab) (interactive) (setq target-tab (or target-tab "artery")) ;; Prevent double-invocation race condition (when (and ac--backend-started ac--backend-complete) (ac-debug-log (format "aesthetic-backend already complete, switching to tab: %s" target-tab)) ;; Just switch to the requested tab if backend already running (run-with-timer 0.5 nil (lambda (target) (condition-case nil (when (member target '("artery" "fishy" "boot" "status" "stripe" "chat" "web 1/2" "web 2/2" "tests" "llm" "mail" "signal" "top")) (tab-bar-switch-to-tab target)) (error nil))) target-tab) (cl-return-from aesthetic-backend nil)) (when ac--backend-initializing (ac-debug-log "aesthetic-backend already initializing; skipping duplicate start") (cl-return-from aesthetic-backend nil)) (setq ac--backend-started t ac--backend-initializing t ac--backend-complete nil) ;; Initialize boot progress buffer (ac--boot-init) ;; Create startup lock file to tell crash monitor we're starting up ;; This prevents false "unresponsive" detection during heavy init (with-temp-file ac--startup-lock-file (insert (format "%s" (float-time)))) (ac-debug-log "Created startup lock file - crash monitor will wait") (ac-debug-log (format "Starting aesthetic-backend with target-tab: %s" target-tab)) (ac-perf-log "aesthetic-backend started") ;; Set environment variable to tell fish it's running inside Emacs (setenv "AC_EMACS_MODE" "t") ;; Prevent OSC color query responses from leaking (shows as "11;rgb:..." text) (setenv "COLORFGBG" "15;0") ;; Set up a watchdog timer to detect hangs (30 seconds) (run-with-timer 30 nil (lambda () (ac-debug-log "WARNING: aesthetic-backend has been running for 30+ seconds") (message "โš ๏ธ Backend initialization taking longer than expected. Check .emacs-logs/"))) ;; Clean up unwanted buffers before starting (with error handling) (condition-case nil (dolist (bufname '("*scratch*" "*Messages*" "*straight-process*" "*async-native-comp*")) (when-let ((buf (get-buffer bufname))) (dolist (win (get-buffer-window-list buf nil t)) (with-selected-window win (switch-to-buffer (other-buffer buf t)))) (kill-buffer buf))) (error nil)) ;; Create artery tab FIRST - this is the primary tab (leftmost) (tab-rename "artery") (ac--boot-start-step "artery") (let ((default-directory ac--directory-path) (buf (or (get-buffer "๐Ÿฉธ-artery") (generate-new-buffer "๐Ÿฉธ-artery")))) (with-current-buffer buf (condition-case err (progn (unless (eq major-mode 'eat-mode) (eat-mode) (eat-exec buf "๐Ÿฉธ-artery" "/usr/bin/fish" nil '("-c" "ac-artery-dev"))) (eat-semi-char-mode) (when (and (boundp 'evil-mode) evil-mode) (evil-emacs-state))) (error (ac-debug-log (format "Error starting artery eat buffer: %s" err)) (ac--boot-error-step "artery")))) (switch-to-buffer buf) (redisplay t)) (ac--boot-complete-step "artery") ;; Create boot tab SECOND - shows progress + crash diary after boot (tab-new) (tab-rename "boot") (let ((boot-buf (get-buffer ac--boot-buffer-name))) (when boot-buf (switch-to-buffer boot-buf) (ac-boot-mode) (redisplay t))) ;; Create all the split tabs with optimized delays (reduced from 500ms to 350ms) (let ((delay 0.5) (step-delay 0.35) ; Optimized: was 0.5 (tab-specs '(("status" ("url" "tunnel")) ("stripe" ("stripe-print" "stripe-ticket")) ("chat" ("chat-system" "chat-sotce" "chat-clock")) ("web 1/2" ("site" "session")) ("web 2/2" ("redis" "bookmarks" "oven" "silo" "media" "views")) ("tests" ("kidlisp")) ("llm" ("llm")) ("mail" ("mail")) ("signal" ("signal")) ("top" ("top"))))) (dolist (tab-spec tab-specs) (let ((tab-name (car tab-spec)) (commands (cadr tab-spec)) (current-delay delay)) (run-with-timer current-delay nil (lambda (name cmds) (ac--boot-start-step name) (condition-case err (progn (ac--create-split-tab name cmds) (ac--boot-complete-step name)) (error (ac-debug-log (format "Error creating tab %s: %s" name err)) (ac--boot-error-step name))) ;; Switch back to boot tab to show progress (when (ac--tab-exists-p "boot") (tab-bar-switch-to-tab "boot")) (redisplay t)) tab-name commands) (setq delay (+ delay step-delay))))) ;; Create fishy tab (tab bar position after other tabs) (run-with-timer 3.5 nil (lambda () (ac--boot-start-step "fishy") (unless (ac--tab-exists-p "fishy") (tab-new) (tab-rename "fishy") (let ((default-directory ac--directory-path) (buf (or (get-buffer "๐ŸŸ-fishy") (generate-new-buffer "๐ŸŸ-fishy")))) (with-current-buffer buf (condition-case err (progn (unless (eq major-mode 'eat-mode) (eat-mode) (eat-exec buf "๐ŸŸ-fishy" "/usr/bin/fish" nil nil)) (eat-semi-char-mode) (when (and (boundp 'evil-mode) evil-mode) (evil-emacs-state))) (error (ac-debug-log (format "Error starting fishy eat buffer: %s" err)) (ac--boot-error-step "fishy")))) (switch-to-buffer buf) (redisplay t))) (ac--boot-complete-step "fishy") (tab-bar-switch-to-tab "boot"))) ;; Calculate when tabs should be done (8 batch tabs * 0.35s = 2.8s + 0.5s initial = 3.3s, fishy at 3.5s) (let ((tabs-done-time 4.2)) ;; Start CDP tunnel after tabs (run-with-timer tabs-done-time nil (lambda () (ac--boot-start-step "cdp") (ac-start-cdp-tunnel-async) ;; CDP is async, mark done after a short delay (run-with-timer 1.0 nil (lambda () (ac--boot-complete-step "cdp"))))) ;; Finalize and switch to target tab (run-with-timer (+ tabs-done-time 1.5) nil (lambda (target) (ac--boot-start-step "finalize") ;; Remove startup lock (when (file-exists-p ac--startup-lock-file) (delete-file ac--startup-lock-file) (ac-debug-log "Removed startup lock - crash monitor now active")) (setq ac--backend-complete t ac--backend-initializing nil) (ac--boot-complete-step "finalize") (ac-perf-log "aesthetic-backend initialization complete") ;; Convert boot tab into crash diary (combined boot+crash) (run-with-timer 1.0 nil (lambda () (when (ac--tab-exists-p "boot") (tab-bar-switch-to-tab "boot") (let ((default-directory ac--directory-path) (buf (generate-new-buffer "๐Ÿ’ฅ-crash-diary"))) (with-current-buffer buf (condition-case err (progn (eat-mode) (eat-exec buf "๐Ÿ’ฅ-crash-diary" "/usr/bin/fish" nil '("-c" "ac-crash-diary"))) (error (ac-debug-log (format "Error starting crash diary: %s" err))))) (switch-to-buffer buf) (redisplay t))))) ;; Switch to target tab (run-with-timer 1.5 nil (lambda (tgt) (condition-case nil (when (member tgt '("artery" "fishy" "boot" "status" "stripe" "chat" "web 1/2" "web 2/2" "tests" "llm" "mail" "signal" "top")) (tab-bar-switch-to-tab tgt) (when (eq major-mode 'eat-mode) (goto-char (point-max))) (redisplay t)) (error nil))) target)) target-tab) ;; Retry logic (run-with-timer (+ tabs-done-time 3.0) nil (lambda (target) (when (and (not ac--backend-complete) (< ac--backend-retry-count ac--backend-max-retries)) (setq ac--backend-retry-count (1+ ac--backend-retry-count)) (ac-debug-log (format "Retrying aesthetic-backend (attempt %d)" ac--backend-retry-count)) (aesthetic-backend target))) target-tab))) (defun ac-start-cdp-tunnel-async () "Start CDP tunnel to VS Code host in background (non-blocking)." (ac-debug-log "Starting CDP tunnel to host...") (let ((proc (start-process-shell-command "cdp-tunnel" nil "fish -c 'ac-cdp-tunnel' >/dev/null 2>&1"))) (when proc (set-process-sentinel proc (lambda (p e) (if (= 0 (process-exit-status p)) (ac-debug-log "CDP tunnel established") (ac-debug-log (format "CDP tunnel failed: %s" e)))))))) ;;; =================================================================== ;;; AUTO-START AESTHETIC-BACKEND ON FIRST TERMINAL FRAME ;;; =================================================================== (defvar ac--backend-started nil "Flag to ensure aesthetic-backend only runs once.") (defun ac--backend-tabs-minimal-p () "Return t if minimal tabs are present (artery + fishy)." (and (ac--tab-exists-p "artery") (ac--tab-exists-p "fishy"))) (defun ac--ensure-backend (&optional reason) "Ensure aesthetic-backend is running; retries if needed. REASON is logged for debugging." (when (and (not ac--backend-initializing) (not (ac--backend-tabs-minimal-p)) (< ac--backend-retry-count ac--backend-max-retries)) (setq ac--backend-retry-count (1+ ac--backend-retry-count)) (ac-debug-log (format "Ensuring aesthetic-backend (attempt %d) reason=%s" ac--backend-retry-count (or reason "unknown"))) (aesthetic-backend "artery"))) (defun ac--maybe-start-backend (frame) "Start aesthetic-backend when first terminal frame connects." (when (and (not ac--backend-started) (not (display-graphic-p frame)) ; Terminal frame (frame-live-p frame)) ;; DON'T set flag here - let aesthetic-backend set it when it actually runs (ac-debug-log "First terminal frame detected, scheduling aesthetic-backend") ;; Delay to ensure frame is fully ready (run-with-timer 1 nil (lambda () (condition-case err (aesthetic-backend "artery") (error (message "Error starting aesthetic-backend: %s" err))))))) ;; Self-heal: if backend didn't start after daemon restart, try again once. (add-hook 'emacs-startup-hook (lambda () (run-with-timer 6 nil (lambda () (when (and (not ac--backend-started) (frame-live-p (selected-frame)) (<= (length (tab-bar-tabs)) 1)) (ac--ensure-backend "startup self-heal")))))) ;; Auto-start full backend on first terminal frame (add-hook 'after-make-frame-functions #'ac--maybe-start-backend) (cl-defun aesthetic-backend-minimal () "Minimal version - start artery and fishy only. Use M-x aesthetic-backend for all tabs." (interactive) (ac-debug-log "Starting aesthetic-backend-minimal") (setenv "AC_EMACS_MODE" "t") ;; Start with artery in the first tab (tab-rename "artery") (let ((default-directory ac--directory-path)) (eat "fish -c 'ac-artery-dev'") (when (get-buffer "*eat*") (with-current-buffer "*eat*" (rename-buffer "๐Ÿฉธ-artery" t) (eat-semi-char-mode)))) ;; Add fishy tab after 2 seconds (only if not already created) (run-with-timer 2 nil (lambda () (condition-case nil (unless (get-buffer "๐ŸŸ-fishy") (tab-new) (tab-rename "fishy") (let ((default-directory ac--directory-path)) (eat "fish") (when (get-buffer "*eat*") (with-current-buffer "*eat*" (rename-buffer "๐ŸŸ-fishy" t) (eat-semi-char-mode)))) ;; Switch back to artery (run-with-timer 1 nil (lambda () (tab-bar-switch-to-tab "artery")))) (error nil)))) (message "๐Ÿš€ Artery started! Use M-x aesthetic-backend-full to load all tabs.") (ac-debug-log "aesthetic-backend-minimal complete")) (cl-defun aesthetic-backend-full () "Load all remaining tabs (status, stripe, chat, web, tests, llm, top)." (interactive) (message "Loading additional tabs...") (dolist (tab-spec '(("status" ("url" "tunnel")) ("stripe" ("stripe-print" "stripe-ticket")) ("chat" ("chat-system" "chat-sotce" "chat-clock")) ("web 1/2" ("site" "session")) ("web 2/2" ("redis" "bookmarks" "oven" "silo" "media" "views")) ("tests" ("kidlisp")) ("llm" ("llm")) ("top" ("top")))) (let ((tab-name (car tab-spec)) (commands (cadr tab-spec))) (condition-case err (progn (ac--create-split-tab tab-name commands) (message "Created tab: %s" tab-name)) (error (message "Error creating tab %s: %s" tab-name err))))) (message "All tabs loaded!")) ;;; =================================================================== ;;; END OF AESTHETIC COMPUTER EMACS CONFIGURATION ;;; =================================================================== (ac-debug-log "=== Emacs Configuration Load Complete ===") (ac-perf-log "Config load complete")