;; -*- lexical-binding: t -*- ;; Set background immediately to prevent white flash on startup (unless (daemonp) (set-face-attribute 'default nil :background "#1f1f28" :foreground "#ffd700")) (require 'cl-lib) ;; Skip package installation and suppress load errors during batch compilation (defvar use-package-ensure-function) (eval-when-compile (when noninteractive (require 'use-package-ensure nil t) (setq use-package-ensure-function #'ignore) (dolist (pkg '(vertico marginalia orderless consult corfu corfu-terminal cape gptel eat magit diff-hl yaml-mode systemd dockerfile-mode multiple-cursors markdown-mode)) (provide pkg)))) (defconst +zsh-path+ (if (file-exists-p "/bin/zsh") "/bin/zsh") "Path to zsh, or nil if not found.") (defconst +bash-path+ "/bin/bash" "Path to bash shell.") (defconst +if-cn+ (= 8 (/ (car (current-time-zone)) 3600)) "Non-nil if running in China timezone (UTC+8).") ;;; package.el --- Package manager configuration -*- lexical-binding: t -*- ;;; Commentary: ;; Configure package archives based on location ;; Requires: +if-cn+ from env.el ;;; Code: (defvar package-archives) (defvar package-check-signature) (require 'package) (when +if-cn+ (setq package-archives '(("melpa" . "https://mirrors.ustc.edu.cn/elpa/melpa/") ("gnu" . "https://mirrors.ustc.edu.cn/elpa/gnu/") ("nongnu" . "https://mirrors.ustc.edu.cn/elpa/nongnu/")))) ;; Initialize immediately - needed for use-package :ensure (package-initialize) (when (version< emacs-version "30") (setq package-check-signature nil));;; default.el --- Core Emacs settings -*- lexical-binding: t -*- ;;; Commentary: ;; Basic Emacs configuration: UI, performance, and behavior settings ;;; Code: ;; Declared for byte-compiler clarity when loaded before libraries. (defvar recentf-max-saved-items) (defvar recentf-max-menu-items) (defvar savehist-additional-variables) ;; disable writing custom configs to .emacs (setq custom-file null-device) ;; UI chrome disabled in early-init.el for faster startup; ;; these are kept as fallback for non-early-init environments. (tool-bar-mode -1) (menu-bar-mode -1) (scroll-bar-mode -1) (global-display-line-numbers-mode 1) (global-auto-revert-mode 1) ;; Built-in convenience modes (recentf-mode 1) (setq recentf-max-saved-items 50) ; Reduced from 100 for faster startup (setq recentf-max-menu-items 15) (setq recentf-auto-cleanup 'never) ; Disable auto-cleanup on load (run-at-time nil (* 5 60) 'recentf-save-list) ; Save every 5 minutes (savehist-mode 1) (setq history-length 25) (setq savehist-additional-variables '(kill-ring search-ring regexp-search-ring)) (save-place-mode 1) ;; Handle very long lines automatically (Emacs 27+) (when (fboundp 'global-so-long-mode) (global-so-long-mode 1)) (setq-default bookmark-save-flag 1 inhibit-startup-message t semantic-mode 1 tramp-default-method "ssh" ;; avoid y/n when ssh echo-keystrokes 0.8 history-length 1000 history-delete-duplicates t kill-ring-max 1000 save-interprogram-paste-before-kill t duplicate-line-final-position 1 auto-revert-verbose nil global-auto-revert-non-file-buffers t ;; Better scrolling scroll-conservatively 101 scroll-margin 0 scroll-preserve-screen-position t ) (setq delete-by-moving-to-trash t) (set-language-environment "UTF-8") ;; Enhanced show-paren-mode (show-paren-mode t) (setq show-paren-delay 0) ; Show matching parens instantly (setq show-paren-style 'mixed) (setq show-paren-when-point-inside-paren t) (setq show-paren-when-point-in-periphery t) ;; (global-hl-line-mode t) (display-time-mode t) (electric-pair-mode t) ;; Configure electric-pair settings (defvar electric-pair-preserve-balance) (defvar electric-pair-delete-adjacent-pairs) (defvar electric-pair-skip-self) (defvar electric-pair-skip-whitespace) (setq electric-pair-preserve-balance t electric-pair-delete-adjacent-pairs t electric-pair-skip-self 'unless-escape electric-pair-skip-whitespace t) (let ((mode-line (default-value 'mode-line-format)) (res)) (push '(:eval (format "[%d] " (point))) res) (while mode-line (push (car mode-line) res) (pop mode-line)) (setq-default mode-line-format (nreverse res)) ) (force-mode-line-update t) (defalias 'yes-or-no-p 'y-or-n-p) ;; Modern Emacs 29+ uses use-short-answers (when (boundp 'use-short-answers) (setq use-short-answers t)) ;; Better frame title (setq frame-title-format '("%b - Emacs") initial-scratch-message "") (setq-default whitespace-line-column 80 whitespace-style '(face lines-tail) ) (add-hook 'prog-mode-hook #'whitespace-mode) ;; Tree-sitter support (Emacs 29+) ;; Automatically use tree-sitter modes when available (when (and (fboundp 'treesit-available-p) (treesit-available-p)) (setq major-mode-remap-alist '((python-mode . python-ts-mode) (c-mode . c-ts-mode) (c++-mode . c++-ts-mode) (c-or-c++-mode . c-or-c++-ts-mode) (bash-mode . bash-ts-mode) (sh-mode . bash-ts-mode) (css-mode . css-ts-mode) (js-mode . js-ts-mode) (js2-mode . js-ts-mode) (json-mode . json-ts-mode) (typescript-mode . typescript-ts-mode) (rust-mode . rust-ts-mode) (go-mode . go-ts-mode) (yaml-mode . yaml-ts-mode) (dockerfile-mode . dockerfile-ts-mode)))) ;; Associate .al files with Common Lisp mode (add-to-list 'auto-mode-alist '("\\.al\\'" . lisp-mode)) (require 'epa-file) (epa-file-enable) ;; -*- lexical-binding: t -*- (setq-default dired-kill-when-opening-new-dired-buffer t) ;; 1. Interactive function to search .el/.lisp files from current dired directory (defun my/find-dired (regexp) "Run `find-lisp-find-dired' from the current dired directory with given REGEXP. Interactively prompts for the regexp." (interactive (list (read-regexp "Find files matching (regexp): "))) (let ((dir (if (eq major-mode 'dired-mode) (dired-current-directory) default-directory))) (find-lisp-find-dired dir regexp))) (defun dired-copy-file-content-to-clipboard () "Copy content of file at point in dired to clipboard." (interactive) (let ((file (dired-get-file-for-visit))) (when (and file (file-regular-p file)) (with-temp-buffer (insert-file-contents file) (clipboard-kill-region (point-min) (point-max))) (message "Copied content of %s to clipboard" (file-name-nondirectory file))))) (with-eval-after-load 'dired (define-key dired-mode-map (kbd "b") #'dired-copy-file-content-to-clipboard) (define-key dired-mode-map (kbd "F") 'my/find-dired)) ;; use my/test-replace-with-clipboard after copy the test buffer to an old buffer ;;; func.el --- Utility functions -*- lexical-binding: t -*- ;;; Commentary: ;; Helper functions used across the configuration ;;; Code: (require 'cl-lib) (require 'seq) ; Built-in since Emacs 25 (require 'map) ; Built-in since Emacs 25 (declare-function global-text-scale-adjust "face-remap" (increment)) (defun display-startup-echo-area-message () (message (concat "Discourtesy is unspeakably ugly to me. <" (emacs-init-time) ">"))) (defun sudo-edit-current-buffer () "Edit the current buffer with sudo privileges." (interactive) (let ((file-name (buffer-file-name))) (if file-name (progn (kill-buffer (current-buffer)) (find-file (concat "/sudo::" file-name))) (message "Buffer is not associated with a file.")))) (defun smart-load (dir file) "Dynamically load an Emacs Lisp library from a specified directory. DIR is the base directory, FILE is the library name (without .el extension)." (let* ((full-dir (file-name-as-directory (concat dir file "-"))) (el-file (concat full-dir file ".el"))) (when (file-exists-p el-file) (load el-file)))) (defun my-pick (candidates reader) "Prompt with READER and return index of selected item in CANDIDATES." (let ((completion (mapcar #'list candidates))) (cl-position (completing-read reader completion) candidates :test 'string=))) (cl-defmacro %load-file ((dir file) &body body) `(let ((ff (concat ,dir ,file))) (when (file-exists-p ff) (load ff) ,@body ))) (defun is-number-string-p (str) "Check if STR is purely numeric, including negative numbers." (string-match-p "^[-+]?[0-9]+\\.?[0-9]*$" str)) (defun indent-buffer () "Indent the entire buffer." (interactive) (indent-region (point-min) (point-max))) (defvar my/global-font-base-height nil "Baseline default face height captured before global zoom changes.") (defvar my/global-line-spacing-base nil "Baseline `line-spacing' captured before global zoom changes.") (defun my/capture-global-typography-baseline () "Capture font and line-spacing baselines once." (unless (numberp my/global-font-base-height) (let ((height (face-attribute 'default :height nil 'default))) (setq my/global-font-base-height (if (numberp height) height 100)))) (when (null my/global-line-spacing-base) (setq my/global-line-spacing-base (default-value 'line-spacing)))) (defun my/recompute-line-spacing-from-font-height () "Keep numeric `line-spacing' proportional to the current font height." (my/capture-global-typography-baseline) (when (numberp my/global-line-spacing-base) (let* ((current-height (face-attribute 'default :height nil 'default)) (safe-height (if (numberp current-height) current-height my/global-font-base-height)) (ratio (/ (float safe-height) my/global-font-base-height))) (setq-default line-spacing (* my/global-line-spacing-base ratio))))) (defun my/global-text-scale-adjust (increment &optional key) "Adjust text scale globally by INCREMENT and sync line spacing. When KEY is non-nil, use it as the adjustment selector for `global-text-scale-adjust' (e.g. ?=, ?-, ?0)." (interactive "p") (my/capture-global-typography-baseline) ;; `global-text-scale-adjust' determines direction from ;; `last-command-event'. Force KEY when provided so behavior is ;; deterministic across keybindings and `M-x' invocations. (let ((last-command-event (or key last-command-event))) (global-text-scale-adjust (max 1 (abs increment)))) (my/recompute-line-spacing-from-font-height)) (defun my/global-text-scale-increase () "Increase text scale globally." (interactive) (my/global-text-scale-adjust 1 ?=)) (defun my/global-text-scale-decrease () "Decrease text scale globally." (interactive) (my/global-text-scale-adjust 1 ?-)) (defun my/global-text-scale-reset () "Reset global text scale and restore baseline line spacing." (interactive) (my/capture-global-typography-baseline) (my/global-text-scale-adjust 1 ?0) (setq-default line-spacing my/global-line-spacing-base)) (defun my/test-replace-with-clipboard () "Comment buffer and insert clipboard content as new test code." (interactive) (let* ((clipboard (current-kill 0 t)) ; get last killed text (separator "---------OLD VERSION--------\n\n")) (when (string-empty-p clipboard) (user-error "Clipboard/kill-ring is empty!")) (goto-char (point-min)) (insert "\n\n" separator "\n\n") (comment-region (point-min) (point-max) nil) (goto-char (point-min)) (insert clipboard) (unless (string-suffix-p "\n" clipboard) (insert "\n")) (message "Buffer commented + new test code inserted from clipboard"))) ;; -*- lexical-binding: t -*- (defconst +term-buffer-name+ "*ansi-term*" "Name of the ansi-term buffer.") (defconst +eat-buffer-name+ "*eat*" "Name of the eat buffer.") (declare-function eat-term-send-string-as-yank "eat") (declare-function eat-term-parameter "eat") (defvar eat-terminal) (defun h3--find-eat-buffer () "Return an active eat buffer, preferring `+eat-buffer-name+'." (or (get-buffer +eat-buffer-name+) (seq-find (lambda (buf) (with-current-buffer buf (eq major-mode 'eat-mode))) (buffer-list)))) (defun h3--find-ansi-term-buffer () "Return an active ansi-term buffer, preferring `+term-buffer-name+'." (or (get-buffer +term-buffer-name+) (seq-find (lambda (buf) (with-current-buffer buf (eq major-mode 'term-mode))) (buffer-list)))) (defun %remove-leading-hashes (text) "Remove any number of leading '#' characters from each line in TEXT. Preserves leading whitespace if present before the hashes." (with-temp-buffer (insert text) (goto-char (point-min)) (while (re-search-forward "^[#;]+" nil t) (replace-match "" nil t)) (buffer-string))) (defun my-send-region-to-term () "Send region or current line to terminal (eat or ansi-term)." (interactive) (let* ((text0 (if (use-region-p) (buffer-substring-no-properties (region-beginning) (region-end)) (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (text (%remove-leading-hashes text0)) (term-buf (if (display-graphic-p) (h3--find-eat-buffer) (h3--find-ansi-term-buffer)))) (unless term-buf (error "No terminal buffer found")) (with-current-buffer term-buf (if (eq major-mode 'eat-mode) (progn (eat-term-send-string-as-yank eat-terminal text) (process-send-string (eat-term-parameter eat-terminal 'eat--process) "\n")) (let ((proc (get-buffer-process term-buf))) (unless proc (error "No active process in terminal buffer")) (process-send-string proc (concat text "\n"))))))) (defun my-toggle-terminal () "Toggle terminal: eat in GUI, ansi-term in console/nw." (interactive) (if (display-graphic-p) ;; GUI mode: use eat (let ((eat-buffer (get-buffer +eat-buffer-name+))) (if eat-buffer (let ((win (get-buffer-window +eat-buffer-name+))) (if win (select-window win) (switch-to-buffer eat-buffer))) (progn (split-window-vertically) (require 'eat) (eat)))) ;; Console/nw mode: use ansi-term (let ((term-buffer (seq-find (lambda (buf) (with-current-buffer buf (eq major-mode 'term-mode))) (buffer-list))) (shell (or +zsh-path+ +bash-path+))) (if term-buffer (let ((win (get-buffer-window +term-buffer-name+))) (if win (select-window win) (switch-to-buffer term-buffer))) (progn (split-window-vertically) (ansi-term shell)))))) (global-set-key (kbd "C-c t") #'my-toggle-terminal) (global-set-key (kbd "C-c T") #'my-send-region-to-term) (global-set-key (kbd "C-c ") #'my-send-region-to-term) ;; to supress warning (defvar term-raw-map) (defvar term-mode-map) (declare-function term-send-raw-string "") (declare-function term-char-mode "") (declare-function term-line-mode "") (defun h3--terminal-discard-key () "Consume a terminal key sequence without forwarding it to the process." (interactive)) (defun term-clear () "Clear the entire term buffer and send the clear to the shell." (interactive) (let ((inhibit-read-only t)) (erase-buffer)) (term-send-raw-string "\C-l")) (with-eval-after-load 'term (define-key term-raw-map (kbd "C-c l") 'term-clear) (define-key term-raw-map (kbd "M-x") 'execute-extended-command) ;; Toggle between char mode (shell) and line mode (edit) (define-key term-raw-map (kbd "C-c C-j") 'term-line-mode) ;; Keep C-c function-key sequences inside Emacs in char-mode. (define-key term-raw-map (kbd "C-c ") #'my-send-region-to-term) (define-key term-raw-map (kbd "C-c ") #'h3--terminal-discard-key) (define-key term-raw-map (kbd "C-c ") #'h3--terminal-discard-key) (define-key term-mode-map (kbd "C-c C-k") 'term-char-mode) ) ;; -*- lexical-binding: t -*- (add-hook 'emacs-lisp-mode-hook #'(lambda () (local-set-key (kbd "C-c r") 'eval-region) (local-set-key (kbd "C-c f") 'eval-defun) (local-set-key (kbd "C-c e") 'eval-last-sexp) (local-set-key (kbd "C-c j") 'eval-print-last-sexp) (local-set-key (kbd "C-c b") 'eval-buffer) ;; (local-set-key (kbd "C-c m") 'eval-code-block) ;; (local-set-key (kbd "C-c M") 'eval-cmmt-block) )) ;; Markdown mode (use-package markdown-mode :ensure t :mode (("README\\.md\\'" . gfm-mode) ("\\.md\\'" . markdown-mode) ("\\.markdown\\'" . markdown-mode)) :init (setq markdown-command "multimarkdown")) ;; -*- lexical-binding: t -*- (declare-function org-insert-todo-subheading "") (declare-function org-element-at-point "org-element") (declare-function org-element-type "org-element") (declare-function org-element-property "org-element") (with-eval-after-load 'org (defvar org-src-fontify-natively) (defvar org-startup-with-inline-images) (defvar org-edit-src-content-indentation) (defvar org-src-preserve-indentation) (setq org-src-fontify-natively t) (setq org-startup-with-inline-images t) (org-babel-do-load-languages 'org-babel-load-languages '((lisp . t) (shell . t) ) ) (setq org-edit-src-content-indentation 0) (setq org-src-preserve-indentation nil)) (defun org-mode-insert () "quick insert in org mode" (interactive) (let* ((candidates '(table example src comment verse quote center image footnote todo-sub habit encrypt email sh bash shell config diagram uml plantuml py python json js )) (index (my-pick candidates "insert: ")) (pos-start (point)) (bchar-count 0) ) (when index (cl-case (elt candidates index) (habit (insert (concat "* TODO task\n" "SCHEDULED: <2025-07-07 Mon .+2d/4d>\n" ":PROPERTIES:\n" ":STYLE: habit\n" ":END:\n")) (setq bchar-count 21)) (table (insert (concat "| Function | Key |\n" "|---------- +-------|\n" "| | |\n")) (setq bchar-count 21)) ((example src comment verse quote center) (insert (upcase (concat (format "#+begin_%s\n\n" (elt candidates index)) (format "#+end_%s\n" (elt candidates index))) )) (setq bchar-count (+ (length (symbol-name (elt candidates index))) 8)) (when (eq (elt candidates index) 'src) (cl-incf bchar-count) ) ) ((sh bash shell) (insert "#+BEGIN_SRC shell\n\n#+END_SRC\n") (setq bchar-count 11) ) ((json js) (insert "#+BEGIN_SRC js\n\n#+END_SRC\n") (setq bchar-count 11) ) ((python py) (insert "#+BEGIN_SRC python\n\n#+END_SRC\n") (setq bchar-count 11) ) ((diagram uml plantuml) (insert "#+BEGIN_SRC plantuml :file tmp.png :cache yes\n@startuml\n\n@enduml\n#+END_SRC\n") (setq bchar-count 19) ) ((config) (insert "#+BEGIN_SRC conf\n\n#+END_SRC\n") (setq bchar-count 11) ) (image (insert "#+CAPTION: \n#+NAME: \n[[./x.jpg]]\n") (setq bchar-count 23) ) (encrypt (insert "# -*- mode:org; epa-file-encrypt-to: (\"4org\") -*-\n") ) (footnote (insert "[fn:1] \n") (setq bchar-count 1) ) (todo-sub (org-insert-todo-subheading)) ) (backward-char bchar-count) (indent-region pos-start (+ (point) bchar-count)) ))) (defun my/org-copy-block-content () "Copy the content of the block at point to the kill ring." (interactive) (require 'org-element) (let* ((element (org-element-at-point)) (type (org-element-type element)) (content nil)) ;; 1. Handle blocks that store raw text in :value ;; (src, example, comment, export, fixed-width) (if (memq type '(src-block example-block comment-block export-block fixed-width)) (setq content (org-element-property :value element)) ;; 2. Handle blocks that store parsed content (quote, special, center, etc.) ;; For these, we grab the buffer substring between contents-begin and contents-end (let ((beg (org-element-property :contents-begin element)) (end (org-element-property :contents-end element))) (when (and beg end) (setq content (buffer-substring-no-properties beg end))))) (if content (progn (kill-new content) (message "Copied content from %s!" type)) (user-error "Not inside a supported block")))) (add-hook 'org-mode-hook #'(lambda () (local-set-key (kbd "C-c i") 'org-mode-insert) (local-set-key (kbd "C-c w") 'my/org-copy-block-content)) ) ;;; key-chord.el --- map pairs of simultaneously pressed keys to commands ;; -*- lexical-binding: t; -*- ;; Vendored: v0.8.2 from https://github.com/emacsorphanage/key-chord ;; Copyright (C) 2003, 2005, 2008, 2012 David Andersson ;; Author: David Andersson ;; Maintainer: LemonBreezes ;; Package-Version: 0.8.2 ;; Package-Requires: ((emacs "24")) ;; Keywords: keyboard chord input ;; URL: https://github.com/LemonBreezes/key-chord ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 2 of ;; the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be ;; useful, but WITHOUT ANY WARRANTY; without even the implied ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ;; PURPOSE. See the GNU General Public License for more details. ;; You should have received a copy of the GNU General Public ;; License along with this program; if not, write to the Free ;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, ;; MA 02111-1307 USA ;;; Commentary: ;; This package implements support for mapping a pair of simultaneously ;; pressed keys to a command and for mapping the same key being pressed ;; twice in quick succession to a command. Such bindings are called ;; "key chords". ;;; Code: (defgroup key-chord nil "Map pairs of simultaneously pressed keys to commands." :group 'bindings) (defun key-chord-in-read-char-context-p () "Return non-nil if we're in a read-char or similar context. This is determined by checking if `overriding-local-map` is set to one of the special read keymaps." (and overriding-local-map (memq overriding-local-map (list read-key-empty-map read-key-full-map)))) (defcustom key-chord-two-keys-delay 0.1 "Max time delay between two key press to be considered a key chord." :type 'float) (defcustom key-chord-one-key-delay 0.2 "Max time delay between two press of the same key for a chord. This should normally be a little longer than `key-chord-two-keys-delay'." :type 'float) (defcustom key-chord-in-macros nil "If nil, don't expand key chords when executing keyboard macros. If non-nil, expand chord sequenses in macros, but only if a similar chord was entered during the last interactive macro recording. (This carries a bit of guesswork. We can't know for sure when executing whether two keys were typed quickly or slowly when recorded. Be careful enabling this as it could cause key chords to be expanded in places where they shouldn't be)." :type 'boolean) (defcustom key-chord-one-key-min-delay 0.0 "Minimum delay (in seconds) between two presses for a double-tap key chord. The delay applies for chords using the same key. If the delay between two identical key presses is less than this value (as when holding a key), the chord will not trigger." :type 'float) (defcustom key-chord-typing-detection nil "If non-nil, detect typing and temporarily disable chord detection. This helps avoid accidental chord triggering during fast typing. This also dramatically improves the performance of key-chord when typing text by acting like a debounce." :type 'boolean) (defcustom key-chord-typing-speed-threshold 0.1 "Maximum delay between keystrokes to be considered part of typing flow. Measured in seconds. If keys are pressed faster than this threshold, key-chord detection will be temporarily disabled." :type 'float) (defcustom key-chord-typing-reset-delay 0.5 "Time after which to reset typing detection if no keys are pressed. Measured in seconds. After this much idle time, key-chord detection will be re-enabled." :type 'float) (defcustom key-chord-use-key-tracking t "If non-nil, track which keys are used in chords to optimize lookups. This improves performance by avoiding unnecessary `key-chord-lookup-key' calls. However, it could potentially cause issues if key chords are defined outside of the normal key-chord-define* functions." :type 'boolean) ;; Internal vars (defvar key-chord-mode nil) ;; Shortcut for key-chord-input-method: no need to test a key again if it ;; didn't matched a chord the last time. Improves feedback during autorepeat. (defvar key-chord-last-unmatched nil) ;; Macro heuristics: Keep track of which chords was used when the last macro ;; was defined. Or rather, only the first-char of the chords. Only expand ;; matching chords during macro execution. (defvar key-chord-in-last-kbd-macro nil) (defvar key-chord-defining-kbd-macro nil) ;; Typing detection variables (defvar key-chord-in-typing-flow nil "Non-nil when user appears to be typing text rather than executing commands.") (defvar key-chord-last-key-time nil "Time when the last key was pressed.") (defvar key-chord-typing-reset-time nil "Time after which typing flow should be reset.") ;; Key tracking for optimization (defvar key-chord-keys-in-use (make-vector 256 nil) "Vector indicating which keys are used in any chord.") ;;;###autoload (define-minor-mode key-chord-mode "Map pairs of simultaneously pressed keys to commands. See functions `key-chord-define-global', `key-chord-define-local', and `key-chord-define' and variables `key-chord-two-keys-delay' and `key-chord-one-key-delay'." :global t (setq input-method-function (and key-chord-mode 'key-chord-input-method)) (key-chord-reset-typing-detection)) ;;;###autoload (defun key-chord-define-global (keys command) "Define a key-chord of the two keys in KEYS starting a COMMAND. KEYS can be a string or a vector of two elements. Currently only elements that corresponds to ascii codes in the range 32 to 126 can be used. COMMAND can be an interactive function, a string, or nil. If COMMAND is nil, the key-chord is removed. Note that KEYS defined locally in the current buffer will have precedence." (interactive "sSet key chord globally (2 keys): \nCSet chord \"%s\" to command: ") (key-chord-define (current-global-map) keys command)) ;;;###autoload (defun key-chord-define-local (keys command) "Locally define a key-chord of the two keys in KEYS starting a COMMAND. KEYS can be a string or a vector of two elements. Currently only elements that corresponds to ascii codes in the range 32 to 126 can be used. COMMAND can be an interactive function, a string, or nil. If COMMAND is nil, the key-chord is removed. The binding goes in the current buffer's local map, which in most cases is shared with all other buffers in the same major mode." (interactive "sSet key chord locally (2 keys): \nCSet chord \"%s\" to command: ") (key-chord-define (current-local-map) keys command)) (defun key-chord-unset-global (keys) "Remove global key-chord of the two keys in KEYS." (interactive "sUnset key chord globally (2 keys): ") (key-chord-define (current-global-map) keys nil)) (defun key-chord-unset-local (keys) "Remove local key-chord of the two keys in KEYS." (interactive "sUnset key chord locally (2 keys): ") (key-chord-define (current-local-map) keys nil)) ;;;###autoload (defun key-chord-register-keys (key1 key2) "Register KEY1 and KEY2 as being used in a key chord. This function should be called by packages that define key chords outside of the standard key-chord-define functions." ;; Always register keys regardless of key-tracking setting (when (and (integerp key1) (< key1 256)) (aset key-chord-keys-in-use key1 t)) (when (and (integerp key2) (< key2 256)) (aset key-chord-keys-in-use key2 t))) ;;;###autoload (defun key-chord-unregister-keys (key1 key2) "Unregister KEY1 and KEY2 as being used in a key chord. This should only be called if you're certain these keys are not used in any other chords." ;; Always attempt to unregister keys regardless of key-tracking setting ;; Only unregister if we're sure the keys aren't used elsewhere (let ((remove-key1 t) (remove-key2 t)) ;; Check if keys are used in any other chords (let ((maps (list (current-global-map) (current-local-map)))) (dolist (map (append maps (current-minor-mode-maps))) (when map ;; Check if key1 is used in any other chord (dotimes (k 256) (when (and (not (and (= k key2) (= key1 key2))) (or (key-chord-lookup-key1 map (vector 'key-chord key1 k)) (key-chord-lookup-key1 map (vector 'key-chord k key1)))) (setq remove-key1 nil))) ;; Check if key2 is used in any other chord (dotimes (k 256) (when (and (not (and (= k key1) (= key1 key2))) (or (key-chord-lookup-key1 map (vector 'key-chord key2 k)) (key-chord-lookup-key1 map (vector 'key-chord k key2)))) (setq remove-key2 nil)))))) ;; Remove keys from tracking vector if not used elsewhere (when remove-key1 (aset key-chord-keys-in-use key1 nil)) (when remove-key2 (aset key-chord-keys-in-use key2 nil)))) ;;;###autoload (defun key-chord-define (keymap keys command) "Define in KEYMAP, a key-chord of the two keys in KEYS starting a COMMAND. KEYS can be a string or a vector of two elements. Currently only elements that corresponds to ascii codes in the range 32 to 126 can be used. COMMAND can be an interactive function, a string, or nil. If COMMAND is nil, the key-chord is removed." (when (/= 2 (length keys)) (error "Key-chord keys must have two elements")) (let ((key1 (aref keys 0)) (key2 (aref keys 1))) ;; Check that both keys are bytes (unless (and (integerp key1) (< key1 256) (integerp key2) (< key2 256)) (error "Key-chord keys must both be bytes (characters with codes < 256)")) (if (eq key1 key2) (define-key keymap (vector 'key-chord key1 key2) command) (define-key keymap (vector 'key-chord key1 key2) command) (define-key keymap (vector 'key-chord key2 key1) command)) ;; Update key tracking directly (if command (key-chord-register-keys key1 key2) (key-chord-unregister-keys key1 key2)))) (defun key-chord-lookup-key1 (keymap key) "Like `lookup-key' but no third arg and no numeric return value. KEYMAP is the keymap to look up the KEY in." (let ((res (lookup-key keymap key))) (and (not (numberp res)) res))) (defun key-chord-lookup-key (key) "Lookup KEY in all current key maps." (let ((maps (current-minor-mode-maps)) res) (while (and maps (not res)) (setq res (key-chord-lookup-key1 (car maps) key)) (setq maps (cdr maps))) (or res (and (current-local-map) (key-chord-lookup-key1 (current-local-map) key)) (key-chord-lookup-key1 (current-global-map) key)))) (defun key-chord-describe () "List key chord bindings in a help buffer. Two key chords will be listed twice and there will be Prefix Commands. Please ignore that." (interactive) (describe-bindings [key-chord])) (defun key-chord-reset-all-state () "Reset all key-chord state variables." (setq key-chord-last-unmatched nil) (setq key-chord-in-typing-flow nil) (setq key-chord-last-key-time nil) (setq key-chord-typing-reset-time nil)) (defun key-chord-reset-typing-detection () "Reset typing detection state when `key-chord-mode' is toggled." (key-chord-reset-all-state)) (defun key-chord-reset-typing-mode () "Reset the typing detection mode." (setq key-chord-in-typing-flow nil) (setq key-chord-last-unmatched nil)) (defun key-chord-pause-typing-flow () "Temporarily pause typing flow detection to allow chord detection." (when key-chord-typing-detection (setq key-chord-in-typing-flow nil))) (defun key-chord-valid-chord-char-p (char) "Return non-nil if CHAR is valid for chord detection. A valid character is an integer in the range 0-255." (and (integerp char) (< char 256))) (defun key-chord-execute-chord (first-char next-char) "Execute the chord composed of FIRST-CHAR and NEXT-CHAR. Returns the key-chord event to be processed." ;; Record the chord for macro tracking (setq key-chord-defining-kbd-macro (cons first-char key-chord-defining-kbd-macro)) ;; Reset state after successful chord execution (setq key-chord-last-unmatched nil) ;; Return the chord event (list 'key-chord first-char next-char)) (defun key-chord-no-chord (first-char &optional next-char) "Handle the case when no chord is matched. FIRST-CHAR is the first character pressed. If NEXT-CHAR is provided, it will be added to `unread-command-events`." (when next-char (setq unread-command-events (cons next-char unread-command-events))) (setq key-chord-last-unmatched first-char) (list first-char)) (defun key-chord-check-typing-flow (current-time) "Check if user is in typing mode based on timing between keystrokes. CURRENT-TIME is the time at which the function was called." (when key-chord-typing-detection ;; Check if we need to reset typing flow based on elapsed time (when (and key-chord-typing-reset-time (> (float-time current-time) key-chord-typing-reset-time)) (setq key-chord-in-typing-flow nil) (setq key-chord-last-unmatched nil)) ;; Reset last-unmatched when exiting typing flow ;; Set new reset time (setq key-chord-typing-reset-time (+ (float-time current-time) key-chord-typing-reset-delay)) ;; Check if we're in typing flow based on timing (unless key-chord-in-typing-flow (when key-chord-last-key-time (let ((elapsed (float-time (time-subtract current-time key-chord-last-key-time)))) (when (< elapsed key-chord-typing-speed-threshold) (setq key-chord-in-typing-flow t))))) ;; Update last key time (setq key-chord-last-key-time current-time))) (defun key-chord-input-method (first-char) "Input method controlled by key bindings with the prefix `key-chord'. FIRST-CHAR is the first character input by the user." ;; Check if we're in a read-char context and bypass key-chord if so (if (key-chord-in-read-char-context-p) (progn (key-chord-reset-all-state) (list first-char)) ;; Not in read-char context, proceed with normal key-chord processing (progn ;; Check typing mode (but not during macro execution) (unless executing-kbd-macro (key-chord-check-typing-flow (current-time))) (cond ;; For any non-valid chord character, bypass chord detection ((not (key-chord-valid-chord-char-p first-char)) (setq key-chord-last-unmatched nil) ;; Reset for safety (list first-char)) ;; Skip chord detection if in typing mode (but not during macro execution) ((and key-chord-typing-detection key-chord-in-typing-flow (not executing-kbd-macro)) ;; Don't update key-chord-last-unmatched in typing flow mode (list first-char)) ;; Skip chord detection during macro execution if key-chord-in-macros is nil ;; or if the key is not in a recorded chord ((and executing-kbd-macro (or (not key-chord-in-macros) (not (memq first-char key-chord-in-last-kbd-macro)))) (key-chord-no-chord first-char)) ;; Optimization: Skip chord detection completely if key tracking is enabled ;; and first-char is not used in any chord ((and key-chord-use-key-tracking (not (aref key-chord-keys-in-use first-char))) (key-chord-no-chord first-char)) ;; Continue with chord detection for keys that might be part of a chord ((not (eq first-char key-chord-last-unmatched)) (key-chord-pause-typing-flow) ;; Temporarily exit typing flow for chord detection (let ((res (key-chord-lookup-key (vector 'key-chord first-char)))) (if (not res) (key-chord-no-chord first-char) (let ((start-time (current-time)) (delay (if (key-chord-lookup-key (vector 'key-chord first-char first-char)) key-chord-one-key-delay key-chord-two-keys-delay))) ;; Read second character (let ((next-char (read-event nil nil delay))) (cond ;; No second character received within timeout ((null next-char) (key-chord-no-chord first-char)) ;; Handle invalid next character ((not (key-chord-valid-chord-char-p next-char)) (key-chord-no-chord first-char next-char)) ;; Handle double-press of the same key (with timing checks) ((eq first-char next-char) (let ((elapsed (float-time (time-subtract (current-time) start-time)))) (if (< elapsed key-chord-one-key-min-delay) ;; Too fast (key held down) - not a chord (key-chord-no-chord first-char next-char) ;; Check if this is a valid same-key chord (if (key-chord-lookup-key (vector 'key-chord first-char next-char)) (key-chord-execute-chord first-char next-char) ;; Not a valid chord (key-chord-no-chord first-char next-char))))) ;; Handle two different keys (t ;; Check if this is a valid two-key chord (if (key-chord-lookup-key (vector 'key-chord first-char next-char)) (key-chord-execute-chord first-char next-char) ;; Not a valid chord (key-chord-no-chord first-char next-char))))))))) ;; Key was last unmatched (t (key-chord-no-chord first-char)))))) (defun key-chord--start-kbd-macro (_append &optional _no-exec) "Reset key chord tracking when a keyboard macro is started. _APPEND and _NO-EXEC are ignored arguments from `start-kbd-macro'." (setq key-chord-defining-kbd-macro nil)) (advice-add 'start-kbd-macro :after #'key-chord--start-kbd-macro) (defun key-chord--end-kbd-macro (&optional _repeat _loopfunc) "Save key chord information when a keyboard macro ends. _REPEAT and _LOOPFUNC are ignored arguments from `end-kbd-macro'." (setq key-chord-in-last-kbd-macro key-chord-defining-kbd-macro)) (advice-add 'end-kbd-macro :after #'key-chord--end-kbd-macro) (provide 'key-chord) ;; Local Variables: ;; indent-tabs-mode: nil ;; End: ;;; key-chord.el ends here ;;; hotkey.el --- Global keybindings -*- lexical-binding: t -*- ;;; Commentary: ;; Global keyboard shortcuts and key-chord configurations ;;; Code: (declare-function recompile "") (declare-function subword-right "subword") (declare-function subword-left "subword") (declare-function cape-emoji "cape") (declare-function my/global-text-scale-decrease "") (declare-function my/global-text-scale-increase "") (declare-function my/global-text-scale-reset "") (global-set-key (kbd "C-x SPC") 'set-mark-command) (global-set-key (kbd "C-x >") 'end-of-buffer) (global-set-key (kbd "C-x <") 'beginning-of-buffer) (global-set-key (kbd "C-c c") #'replace-string) (global-set-key (kbd "C-c q") #'read-only-mode) (global-set-key (kbd "M-n") #'forward-paragraph) (global-set-key (kbd "M-p") #'backward-paragraph) (global-set-key (kbd "M-F") #'subword-right) (global-set-key (kbd "M-B") #'subword-left) (global-set-key (kbd "C-x p") (lambda () (interactive) (other-window -1))) (global-set-key (kbd "C-x B") #'scratch-buffer) (global-set-key (kbd "C-c Q") 'sudo-edit-current-buffer) (global-set-key (kbd "C-c k") #'delete-frame) (global-set-key (kbd "C-c K") #'save-buffers-kill-emacs) (global-set-key (kbd "C-c s") #'eww-search-words) ;; Compile and shell commands are defined in 14-template.el ;; with smart language detection and command memory (global-set-key (kbd "C-c y") #'duplicate-line) (global-set-key (kbd "C-c TAB") #'indent-buffer) (global-set-key (kbd "C--") #'my/global-text-scale-decrease) (global-set-key (kbd "C-=") #'my/global-text-scale-increase) (global-set-key (kbd "C-0") #'my/global-text-scale-reset) (global-set-key (kbd "C-_") #'undo) (global-set-key (kbd "C-+") #'undo-redo) (global-set-key (kbd "C-x r (") #'kmacro-call-macro) (global-set-key (kbd "C-x r )") #'kmacro-call-macro) ;; M-. is xref find definitions (global-set-key (kbd "M-,") #'xref-find-references) ;; Vocabulary and abbrev keybindings are in core/19-abbrev.el: ;; C-$ - Add word to vocabulary ;; C-# - Add multiple typo variants (global-set-key (kbd "C-!") #'cape-emoji) (global-unset-key (kbd "C-x c")) (global-unset-key (kbd "C-z")) (global-unset-key (kbd "M-z")) (global-unset-key (kbd "M-m")) (global-unset-key (kbd "M-/")) (global-unset-key (kbd "C-x C-z")) (require 'dired) (define-key dired-mode-map (kbd "o") (lambda () (interactive) (let ((file (dired-get-file-for-visit))) (eww-open-file file) ))) ;; 1. `g` (lowercase g) - Revert buffer, reload the current directory ;; 2. `C-u g` - Reload directory and show all files, including hidden ones (setq dired-listing-switches "-Alht") ;; http://emacswiki.org/emacs/download/key-chord.el ;; (require 'key-chord) (key-chord-mode 1) (setq key-chord-typing-detection t) (defun my/goto-char (char) "Jump forward to next CHAR like vim f" (interactive "cGo to char: ") (search-forward (char-to-string char) (line-end-position) t 1) (forward-char) ) (defun my/goto-char-backward (char) "Jump backward to previous CHAR like vim F" (interactive "cGo to char backward: ") (search-backward (char-to-string char) 0 t 1) (forward-char) ) (key-chord-define-global ",." "<>\C-b") (key-chord-define-global "''" "`'\C-b") ;; (key-chord-define-global "ss" 'my/goto-char) (key-chord-define-global "hh" 'my/goto-char-backward) ;; mode specific keybinding ;; (key-chord-define c-mode-map ";;" "\C-e;") ;; -*- lexical-binding: t -*- ;; Apply background immediately to prevent white flash ;; This must happen before any other face configuration (set-face-attribute 'default nil :background "#1f1f28" :foreground "#ffd700") (defconst %color/fujiWhite "#DCD7BA") (defconst %color/old-white "#C8C093") (defconst %color/sumiInk-0 "#16161D") (defconst %color/sumiInk-1b "#1f1f28") (defconst %color/sumiInk-1 "#1F1F28") (defconst %color/sumiInk-2 "#2A2A37") (defconst %color/sumiInk-3 "#363646") (defconst %color/sumiInk-4 "#54546D") (defconst %color/waveBlue-1 "#223249") (defconst %color/waveBlue-2 "#2D4F67") (defconst %color/waveAqua1 "#6A9589") (defconst %color/waveAqua2 "#7AA89F") (defconst %color/winterGreen "#2B3328") (defconst %color/winterYellow "#49443C") (defconst %color/winterRed "#43242B") (defconst %color/winterBlue "#252535") (defconst %color/autumnGreen "#76946A") (defconst %color/autumnRed "#C34043") (defconst %color/autumnYellow "#DCA561") (defconst %color/samuraiRed "#E82424") (defconst %color/roninYellow "#FF9E3B") (defconst %color/dragonBlue "#658594") (defconst %color/fujiGray "#727169") (defconst %color/springViolet1 "#938AA9") (defconst %color/oniViolet "#957FB8") (defconst %color/crystalBlue "#7E9CD8") (defconst %color/springViolet2 "#9CABCA") (defconst %color/springBlue "#7FB4CA") (defconst %color/lightBlue "#A3D4D5") (defconst %color/springGreen "#98BB6C") (defconst %color/boatYellow1 "#938056") (defconst %color/boatYellow2 "#C0A36E") (defconst %color/carpYellow "#E6C384") (defconst %color/sakuraPink "#D27E99") (defconst %color/waveRed "#E46876") (defconst %color/peachRed "#FF5D62") (defconst %color/surimiOrange "#FFA066") (defconst %color/katanaGray "#717C7C") (defconst %color/comet "#54536D") ;; (display-graphic-p) (when (not (display-graphic-p)) (set-face-background 'default "dark") ) (custom-set-faces '(font-lock-type-face ((t (:bold t :foreground "#1B813E")))) `(font-lock-regexp-grouping-backslash ((t (:foreground ,%color/boatYellow2)))) '(font-lock-keyword-face ((t (:bold t :foreground "#800080")))) '(font-lock-warning-face ((t (:bold nil :foreground "#DE2A18")))) '(font-lock-string-face ((t (:bold t :foreground "#3A8FB7")))) '(font-lock-builtin-face ((t (:bold t :foreground "#F43E06")))) `(font-lock-reference-face ((t (:foreground ,%color/peachRed)))) '(font-lock-constant-face ((t (:bold t :foreground "#BDC0BA")))) '(font-lock-function-name-face ((t (:bold t :foreground "#5BAE23")))) '(font-lock-variable-name-face ((t (:bold t :foreground "#F596AA")))) `(font-lock-negation-char-face ((t (:foreground ,%color/peachRed)))) '(font-lock-comment-face ((t (:bold nil :foreground "#7a7374")))) '(font-lock-comment-delimiter-face ((t (:bold t :foreground "#fbb612")))) `(font-lock-doc-face ((t (:foreground ,%color/comet)))) `(font-lock-doc-markup-face ((t (:foreground ,%color/comet)))) '(font-lock-preprocessor-face ((t (:bold t :foreground "CornflowerBlue")))) `(elisp-shorthand-font-lock-face ((t (:foreground ,%color/fujiWhite)))) '(region ((t (:background "#800080")))) '(highlight ((t (:background "gray18")))) '(cursor ((t (:foreground "black")))) '(default ((t (:background "#1f1f28" :foreground "#ffd700")))) '(line-number ((t (:foreground "#54546D")))) '(line-number-current-line ((t (:foreground "#9CABCA" :background "#2A2A37" :bold t)))) '(numbers ((t (:background "#D27E99")))) '(hl-line ((t (:background "#2A2A37")))) `(separator-line ((t (:background ,%color/sumiInk-0)))) `(vertical-border ((t (:foreground ,%color/sumiInk-4)))) `(mode-line ((t (:background ,%color/sumiInk-0)))) `(mode-line-inactive ((t (:background unspecified :foreground ,%color/sumiInk-4 :bold unspecified)))) `(mode-line-active ((t (:background ,%color/sumiInk-0 :foreground ,%color/old-white :bold unspecified)))) `(mode-line-highlight ((t (:foreground ,%color/boatYellow2)))) `(mode-line-buffer-id ((t (:foreground ,%color/waveAqua2 :bold t)))) `(ace-jump-face-background ((t (:foreground ,%color/waveBlue-2)))) `(ace-jump-face-foreground ((t (:foreground ,%color/peachRed :background ,%color/sumiInk-0 :bold t)))) ;; vertico `(vertico-multiline ((t (:background ,%color/samuraiRed)))) `(vertico-group-title ((t (:background ,%color/winterBlue :foreground ,%color/lightBlue :bold t)))) `(vertico-group-separator ((t (:background ,%color/winterBlue :foreground ,%color/lightBlue :strike-through t)))) `(vertico-current ((t (:foreground ,%color/carpYellow :bold t :italic t :background ,%color/waveBlue-1)))) `(vertico-posframe-border ((t (:background ,%color/sumiInk-3)))) `(vertico-posframe ((t (:background ,%color/sumiInk-2)))) `(orderless-match-face-0 ((t (:foreground ,%color/crystalBlue :bold t)))) `(comint-highlight-prompt ((t (:background ,%color/springViolet2 :foreground ,%color/sumiInk-1)))) `(completions-annotations ((t (:background unspecified :foreground ,%color/dragonBlue :italic t)))) `(marginalia-file-priv-no ((t (:background unspecified)))) ;; ;; rainbow delimiters `(rainbow-delimiters-mismatched-face ((t (:foreground ,%color/peachRed)))) `(rainbow-delimiters-unmatched-face ((t (:foreground ,%color/waveAqua2)))) `(rainbow-delimiters-base-error-face ((t (:foreground ,%color/peachRed)))) `(rainbow-delimiters-base-face ((t (:foreground ,%color/sumiInk-4)))) `(rainbow-delimiters-depth-1-face ((t (:foreground ,%color/springViolet2)))) `(rainbow-delimiters-depth-2-face ((t (:foreground ,%color/dragonBlue)))) `(rainbow-delimiters-depth-3-face ((t (:foreground ,%color/springViolet1)))) `(rainbow-delimiters-depth-4-face ((t (:foreground ,%color/springGreen)))) `(rainbow-delimiters-depth-5-face ((t (:foreground ,%color/waveAqua2)))) `(rainbow-delimiters-depth-6-face ((t (:foreground ,%color/carpYellow)))) `(rainbow-delimiters-depth-7-face ((t (:foreground ,%color/waveRed)))) `(rainbow-delimiters-depth-8-face ((t (:foreground ,%color/lightBlue)))) `(rainbow-delimiters-depth-9-face ((t (:foreground ,%color/springViolet2)))) ;; show-paren `(show-paren-match ((t (:background ,%color/waveAqua1 :foreground ,%color/sumiInk-0 :bold t)))) `(show-paren-match-expression ((t (:background ,%color/waveAqua1 :foreground ,%color/sumiInk-0 :bold t)))) `(show-paren-mismatch ((t (:background ,%color/peachRed :foreground ,%color/old-white)))) `(tooltip ((t (:foreground ,%color/sumiInk-0 :background ,%color/carpYellow :bold t)))) `(match ((t (:background ,%color/carpYellow :foreground ,%color/sumiInk-0)))) ;; term - enhanced colors for better visibility `(term ((t (:background ,%color/sumiInk-1b :foreground ,%color/fujiWhite)))) `(term-color-blue ((t (:background ,%color/springBlue :foreground ,%color/springBlue)))) `(term-color-bright-blue ((t (:background ,%color/lightBlue :foreground ,%color/lightBlue)))) `(term-color-green ((t (:background ,%color/springGreen :foreground ,%color/springGreen)))) `(term-color-bright-green ((t (:background ,%color/springGreen :foreground ,%color/springGreen)))) `(term-color-black ((t (:background ,%color/sumiInk-1b :foreground ,%color/old-white)))) `(term-color-bright-black ((t (:background ,%color/fujiGray :foreground ,%color/fujiGray)))) `(term-color-white ((t (:background ,%color/fujiWhite :foreground ,%color/fujiWhite)))) `(term-color-bright-white ((t (:background ,%color/fujiWhite :foreground ,%color/fujiWhite)))) `(term-color-red ((t (:background ,%color/peachRed :foreground ,%color/peachRed)))) `(term-color-bright-red ((t (:background ,%color/samuraiRed :foreground ,%color/samuraiRed)))) `(term-color-yellow ((t (:background ,%color/carpYellow :foreground ,%color/carpYellow)))) `(term-color-bright-yellow ((t (:background ,%color/roninYellow :foreground ,%color/roninYellow)))) `(term-color-cyan ((t (:background ,%color/springBlue :foreground ,%color/springBlue)))) `(term-color-bright-cyan ((t (:background ,%color/lightBlue :foreground ,%color/lightBlue)))) `(term-color-magenta ((t (:background ,%color/springViolet2 :foreground ,%color/springViolet2)))) `(term-color-bright-magenta ((t (:background ,%color/sakuraPink :foreground ,%color/sakuraPink)))) ;; popup `(popup-face ((t (:inherit 'tooltip)))) `(popup-selection-face ((t (:inherit 'tooltip)))) `(popup-tip-face ((t (:inherit 'tooltip)))) ;; eat - enhanced colors for better visibility `(eat-term-color-0 ((t (:background ,%color/sumiInk-1b :foreground ,%color/old-white)))) `(eat-term-color-1 ((t (:background ,%color/peachRed :foreground ,%color/peachRed)))) `(eat-term-color-2 ((t (:background ,%color/springGreen :foreground ,%color/springGreen)))) `(eat-term-color-3 ((t (:background ,%color/carpYellow :foreground ,%color/sumiInk-0)))) `(eat-term-color-4 ((t (:background ,%color/springBlue :foreground ,%color/springBlue)))) `(eat-term-color-5 ((t (:background ,%color/springViolet2 :foreground ,%color/springViolet2)))) `(eat-term-color-6 ((t (:background ,%color/lightBlue :foreground ,%color/lightBlue)))) `(eat-term-color-7 ((t (:background ,%color/fujiWhite :foreground ,%color/fujiWhite)))) `(eat-term-color-8 ((t (:background ,%color/fujiGray :foreground ,%color/fujiGray)))) `(eat-term-color-9 ((t (:background ,%color/samuraiRed :foreground ,%color/samuraiRed)))) `(eat-term-color-10 ((t (:background ,%color/springGreen :foreground ,%color/springGreen)))) `(eat-term-color-11 ((t (:background ,%color/roninYellow :foreground ,%color/sumiInk-0)))) `(eat-term-color-12 ((t (:background ,%color/lightBlue :foreground ,%color/lightBlue)))) `(eat-term-color-13 ((t (:background ,%color/sakuraPink :foreground ,%color/sakuraPink)))) `(eat-term-color-14 ((t (:background ,%color/lightBlue :foreground ,%color/lightBlue)))) `(eat-term-color-15 ((t (:background ,%color/fujiWhite :foreground ,%color/fujiWhite)))) ;; ;; message colors `(message-header-name ((t (:foreground ,%color/sumiInk-4)))) `(message-header-other ((t (:foreground ,%color/surimiOrange)))) `(message-header-subject ((t (:foreground ,%color/carpYellow)))) `(message-header-to ((t (:foreground ,%color/old-white)))) `(message-header-cc ((t (:foreground ,%color/waveAqua2)))) `(message-header-xheader ((t (:foreground ,%color/old-white)))) `(custom-link ((t (:foreground ,%color/crystalBlue)))) `(link ((t (:foreground ,%color/crystalBlue)))) ;; org-mode `(org-done ((t (:foreground ,%color/dragonBlue)))) `(org-code ((t (:background ,%color/sumiInk-0)))) `(org-meta-line ((t (:background ,%color/winterGreen :foreground ,%color/springGreen)))) `(org-block ((t (:background ,%color/sumiInk-0 :foreground ,%color/sumiInk-4)))) `(org-block-begin-line ((t (:background ,%color/winterBlue :foreground ,%color/springBlue)))) `(org-block-end-line ((t (:background ,%color/winterRed :foreground ,%color/peachRed)))) `(org-headline-done ((t (:foreground ,%color/dragonBlue :strike-through t)))) `(org-todo ((t (:foreground ,%color/surimiOrange :bold t)))) `(org-headline-todo ((t (:foreground ,%color/sumiInk-2)))) `(org-upcoming-deadline ((t (:foreground ,%color/peachRed)))) `(org-footnote ((t (:foreground ,%color/waveAqua2)))) `(org-indent ((t (:background ,%color/sumiInk-1b :foreground ,%color/sumiInk-1b)))) `(org-hide ((t (:background ,%color/sumiInk-1b :foreground ,%color/sumiInk-1b)))) `(org-date ((t (:foreground ,%color/waveBlue-2)))) `(org-ellipsis ((t (:foreground ,%color/waveBlue-2 :bold t)))) `(org-level-1 ((t (:foreground ,%color/peachRed :height 1.3 :bold t)))) `(org-level-2 ((t (:foreground ,%color/springViolet2 :height 1.15 :bold t)))) `(org-level-3 ((t (:foreground ,%color/boatYellow2 :height 1.05)))) `(org-level-4 ((t (:foreground ,%color/fujiWhite)))) `(org-level-5 ((t (:foreground ,%color/fujiWhite)))) `(org-level-6 ((t (:foreground ,%color/carpYellow)))) `(org-level-7 ((t (:foreground ,%color/surimiOrange)))) `(org-level-8 ((t (:foreground ,%color/springGreen)))) ) ;; -*- lexical-binding: t -*- ;;; 11-eww-lnum.el --- EWW link numbering for keyboard navigation ;;; Commentary: ;; Provides numbered link selection in EWW browser ;; Source: https://github.com/m00natic/eww-lnum ;;; Code: (with-eval-after-load 'eww ;; eww-lnum ;; https://github.com/m00natic/eww-lnum (require 'eww) (defface eww-lnum-number '((((class color grayscale) (min-colors 88) (background light)) :foreground "grey50") (((class color grayscale) (min-colors 88) (background dark)) :foreground "grey70") (((class color) (min-colors 8) (background light)) :foreground "green") (((class color) (min-colors 8) (background dark)) :foreground "yellow")) "Face used for item numbers." :group 'eww) (defcustom eww-lnum-quick-browsing 'quick-numbers "If non-nil, do aggressive selection. Possible values are: `quick-numbers' quick selection only when entering numbers `quick-filter' ditto only when filtering `quick-all' always quick selecting" :group 'eww :type '(radio (const :format "%v " nil) (const :format "%v " quick-numbers) (const :format "%v " quick-filter) (const :format "%v" quick-all))) (defcustom eww-lnum-context-alist '(("news.ycombinator.com" . 2) ("reddit.com" . 1)) "Alist of number of additional items to be numbered after \ filtering for same url." :group 'eww :type 'alist) (defvar eww-lnum-actions-custom-type '(repeat (choice :format "%[Value Menu%] %v" (string :tag "Information line") (group :tag "Keycode and Action" :indent 2 (character :tag "Key") function (string :tag "Prompt"))))) (defcustom eww-lnum-actions-general '("---- Default ----" (?g (lambda (info) (push-mark (point)) (goto-char (cadr info))) "Move to")) "Alist specifying keycodes and available actions over selected anchor." :group 'eww :type eww-lnum-actions-custom-type) (defcustom eww-lnum-actions-link-alist '("---- Link ----" (?f eww-lnum-visit "Visit") (?e (lambda (info) (eww-lnum-visit info nil t)) "Edit URL and visit") (?F (lambda (info) (eww-lnum-visit info t)) "Visit in new buffer") (?E (lambda (info) (eww-lnum-visit info t t)) "Edit URL and visit in new buffer") (?b (lambda (info) (eww-lnum-visit info :background)) "Open in background") (?B (lambda (info) (eww-lnum-visit info :background t)) "Edit URL and open in background") (?d (lambda (info) (save-excursion (goto-char (cadr info)) (eww-download))) "Download") (?w (lambda (info) (let ((url (car info))) (kill-new url) (message url))) "Copy") (?o (lambda (info) (eww-browse-with-external-browser (car info))) "Open in external browser")) "Alist specifying keycodes and available actions over a selected link." :group 'eww :type eww-lnum-actions-custom-type) (defcustom eww-lnum-actions-button-alist '("---- Button ----" (?p eww-lnum-activate-form "Push")) "Alist specifying keycodes and available actions over a selected button." :group 'eww :type eww-lnum-actions-custom-type) (defun eww-lnum-current-url () "Get current page url." (if (boundp 'eww-current-url) eww-current-url (plist-get eww-data :url))) (defun eww-lnum-remove-overlays (&optional start end) "Remove numbering and match overlays between START and END points. If missing, clear the current visible window." (let* ((start-pos (window-start)) (window-size (- (window-end) start-pos)) (start (or start (max (- start-pos window-size) (point-min)))) (end (or end (min (+ start-pos (* 2 window-size)) (point-max))))) (dolist (overlay (overlays-in start end)) (if (or (overlay-get overlay 'eww-lnum-overlay) (overlay-get overlay 'eww-lnum-match)) (delete-overlay overlay))))) (defmacro eww-lnum-set-overlay (pos index) "Set numbering overlay at POS with INDEX." `(let ((overlay (make-overlay ,pos (1+ ,pos)))) (let ((num (format "[%d]" (setq ,index (1+ ,index))))) (overlay-put overlay 'before-string num) (add-text-properties 0 (length num) '(face eww-lnum-number) num) (overlay-put overlay 'evaporate t)) (overlay-put overlay 'eww-lnum-overlay ,index))) (defun eww-lnum-replace-regexps-in-string (string &rest regexps) "In STRING replace an alist of REGEXPS." (if (cadr regexps) (replace-regexp-in-string (car regexps) (cadr regexps) (apply #'eww-lnum-replace-regexps-in-string string (cddr regexps))) string)) (defun eww-lnum-set-numbering (next-func &optional reg dont-clear-p) "Make overlays that display link numbers. Return last used index. NEXT-FUNC is function to iterate numbered elements. REG is filter string for anchor text. DONT-CLEAR-P determines whether previous numbering has to be cleared." (setq reg (if reg (eww-lnum-replace-regexps-in-string ; escape special characters reg "\\?" "\\\\?" "\\!" "\\\\!" "\\[" "\\\\[" "\\*" "\\\\*" "\\+" "\\\\+" "\\." "\\\\." "\\^" "\\\\^" "\\$" "\\\\$") "")) (or dont-clear-p (eww-lnum-remove-overlays)) (let ((pos (max (1- (window-start)) (point-min))) (pmax (min (window-end) (point-max))) (index 0) (context (or (assoc-default (eww-lnum-current-url) eww-lnum-context-alist 'string-match-p) 0))) (while (and pos (setq pos (funcall next-func pos)) (< pos pmax)) (when (string-match-p reg (buffer-substring-no-properties pos (or (next-single-property-change pos 'help-echo) (point-max)))) (eww-lnum-set-overlay pos index) (let ((counter context)) (while (and (>= (setq counter (1- counter)) 0) (setq pos (funcall next-func pos)) (< pos pmax)) (eww-lnum-set-overlay pos index))))) index)) (defun eww-lnum-next (&optional pos) "Return position of next element to be numbered starting at POS. If POS is not given, start from current point." (or pos (setq pos (point))) (if (get-char-property pos 'help-echo) ; currently over such element (setq pos (next-single-property-change pos 'help-echo))) (if (and pos (or (get-char-property pos 'help-echo) (setq pos (next-single-property-change pos 'help-echo)))) pos (point-max))) (defun eww-lnum-next-filter (next-func filter pmin pmax) "Search next element according to NEXT-FUNC and FILTER. Do this in region between points PMIN and PMAX. If such element is found, return its position. Nil otherwise." (setq filter (eww-lnum-replace-regexps-in-string ; escape special characters filter "\\?" "\\\\?" "\\!" "\\\\!" "\\[" "\\\\[" "\\*" "\\\\*" "\\+" "\\\\+" "\\." "\\\\." "\\^" "\\\\^" "\\$" "\\\\$")) (let ((pos pmin)) (catch 'found (while (and pos (setq pos (funcall next-func pos)) (< pos pmax)) (if (string-match-p filter (buffer-substring-no-properties pos (or (next-single-property-change pos 'help-echo) pmax))) (throw 'found pos)))))) (defun eww-lnum (&optional filter dont-clear-p) "Make overlays that display link numbers. Return last used index. FILTER is filter string for anchor text. DONT-CLEAR-P determines whether previous numbering has to be cleared." (eww-lnum-set-numbering 'eww-lnum-next filter dont-clear-p)) (defmacro eww-lnum-prompt-str (num fun start def-anchor filter &optional show-num) "Construct a prompt string for function `eww-lnum-read-interactive'. NUM is a number variable for currently to be selected element. FUN is a function to be called with NUM as argument. START is a string to start the prompt. DEF-ANCHOR is info for the default 0 element. FILTER is current string used for filtering. SHOW-NUM if specified replaces NUM." `(let ((anchor (funcall ,fun ,num)) (show-num ,show-num)) (setq anchor (if anchor (concat " [" anchor "]") (setq ,num 0 show-num "") ,def-anchor)) (concat ,start (or show-num (propertize (number-to-string ,num) 'face 'minibuffer-prompt)) " " ,filter anchor))) (defun eww-lnum-read-interactive (prompt fun last-index &optional def-anchor filter def-num) "Interactively read a valid integer from minubuffer with PROMPT. Execute a one argument function FUN with every current valid integer. DEF-ANCHOR is initial element to print. FILTER is the initial aplied filter. DEF-NUM is the initial selected element, 1 if not given. Use to submit current selection; for correction; or to quit action; and for scrolling page. Entering 0 may choose default anchor without . Every other character is appended to a filtering string. + is appended to the filtering string as . If `eww-lnum-quick-browse' is non-nil, choose without on single possible selection. Return list of selected number and last applied filter." (setq def-anchor (if def-anchor (concat " [" def-anchor "]") "") prompt (propertize prompt 'face 'minibuffer-prompt)) (let ((filter (or filter "")) (auto-num (or (null def-num) (= def-num 0))) ch) (let ((num (if auto-num 1 def-num))) (catch 'select (let ((temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter (if auto-num "")))) (while (not (memq ; while not return or escape (setq ch (read-event temp-prompt t)) '(return 10 13 ?\n ?\r ?\C-g escape 27 ?\e))) (cond ((memq ch '(backspace 8 127 ?\C-h)) (if auto-num (or (string-equal filter "") ; delete last filter character (setq num 1 filter (substring-no-properties filter 0 (1- (length filter))) last-index (eww-lnum filter) temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter ""))) (setq num (/ num 10)) ; delete last digit (if (zerop num) (setq num 1 auto-num t)) (setq temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter (if auto-num ""))))) ;; scroll options ((memq ch '(32 ?\ )) ; scroll down (eww-lnum-remove-overlays) (ignore-errors (scroll-up) ;; scroll-up sets wrongly window-start/end (redisplay)) (setq last-index (eww-lnum filter t)) (if (zerop last-index) ; filter left nothing (let* ((pmax (point-max)) (pos (eww-lnum-next-filter ;search below 'eww-lnum-next filter (min (window-end) pmax) pmax))) (when pos (goto-char pos) (redisplay) (setq last-index (eww-lnum filter t))))) (setq num (if (zerop last-index) 0 1) auto-num t temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter ""))) ((eq ch 'delete) ; scroll up (eww-lnum-remove-overlays) (scroll-down) (redisplay) (setq last-index (eww-lnum filter t)) (if (zerop last-index) ; filter left nothing (let ((pos (eww-lnum-next-filter ;search above 'eww-lnum-next filter (point-min) (window-start)))) (when pos (goto-char pos) (redisplay) (setq last-index (eww-lnum filter t))))) (setq num (if (zerop last-index) 0 1) auto-num t temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter ""))) ;; iteration options ((memq ch '(left up)) (setq num (if (> num 1) (1- num) last-index) auto-num t temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter ""))) ((memq ch '(right down)) (setq num (if (< num last-index) (1+ num) 1) auto-num t temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter ""))) ((and (numberp ch) (< 47 ch) (< ch 58)) (if auto-num (if (= ch 48) (throw 'select (setq num 0)) (setq num (- ch 48) auto-num nil)) (setq num (+ (* num 10) ch -48))) (if (> num last-index) (if (zerop (setq num (/ num 10))) (setq num 1 auto-num t)) (and (memq eww-lnum-quick-browsing '(quick-all quick-numbers)) (> (* num 10) last-index) (throw 'select num))) (setq temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter (if auto-num "")))) (t (setq ch (string (cond ;append filter character ((= ch 67108896) 32) ;+SPACE ((and (< 67108911 ch) ;treat +DIGIT (< ch 67108922)) (- ch 67108864)) ; as DIGIT (t ch))) filter (concat filter ch) last-index (eww-lnum filter)) (if (and (= last-index 1) (memq eww-lnum-quick-browsing '(quick-all quick-filter))) (throw 'select (setq num 1)) (when (zerop last-index) ; filter left nothing (let* ((pmax (point-max)) (pos (or (eww-lnum-next-filter ;search below 'eww-lnum-next filter (min (window-end) pmax) pmax) (eww-lnum-next-filter ;search above 'eww-lnum-next filter (point-min) (window-start))))) (when pos (goto-char pos) (redisplay) (setq last-index (eww-lnum filter t)))) (if (zerop last-index) ; search found nothing, remove new char (setq filter (substring-no-properties filter 0 (1- (length filter))) last-index (eww-lnum filter t)))) (setq num 1 auto-num t temp-prompt (eww-lnum-prompt-str num fun prompt def-anchor filter ""))))))) (if (memq ch '(?\C-g escape 27 ?\e)) (keyboard-quit))) (list num filter)))) (defmacro eww-with-lnum (filter &rest body) "Within TYPE anchor numbering with FILTER execute BODY. Otherwise activate numbering, then clear numbering overlays. Within BODY, `last-index' is bound to the last used index number." `(let ((original-mode-line-format mode-line-format) (buffer (current-buffer))) (unwind-protect (progn (setq mode-line-format "RET: select | BACKSPACE: correction | \ chars, C-digit, C-SPACE: add chars, digits or space to string \ filter | arrows: move selection | SPACE,DEL: scroll | \ ESC, C-g: quit") (force-mode-line-update) (let ((last-index (eww-lnum ,filter))) ,@body)) (with-current-buffer buffer (setq mode-line-format original-mode-line-format) (eww-lnum-remove-overlays (point-min) (point-max)))))) (defun eww-lnum-get-point-info (position) "Get `help-echo' property for POSITION." (or (get-text-property position 'eww-form) (get-text-property position 'help-echo))) (defun eww-lnum-highlight-anchor (arg) "Highlight specified by ARG number anchor. Return text description." (let (marked-label) (dolist (overlay (overlays-in (max (1- (window-start)) (point-min)) (min (window-end) (point-max)))) (cond ((overlay-get overlay 'eww-lnum-match) (delete-overlay overlay)) ((eq arg (overlay-get overlay 'eww-lnum-overlay)) (let* ((start (overlay-start overlay)) (match-overlay (make-overlay start (next-single-property-change start 'help-echo)))) (overlay-put match-overlay 'eww-lnum-match t) (overlay-put match-overlay 'face 'match) (or marked-label (let ((info (eww-lnum-get-point-info start))) (setq marked-label (if (stringp info) info (or (get-text-property start 'help-echo) (buffer-substring-no-properties start (next-single-property-change start 'help-echo))))))))))) marked-label)) (defmacro eww-lnum-get-match-info (condition found-tag) "For the first overlay matching CONDITION throw through FOUND-TAG \ anchor info." `(dolist (overlay (overlays-in (max (1- (window-start)) (point-min)) (min (window-end) (point-max)))) (if ,condition (let ((pos (overlay-start overlay))) (throw ,found-tag (list (eww-lnum-get-point-info pos) pos)))))) (defun eww-lnum-get-anchor-info (&optional num) "Get info (url/action position image image-alt) of anchor numbered as NUM. If NUM is not specified, use currently highlighted anchor." (catch 'found (if num (eww-lnum-get-match-info (eq num (overlay-get overlay 'eww-lnum-overlay)) 'found) (eww-lnum-get-match-info (overlay-get overlay 'eww-lnum-match) 'found)))) (defun eww-lnum-get-action (&optional prompt) "Turn on link numbers and return list of url or action, position of PROMPT" (eww-with-lnum "" (let ((current-url (eww-lnum-current-url))) (if (zerop last-index) (if (y-or-n-p (concat "No items found. Select default? [" current-url "] ")) (list current-url 0) (keyboard-quit)) (let ((num (car (eww-lnum-read-interactive (or prompt "Anchor number: ") 'eww-lnum-highlight-anchor last-index current-url)))) (if (zerop num) (list current-url 0) (eww-lnum-get-anchor-info num))))))) (defun eww-lnum-browse-url (url &optional new-session) "Browse URL in NEW-SESSION. Visit in background if NEW-SESSION is :background." (if new-session (let ((new-buffer "*eww*") (num 0)) (while (get-buffer new-buffer) (setq num (1+ num) new-buffer (format "*eww*<%d>" num))) (if (eq new-session :background) (with-current-buffer (get-buffer-create new-buffer) (eww-mode) (eww-browse-url url)) (switch-to-buffer new-buffer) (eww-mode) (eww-browse-url url))) (eww-browse-url url))) (defun eww-lnum-visit (info &optional new-session edit) "Visit url determined with selection INFO. Optionally visit in NEW-SESSION, in background if equal to :background. If EDIT, edit url before visiting." (if (or new-session edit) (eww-lnum-browse-url (if edit (read-string "Visit url: " (car info)) (car info)) new-session) (goto-char (cadr info)) (eww-follow-link))) (defun eww-lnum-activate-form (info) "Choose appropriate action for form specified by INFO." (push-mark (point)) (goto-char (cadr info)) (let ((type (plist-get (car info) :type))) (cond ((or (string-equal type "checkbox") (string-equal type "radio")) (eww-toggle-checkbox)) ((string-equal (get-text-property (cadr info) 'help-echo) "select field") (eww-change-select nil)) ((or (string-equal type "submit") (eq (get-text-property (cadr info) 'face) 'eww-form-submit)) (eww-submit))))) ;;;###autoload (defun eww-lnum-follow (arg) "Turn on link numbers, ask for one and execute appropriate action on it. If link - visit it; button - press; input - move to it. With prefix ARG visit link in new session. With `-' prefix ARG, visit in background. With double prefix ARG, prompt for url to visit. With triple prefix ARG, prompt for url and visit in new session." (interactive "p") (let* ((edit (or (or (< arg -1) (<= 16 arg)))) (new-buffer (or (= arg 4) (< 16 arg))) (background (< arg 0)) (info (eww-lnum-get-action (format "%sollow%s%s: " (if edit "Edit and f" "F") (if new-buffer " in new buffer" "") (if background " in background" ""))))) (cond ((null info) (message "No valid anchor selected")) ((stringp (car info)) ; link (eww-lnum-visit info (if background :background new-buffer) edit)) (t (eww-lnum-activate-form info))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; universal (defmacro eww-lnum-make-action (text cmd) "Return a TEXT propertized as a link that invokes CMD when clicked." `(propertize ,text 'action ,cmd 'mouse-face 'highlight)) (defun eww-lnum-universal-dispatch (info label action-alist) "Print available options for determined by INFO element." (let ((action-alist (append action-alist eww-lnum-actions-general)) char selection-made) (save-window-excursion (let ((original-mode-line-format mode-line-format)) (unwind-protect (let ((selection-buffer (get-buffer-create "*eww action selection*"))) (set-buffer selection-buffer) (setq mode-line-format "RET, left click: select | \ ,TAB/,BACKTAB: move to next/previous action" buffer-read-only nil) (force-mode-line-update) (mapc (lambda (option) (if (consp option) (insert (eww-lnum-make-action (concat "[ " (char-to-string (car option)) " ] " (nth 2 option)) (cadr option)) "\n") (insert option "\n"))) action-alist) (insert (eww-lnum-make-action "[Backspace] Back to selection" (lambda (_info) 'restart-selection)) "\n") (insert (eww-lnum-make-action "[ ESC ] Quit" (lambda (_info) nil))) (setq buffer-read-only t) (goto-char (point-min)) (while (not (get-text-property (point) 'action)) (forward-line)) ; go over first action (pop-to-buffer selection-buffer) (setq char (read-event (concat (propertize "Select action: " 'face 'minibuffer-prompt) "[" label "]") t)) (while (and (not selection-made) (or (consp char) (memq char '(up down tab backtab return 10 13 ?\n ?\r)))) (if (consp char) ; mouse click?! (progn (mouse-set-point char) ; move to mouse point (let ((action (get-text-property (point) 'action))) (if action (setq selection-made action)))) (cond ((memq char '(up backtab)) ; move to previous action (when (/= (forward-line -1) 0) (goto-char (point-max)) ; ...or start from bottom (beginning-of-line)) (while (and (not (get-text-property (point) 'action)) (= (forward-line -1) 0)))) ((memq char '(down tab)) ; move to next action (forward-line) (if (= (point) (point-max)) (goto-char (point-min))) ; or move to top (while (and (not (get-text-property (point) 'action)) (/= (point) (point-max))) (forward-line))) ((memq char '(return 10 13 ?\n ?\r)) ; select (let ((action (get-text-property (point) 'action))) (if action (setq selection-made action)))))) (unless selection-made (setq char (read-event (concat (propertize "Select action: " 'face 'minibuffer-prompt) "[" label "]") t))))) (setq mode-line-format original-mode-line-format) (kill-buffer (current-buffer))))) (unless (memq char '(?\C-g escape 27 ?\e)) (cond (selection-made (funcall selection-made info)) ((memq char '(backspace 8 ?\C-h)) 'restart-selection) (t (let ((dispatch (assoc-default char action-alist 'eq))) (if dispatch (funcall (car dispatch) info) (message "Invalid selection")))))))) ;;;###autoload (defun eww-lnum-universal () "" (interactive) (let* ((filter "") (current-url (eww-lnum-current-url)) (label current-url) num) (while (eq 'restart-selection (eww-with-lnum filter (let ((info (if (zerop last-index) (list current-url 0) (let ((selection (eww-lnum-read-interactive "Anchor number: " 'eww-lnum-highlight-anchor last-index current-url filter (if (eq num 1) nil num)))) (setq num (car selection) filter (cadr selection)) (if (zerop num) (progn (setq label current-url) (list current-url 0)) (setq label (eww-lnum-highlight-anchor num)) (eww-lnum-get-anchor-info num)))))) (if info (let ((action (car info))) (if (stringp action) ; url (eww-lnum-universal-dispatch info label eww-lnum-actions-link-alist) (let ((type (plist-get action :type))) (if (or (string-equal type "checkbox") (string-equal type "radio") (string-equal type "submit") (eq (get-text-property (cadr info) 'face) 'eww-form-submit) (string-equal (get-text-property (cadr info) 'help-echo) "select field")) (eww-lnum-universal-dispatch ; button info label eww-lnum-actions-button-alist) (eww-lnum-universal-dispatch ; text info label nil))))) (message "No valid anchor selected")))))))) ;;; add link action for generic browser (if browse-url-generic-program (setq eww-lnum-actions-link-alist (append eww-lnum-actions-link-alist `((?m (lambda (info) (browse-url-generic (car info))) ,(concat "Open with " browse-url-generic-program)))))) ;;; add link action for curl if present (if (executable-find "curl") (setq eww-lnum-actions-link-alist (append eww-lnum-actions-link-alist '((?D (lambda (info) (let ((olddir default-directory)) (cd (read-directory-name "Save to: " eww-download-directory nil t)) (shell-command (concat "curl -k -O '" (car info) "' &") "*Curl*") (cd olddir))) "Download with Curl"))))) (add-hook 'eww-mode-hook (lambda () (face-remap-add-relative 'default '(:foreground "#b19693")))) (defun my/browse-url-firefox () "Open URL in Firefox from article, clipboard, or prompt." (interactive) (start-process "firefox" nil "distrobox" "enter" "-n" "debian" "-e" "firefox" (eww-current-url))) (define-key eww-mode-map "f" 'eww-lnum-follow) (define-key eww-mode-map "F" 'eww-lnum-universal) (define-key eww-mode-map "o" 'my/browse-url-firefox) ) ; end with-eval-after-load 'eww (provide '11-eww-lnum) ;;; 11-eww-lnum.el ends here ;;; 11-window-numbering.el --- Window numbering mode -*- lexical-binding: t -*- ;;; Commentary: ;; Numbered window shortcuts for quick window navigation ;; Source: https://github.com/nschum/window-numbering.el ;;; Code: (push "^No window numbered .$" debug-ignored-errors) (defgroup window-numbering nil "Numbered window shortcuts" :group 'convenience) (defcustom window-numbering-auto-assign-0-to-minibuffer t "`window-numbering-mode' assigns 0 to the minibuffer if active." :group 'window-numbering :type '(choice (const :tag "Off" nil) (const :tag "On" t))) (defcustom window-numbering-before-hook nil "*Hook called before `window-numbering-mode'" :group 'window-numbering :type 'hook) (defcustom window-numbering-assign-func nil "*Function called for each window" :group 'window-numbering :type 'function) (defconst window-numbering-mode-line-position 1 "position in the mode-line `window-numbering-mode' displays.") (defface window-numbering-face '() "Face used for the number in the mode-line." :group 'window-numbering) (defvar window-numbering-table nil "table -> (window vector . number table)") (defun select-window-by-number (i &optional arg) (interactive "P") (let ((windows (car (gethash (selected-frame) window-numbering-table))) window) (if (and (>= i 0) (< i 10) (setq window (aref windows i))) (if arg (delete-window window) (select-window window)) (error "No window numbered %s" i)))) ;; define interactive functions for keymap (dotimes (i 9) (eval `(defun ,(intern (format "select-window-%s" i)) (&optional arg) ,(format "Select the window with number %i." i) (interactive "P") (select-window-by-number ,i arg)))) (defun window-numbering-calculate-left (windows) (let ((i 9) left) (while (>= i 0) (let ((window (aref windows i))) (unless window (push (% (1+ i) 10) left))) (cl-decf i)) left)) (defvar window-numbering-windows nil "A vector listing the window for each number.") (defvar window-numbering-numbers nil "A hash map containing each window's number.") (defvar window-numbering-left nil "A list of unused window numbers.") (defun window-numbering-assign (window &optional number) (if number (if (aref window-numbering-windows number) (progn (message "%s assigned to two buffers (%s and %s)" number window (aref window-numbering-windows number)) nil) (setf (aref window-numbering-windows number) window) (puthash window number window-numbering-numbers) (setq window-numbering-left (delq number window-numbering-left)) t) ;; else default adding (when window-numbering-left (unless (gethash window window-numbering-numbers) (let ((number (car window-numbering-left))) (window-numbering-assign window number) number))))) (defun window-numbering-update () "Update the window numbering for the current frame." (setq window-numbering-windows (make-vector 10 nil) window-numbering-numbers (make-hash-table :size 10) window-numbering-left (window-numbering-calculate-left window-numbering-windows)) (puthash (selected-frame) (cons window-numbering-windows window-numbering-numbers) window-numbering-table) (when (and window-numbering-auto-assign-0-to-minibuffer (active-minibuffer-window)) (window-numbering-assign (active-minibuffer-window) 0)) (let ((windows (window-list nil 0 (frame-first-window)))) (run-hook-with-args 'window-numbering-before-hook windows) (when window-numbering-assign-func (mapc (lambda (window) (with-selected-window window (with-current-buffer (window-buffer window) (let ((num (funcall window-numbering-assign-func)) ) (when num (window-numbering-assign window num)))))) windows)) (dolist (window windows) (window-numbering-assign window)))) (defun window-numbering-get-number-string (&optional window) (let ((s (int-to-string (window-numbering-get-number window)))) (propertize s 'face 'window-numbering-face))) (defun window-numbering-get-number (&optional window) (gethash (or window (selected-window)) (cdr (gethash (selected-frame) window-numbering-table)))) (defvar window-numbering-keymap (let ((map (make-sparse-keymap))) (define-key map "\C-c0" 'select-window-0) (define-key map "\C-c1" 'select-window-1) (define-key map "\C-c2" 'select-window-2) (define-key map "\C-c3" 'select-window-3) (define-key map "\C-c4" 'select-window-4) (define-key map "\C-c5" 'select-window-5) (define-key map "\C-c6" 'select-window-6) (define-key map "\C-c7" 'select-window-7) (define-key map "\C-c8" 'select-window-8) map) "Keymap used in by `window-numbering-mode'.") ;;;###autoload (define-minor-mode window-numbering-mode "A minor mode that assigns a number to each window." :init-value nil :lighter nil :keymap window-numbering-keymap :global t (if window-numbering-mode (unless window-numbering-table (save-excursion (setq window-numbering-table (make-hash-table :size 16)) (window-numbering-install-mode-line) (add-hook 'minibuffer-setup-hook 'window-numbering-update) (add-hook 'window-configuration-change-hook 'window-numbering-update) (dolist (frame (frame-list)) (select-frame frame) (window-numbering-update)))) (window-numbering-clear-mode-line) (remove-hook 'minibuffer-setup-hook 'window-numbering-update) (remove-hook 'window-configuration-change-hook 'window-numbering-update) (setq window-numbering-table nil))) (defun window-numbering-install-mode-line (&optional position) "Install window number `window-numbering-mode' the mode-line." (let ((mode-line (default-value 'mode-line-format)) (res)) (dotimes (_ (min (or position window-numbering-mode-line-position) (length mode-line))) (push (car mode-line) res) (pop mode-line)) (push '(:eval (window-numbering-get-number-string)) res) (while mode-line (push (car mode-line) res) (pop mode-line)) (setq-default mode-line-format (nreverse res))) (force-mode-line-update t)) (defun window-numbering-clear-mode-line () "Remove window number of `window-numbering-mode' from mode-line." (let ((mode-line (default-value 'mode-line-format)) (res)) (while mode-line (let ((item (car mode-line))) (unless (equal item '(:eval (window-numbering-get-number-string)) ) (push item res))) (pop mode-line)) (setq-default mode-line-format (nreverse res))) (force-mode-line-update t)) (window-numbering-mode t) (provide '11-window-numbering) ;;; 11-window-numbering.el ends here ;; -*- lexical-binding: t -*- ;; (defun my-custom-proxy-locator (urlobj host) ;; (cond ;; ((string-match-p "\\.internal$" host) "DIRECT") ;; ((string-match-p "^10\\.11\\.12\\.[0-9]+$" host) "DIRECT") ;; (t "PROXY localhost:7890"))) ;; (setq url-proxy-locator #'my-custom-proxy-locator) (when +if-cn+ (setq url-proxy-services (append '(("http" . "localhost:7890") ("https" . "localhost:7890")) (mapcar (lambda (_) (cons "no_proxy" _)) (append (when (boundp '*gpt-list*) (cl-remove-duplicates (mapcar #'car *gpt-list*) :test #'string=)) '("smtp.zoho.com.cn" "imap.zoho.com.cn")))))) ;; (defun my/url-proxy-locator (url) ;; (let ((host (url-host (url-generic-parse-url url)))) ;; (if (and host ;; (string-match ;; "^10\\.11\\.12\\.[0-9]+$" ;; host)) ;; nil ;; url-proxy-services))) ;; (setq url-proxy-locator #'my/url-proxy-locator) ;; -*- lexical-binding: t -*- (unless (version< emacs-version "30") (fido-vertical-mode 1) (setq completion-styles '(basic partial-completion substring initials flex)) (setq read-file-name-completion-ignore-case t read-buffer-completion-ignore-case t) (global-completion-preview-mode 1) ) ;;; 14-template.el --- Language-agnostic project templates -*- lexical-binding: t -*- ;;; Commentary: ;; Quick project scaffolding based on file extension detection ;; Works without language-specific major modes ;; Provides smart compile commands based on file type ;;; Code: (require 'cl-lib) (defconst +template-dir+ "~/.emacs.d/templates/" "Directory containing project templates.") (defvar *last-shell-command* nil "Last shell command executed with C-c #.") (defvar *last-async-shell-command* nil "Last async shell command executed with C-c $.") (defconst +language-extensions+ '(("lisp" . ("\\.lisp$" "\\.asd$" "\\.cl$" "\\.al$")) ("python" . ("\\.py$")) ("c" . ("\\.c$" "\\.h$")) ("cpp" . ("\\.cpp$" "\\.hpp$" "\\.cc$" "\\.hh$")) ("rust" . ("\\.rs$")) ("go" . ("\\.go$")) ("javascript" . ("\\.js$" "\\.mjs$")) ("typescript" . ("\\.ts$")) ("dart" . ("\\.dart$")) ("java" . ("\\.java$")) ("shell" . ("\\.sh$" "\\.bash$" "\\.zsh$")) ("makefile" . ("^Makefile$" "\\.mk$"))) "Alist mapping language names to file extension patterns.") (defconst +language-compile-commands+ '(("c" . "gcc -Wall -g %f -o build/%n") ("cpp" . "g++ -Wall -Wextra -std=c++17 -g %f -o build/%n") ("rust" . "cargo build") ("go" . "go build %f") ("python" . "python3 %f") ("lisp" . "make b-test") ("javascript" . "node %f") ("typescript" . "tsc %f && node %n.js") ("dart" . "distrobox enter -n debian -e flutter build") ("java" . "javac %f && java %c") ("shell" . "bash %f") ("makefile" . "make")) "Alist mapping language to compile command templates. %f = full filename, %n = name without extension, %c = class name") (defconst +language-run-commands+ '(("c" . "./build/%n") ("cpp" . "./build/%n") ("rust" . "cargo run") ("go" . "./%n") ("python" . "python3 %f") ("lisp" . "make re-run") ("javascript" . "node %f") ("typescript" . "node %n.js") ("dart" . "distrobox enter -n debian -e flutter run") ("java" . "java %c") ("shell" . "bash %f") ("makefile" . "make run")) "Alist mapping language to run command templates.") (defun detect-language-by-extension (filename) "Detect programming language by FILENAME extension." (cl-block detect-language-by-extension (dolist (lang +language-extensions+) (dolist (pattern (cdr lang)) (when (string-match-p pattern filename) (cl-return-from detect-language-by-extension (car lang))))) nil)) (defun expand-command-template (template filename) "Expand TEMPLATE with FILENAME placeholders. %f = full filename %n = name without extension %c = class name (capitalized name without extension)" (let* ((name (file-name-nondirectory filename)) (name-no-ext (file-name-sans-extension name)) (class-name (concat (upcase (substring name-no-ext 0 1)) (substring name-no-ext 1)))) (replace-regexp-in-string "%c" class-name (replace-regexp-in-string "%n" name-no-ext (replace-regexp-in-string "%f" name template))))) (defun smart-compile-command () "Get smart compile command based on current file's language. Priority: 1) Last compile command if non-empty 2) Makefile if exists 3) Language-specific template" (cond ;; Priority 1: Use last compile command if non-empty ((and (boundp 'compile-command) compile-command (not (string-empty-p compile-command)) (not (string= compile-command "make -k"))) ; ignore default compile-command) ;; Priority 2: Use Makefile if exists ((file-exists-p "Makefile") "make") ;; Priority 3: Language-specific template ((and buffer-file-name (let* ((lang (detect-language-by-extension buffer-file-name)) (template (cdr (assoc lang +language-compile-commands+)))) (when template (expand-command-template template buffer-file-name))))) ;; Fallback (t "make -k"))) (defun smart-run-command () "Get smart run command based on current file's language. Priority: 1) Makefile if exists 2) Language-specific template" (cond ;; Priority 1: Use Makefile if exists ((file-exists-p "Makefile") "make run") ;; Priority 2: Language-specific template ((and buffer-file-name (let* ((lang (detect-language-by-extension buffer-file-name)) (template (cdr (assoc lang +language-run-commands+)))) (when template (expand-command-template template buffer-file-name))))) ;; Fallback (t nil))) (defun my-compile () "Smart compile with language-specific defaults." (interactive) (let ((default-cmd (or (smart-compile-command) compile-command))) (setq compile-command (read-string "Compile command: " default-cmd)) (compile compile-command))) (defun my-recompile () "Recompile without prompting." (interactive) (if (and (boundp 'compilation-last-buffer) compilation-last-buffer (buffer-live-p compilation-last-buffer)) (recompile) (call-interactively #'my-compile))) (defun my-shell-command () "Shell command with memory - recalls last command." (interactive) (let* ((default-cmd (or *last-shell-command* (smart-run-command) "")) (cmd (read-string "Shell command: " default-cmd))) (setq *last-shell-command* cmd) (shell-command cmd))) (defun my-async-shell-command () "Async shell command with memory - recalls last command." (interactive) (let* ((default-cmd (or *last-async-shell-command* (smart-run-command) "")) (cmd (read-string "Async shell command: " default-cmd))) (setq *last-async-shell-command* cmd) (async-shell-command cmd))) ;; Override default keybindings with smart versions (global-set-key (kbd "C-c !") #'my-compile) (global-set-key (kbd "C-c @") #'my-recompile) (global-set-key (kbd "C-c #") #'my-shell-command) (global-set-key (kbd "C-c $") #'my-async-shell-command) (defun detect-project-language (directory) "Detect primary language in DIRECTORY by counting file extensions." (let ((counts (make-hash-table :test 'equal))) (dolist (file (directory-files-recursively directory ".*" t)) (when-let ((lang (detect-language-by-extension file))) (puthash lang (1+ (gethash lang counts 0)) counts))) ;; Return language with most files (let ((max-lang nil) (max-count 0)) (maphash (lambda (lang count) (when (> count max-count) (setq max-lang lang max-count count))) counts) max-lang))) (defun replace-in-files (directory pattern old-string new-string) "Replace OLD-STRING with NEW-STRING in files matching PATTERN in DIRECTORY." (dolist (file (directory-files-recursively directory pattern)) (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (while (search-forward old-string nil t) (replace-match new-string nil t)) (write-file file)))) (defun init-project-from-template (template-name target-dir project-name) "Initialize project from TEMPLATE-NAME to TARGET-DIR with PROJECT-NAME. Copies template directory and replaces placeholder strings." (interactive (list (completing-read "Template: " '("lisp" "python" "c" "cpp" "rust" "go" "makefile") nil t) (read-directory-name "Target directory: ") (read-string "Project name: "))) (let* ((template-dir (expand-file-name template-name +template-dir+)) (target (file-name-as-directory target-dir))) (unless (file-directory-p template-dir) (error "Template directory not found: %s" template-dir)) (when (file-exists-p target) (error "Target directory already exists: %s" target)) ;; Copy template (copy-directory template-dir target t t t) ;; Replace placeholders (replace-in-files target ".*" "{{PROJECT_NAME}}" project-name) (replace-in-files target ".*" "{{PROJECT_NAME_UPPER}}" (upcase project-name)) (replace-in-files target ".*" "{{PROJECT_NAME_LOWER}}" (downcase project-name)) ;; Rename files containing placeholder (dolist (file (directory-files-recursively target ".*{{PROJECT_NAME}}.*")) (let ((new-name (replace-regexp-in-string "{{PROJECT_NAME}}" project-name file))) (rename-file file new-name))) ;; Clean up backup files (dolist (file (directory-files-recursively target ".*~$")) (delete-file file)) (message "Project initialized: %s" target))) (defun quick-makefile () "Insert a quick Makefile template based on detected language." (interactive) (let* ((lang (or (detect-project-language default-directory) (completing-read "Language: " '("c" "cpp" "rust" "go" "python" "lisp") nil t))) (template (pcase lang ("c" "CC = gcc CFLAGS = -Wall -Wextra -g TARGET = main SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) all: $(TARGET) $(TARGET): $(OBJS) \t$(CC) $(CFLAGS) -o $@ $^ %.o: %.c \t$(CC) $(CFLAGS) -c $< clean: \trm -f $(OBJS) $(TARGET) .PHONY: all clean ") ("cpp" "CXX = g++ CXXFLAGS = -Wall -Wextra -std=c++17 -g TARGET = main SRCS = $(wildcard *.cpp) OBJS = $(SRCS:.cpp=.o) all: $(TARGET) $(TARGET): $(OBJS) \t$(CXX) $(CXXFLAGS) -o $@ $^ %.o: %.cpp \t$(CXX) $(CXXFLAGS) -c $< clean: \trm -f $(OBJS) $(TARGET) .PHONY: all clean ") ("rust" "all: \tcargo build release: \tcargo build --release test: \tcargo test clean: \tcargo clean run: \tcargo run .PHONY: all release test clean run ") ("go" "BINARY = main SOURCES = $(wildcard *.go) all: $(BINARY) $(BINARY): $(SOURCES) \tgo build -o $@ $^ test: \tgo test ./... clean: \trm -f $(BINARY) run: $(BINARY) \t./$(BINARY) .PHONY: all test clean run ") ("python" "PYTHON = python3 VENV = venv all: $(VENV) $(VENV): \t$(PYTHON) -m venv $(VENV) \t./$(VENV)/bin/pip install -r requirements.txt test: \t./$(VENV)/bin/pytest rt: test clean: \trm -rf $(VENV) __pycache__ .pytest_cache run: \t./$(VENV)/bin/python main.py .PHONY: all test rt clean run ") ("lisp" "LISP = /usr/local/bin/ros SYSTEM = $(shell basename $(CURDIR)) all: build build: \t$(LISP) -e '(ql:quickload :$(SYSTEM))' -q b-test: build test test: \t$(LISP) -e '(ql:quickload :$(SYSTEM)/test)' -e '(uiop:quit)' rt: test re-run: build \t$(LISP) -e '(ql:quickload :$(SYSTEM))' -e '(main)' -q clean: \tfind . -name '*.fasl' -delete run: re-run .PHONY: all build b-test test rt re-run clean run ") (_ "# Makefile\n\nall:\n\t@echo \"No template for this language\"\n")))) (insert template) (message "Inserted %s Makefile template" lang))) (defun quick-gitignore () "Insert a quick .gitignore template based on detected language." (interactive) (let* ((lang (or (detect-project-language default-directory) (completing-read "Language: " '("c" "cpp" "rust" "go" "python" "lisp") nil t))) (template (pcase lang ("c" "*.o\n*.out\n*.exe\nmain\nbuild/\n") ("cpp" "*.o\n*.out\n*.exe\nmain\nbuild/\n") ("rust" "target/\nCargo.lock\n") ("go" "*.exe\n*.test\n*.out\n") ("python" "__pycache__/\n*.pyc\n*.pyo\nvenv/\n.pytest_cache/\n") ("lisp" "*.fasl\n*.fas\n*.lib\n*.x86f\n") (_ "# Add patterns here\n")))) (insert template) (message "Inserted %s .gitignore template" lang))) (provide '14-template) ;;; 14-template.el ends here ;;; golden.el --- Mark audited truth blocks in h3prompt org files -*- lexical-binding: t -*- ;;; Commentary: ;; Golden block markers for h3prompt audit workflow ;;; Code: (defvar h3prompt-golden-directory nil "Last used git directory for golden marks. Remembered across invocations.") (defvar h3prompt-golden-history nil "History list for golden directory prompt.") ;;;###autoload (defun h3prompt-golden-mark (beg end) "Wrap region or empty line with golden block markers. If a region is active, wrap it with >>>GOLDEN / <<>>GOLDEN commit=%s root=%s" commit root)) (close-marker "# << (length typo) 2) (push typo variants)))) ;; Remove duplicates and original word (setq variants (delete-dups (remove word variants))) ;; Batch check all variants against dictionary (more efficient) (let ((correct-words (make-hash-table :test 'equal))) ;; Build hash table of correct words for O(1) lookup (dolist (variant variants) (when (member variant (ispell-lookup-words variant)) (puthash variant t correct-words))) ;; Filter out correct words (seq-filter (lambda (variant) (not (gethash variant correct-words))) variants)))) (defun my/add-multiple-typos () "Spell-check word at point, then auto-generate and add typo variants. 1. Runs spell-check to get correct word 2. Generates common typo variants (filtered) 3. Adds original typo + generated variants as abbrevs" (interactive) (let ((typo-at-point (thing-at-point 'word t))) (unless typo-at-point (user-error "No word at point")) ;; Run spell-check to get correction (save-excursion (ispell-word)) (let ((correct (thing-at-point 'word t))) ;; Check if word was actually corrected (when (string= typo-at-point correct) (user-error "Word '%s' is already correct or unchanged" correct)) ;; Generate and filter typo variants (let* ((auto-typos (my/generate-typo-variants correct)) (all-typos (cons typo-at-point auto-typos)) (typos-str (read-string (format "Typos for '%s' (edit if needed): " correct) (mapconcat 'identity all-typos " "))) (typos (split-string typos-str)) (count 0)) ;; Add all typos as abbrevs (dolist (typo typos) (when (and (> (length typo) 1) (not (string= typo correct))) (define-global-abbrev typo correct) (setq count (1+ count)))) ;; Save and report (when (my/save-abbrev-file) (message "✓ Added %d typo(s) for '%s'" count correct)))))) (global-set-key (kbd "C-#") #'my/add-multiple-typos) ;; Configure ispell to use hunspell (system spell-checker) (setq ispell-program-name "hunspell") (provide '19-abbrev) ;;; 19-abbrev.el ends here ;;; 20-greek.el --- Greek letter input -*- lexical-binding: t -*- ;;; Commentary: ;; Quick Greek letter insertion via function keys ;; No external packages required ;;; Code: (defvar term-in-char-mode) (defvar term-raw-map) (defun h3--insert-or-send-text (text) "Insert TEXT normally, skip in terminal modes." (unless (or (eq major-mode 'eat-mode) (and (eq major-mode 'term-mode) (boundp 'term-in-char-mode) term-in-char-mode)) (insert text))) (defun h3--insert-greek-by-name (name capital-p) "Insert or send Greek NAME with CAPITAL-P case." (h3--insert-or-send-text (char-to-string (char-from-name (concat "GREEK " (if capital-p "CAPITAL" "SMALL") " LETTER " (upcase name)))))) (defun insert-greeks-soft () "Insert Greek λ/Λ or β/Β based on fuzzy input." (interactive) (let* ((candidates '(("small lambda") ("capital lambda") ("small beta") ("capital beta") ("format list") ("format if") ("format lowercase") ("catch functions"))) (idx (cl-position "capital beta" candidates :test #'string= :key #'car)) (prompt "Choose: ") (choice (completing-read prompt candidates nil t)) (ci (cl-position choice candidates :test #'string= :key #'car))) (if (<= ci idx) (h3--insert-or-send-text (char-to-string (char-from-name (concat "GREEK " (replace-regexp-in-string " " " LETTER " (upcase choice)))))) (let ((result (cl-case ci (4 "\"~{~a~^ ~}\"") (5 "\"~@[~a~]\"") (6 "\"~(~a~)\"") (7 (let ((names-hash (make-hash-table :test 'equal)) found-names) (save-excursion (goto-char (point-min)) (while (re-search-forward "^[ \t]*@f[ \t]+\\([^\n \t()]+\\)[ \t]*(" nil t) (let ((name (match-string-no-properties 1))) (unless (or (= (elt name 0) 37) (gethash name names-hash)) (puthash name t names-hash) (push name found-names))))) (setq found-names (nreverse found-names)) (if (derived-mode-p 'eat-mode 'term-mode) (mapconcat (lambda (name) (format "#:%s" name)) found-names "\n") (goto-char (point-max)) (unless (bobp) (newline)) (dolist (name found-names) (insert (format "#:%s\n" name))) (message "Inserted %d sharp-name(s): %s" (length found-names) (mapconcat #'identity found-names ", ")) nil)))))) (when (stringp result) (h3--insert-or-send-text result)))))) (defmacro gen-greek-input (a key) "Generate keybindings for Greek letter A on KEY. M-KEY inserts capital letter, C-KEY inserts small letter." `(progn (global-set-key (kbd (concat "M-" ,key)) (lambda () (interactive) (h3--insert-greek-by-name ,a t))) (global-set-key (kbd (concat "C-" ,key)) (lambda () (interactive) (h3--insert-greek-by-name ,a nil))) ;; Bind in term-raw-map for ansi-term (with-eval-after-load 'term (when (and (boundp 'term-raw-map) (keymapp term-raw-map)) (define-key term-raw-map (kbd (concat "M-" ,key)) (lambda () (interactive) (h3--insert-greek-by-name ,a t))) (define-key term-raw-map (kbd (concat "C-" ,key)) (lambda () (interactive) (h3--insert-greek-by-name ,a nil))))))) ;; Bind Greek letters to function keys ;; Note: Function keys may not work in terminal Emacs ;; Use C-c g as alternative for terminal (dolist (x '(("alpha" . "") ("beta" . "") ("chi" . "") ("delta" . "") ("epsilon" . "") ("eta" . "") ("gamma" . "") ("tau" . "") ("iota" . "") ("pi" . "") ("kappa" . "") ("lambda" . "") ("mu" . "") ("nu" . "") ("omicron" . "") ("phi" . "") ("psi" . "") ("rho" . "") ("sigma" . "") ("pi" . "") ("upsilon" . "") ("theta" . "") ("omega" . "") ("xi" . "") ("omega" . "") ("zeta" . ""))) (eval `(gen-greek-input ,(car x) ,(cdr x)))) ;; Terminal-friendly alternative: C-c g (defun my/insert-greek-letter () "Insert Greek letter via completion (works in terminal)." (interactive) (let* ((letters '(("a" . "alpha") ("b" . "beta") ("g" . "gamma") ("d" . "delta") ("l" . "lambda") ("m" . "mu") ("p" . "pi") ("s" . "sigma") ("t" . "theta") ("o" . "omega") ("f" . "phi"))) (key (read-char "Greek letter (a/b/g/d/l/m/p/s/t/o/f, uppercase for capital): ")) (letter-name (cdr (assoc (downcase (char-to-string key)) letters))) (is-capital (and (>= key ?A) (<= key ?Z)))) (when letter-name (h3--insert-or-send-text (char-to-string (char-from-name (concat "GREEK " (if is-capital "CAPITAL" "SMALL") " LETTER " (upcase letter-name)))))))) (global-set-key (kbd "C-c g") #'my/insert-greek-letter) ;; No need to bind in eat-mode-map or term-raw-map ;; These are configured via eat-semi-char-non-bound-keys and term keybindings (with-eval-after-load 'term (when (and (boundp 'term-raw-map) (keymapp term-raw-map)) (define-key term-raw-map (kbd "C-c g") #'my/insert-greek-letter))) (provide '20-greek) ;;; 20-greek.el ends here ;; -*- lexical-binding: t -*- ;; Suppress package load/install during batch byte-compilation (eval-when-compile (when noninteractive (require 'use-package-ensure nil t) (defvar use-package-ensure-function) (setq use-package-ensure-function #'ignore) (dolist (pkg '(vertico marginalia orderless consult corfu corfu-terminal cape gptel eat magit diff-hl yaml-mode systemd dockerfile-mode multiple-cursors markdown-mode)) (provide pkg)))) ;; completion used to be horizontal (declare-function vertico-mode "vertico") (declare-function marginalia-mode "marginalia") (declare-function global-corfu-mode "corfu") (declare-function corfu-history-mode "corfu") (declare-function corfu-popupinfo-mode "corfu") (declare-function corfu-terminal-mode "corfu-terminal") (declare-function cape-dabbrev "cape") (declare-function cape-file "cape") (declare-function cape-keyword "cape") (declare-function cape-elisp-symbol "cape") (declare-function cape-abbrev "cape") (use-package vertico :ensure t :init ;; Disable built-in completion UIs when using Vertico. ;; core/13-completion-no-package.el enables these for minimal mode. (when (fboundp 'fido-vertical-mode) (fido-vertical-mode -1)) (when (fboundp 'fido-mode) (fido-mode -1)) (when (fboundp 'icomplete-mode) (icomplete-mode -1)) (when (fboundp 'global-completion-preview-mode) (global-completion-preview-mode -1)) (vertico-mode)) ;; metadata for items in minibuffer (use-package marginalia :ensure t :after vertico :init (marginalia-mode) ) ;; non-liner matching and filter (use-package orderless :ensure t :config (setq completion-styles '(orderless basic))) ;; implement completion setting in ops (use-package consult :ensure t :bind (("C-x b" . consult-buffer) ("M-g g" . consult-goto-line) ) ) ;; buffer inline completion ui (use-package corfu :ensure t :custom (corfu-auto t) (corfu-auto-delay 0.05) ; faster trigger (was 0.2) (corfu-auto-prefix 1) ; complete after 1 char (was 2) (corfu-cycle t) (corfu-preselect 'prompt) ; preselect first candidate (corfu-preview-current 'insert) ; preview completion as you type (corfu-on-exact-match nil) ; don't auto-insert exact matches :init (global-corfu-mode 1) (corfu-history-mode 1) ; remember frequently used completions (corfu-popupinfo-mode 1) ; show documentation popup :config ;; Make popupinfo appear faster (setq corfu-popupinfo-delay '(0.3 . 0.1)) ) ;; for emacs in terminal (use-package corfu-terminal :ensure t :after corfu :init (corfu-terminal-mode +1) ) ;; multiple backends of different sources as completion endpoint (defmacro my/add-cape (hook &rest capfs) "Add CAPFS as completion backends for HOOK." `(add-hook ',hook (lambda () (setq-local completion-at-point-functions (list ,@capfs))))) (use-package cape :ensure t :init ;; Global defaults: prioritize faster backends first (add-to-list 'completion-at-point-functions #'cape-abbrev) (add-to-list 'completion-at-point-functions #'cape-dabbrev) (add-to-list 'completion-at-point-functions #'cape-file) :config ;; Improve dabbrev performance (setq cape-dabbrev-min-length 2) (setq cape-dabbrev-check-other-buffers t)) (my/add-cape makefile-mode-hook #'cape-dabbrev #'cape-file #'cape-keyword) (my/add-cape emacs-lisp-mode-hook #'cape-elisp-symbol #'cape-dabbrev #'cape-file #'cape-keyword) (provide '00-completion) ;;; 00-completion.el ends here ;;; gpt.el --- GPT/LLM integration -*- lexical-binding: t -*- ;;; Commentary: ;; Configure gptel for multiple LLM backends ;; Set *gpt-list* before loading this file: ;; (setq *gpt-list* '(("api.example.com" "key" openai "gpt-4"))) ;;; Code: (defvar gptel-backend) (defvar gptel-model) (declare-function gptel-backend-name "gptel") (declare-function gptel-send "gptel") (declare-function my/gpt-cycle "gpt") (declare-function eat-other-window "eat") (declare-function eat-term-send-string-as-yank "eat") (defvar *gpt-list* nil "List of GPT backends in format: ((HOST KEY TYPE . MODELS) ...) Example: ((\"api.openai.com\" \"sk-...\" openai \"gpt-4\" \"gpt-3.5-turbo\"))") (defvar *gptel-backends* nil "Cache of created backends.") (defun %gpt-make-backend (lst &optional backend-name) (pcase-let ((`(,host ,key ,type . ,models) lst)) (or (cl-find-if (lambda (b) (string= (gptel-backend-name b) backend-name)) *gptel-backends*) (car (push (funcall (intern (format "gptel-make-%s" type)) (or backend-name (cl-case type (openai "OpenAI") (anthropic "Claude") (deepseek "Deepseek") (t (format "%s-%s" type host)))) :host host :key key :stream t :models models) *gptel-backends*))))) (use-package gptel :ensure t :config ;; Default to first (when *gpt-list* (let ((first (car *gpt-list*))) (setq gptel-backend (%gpt-make-backend first "Claude-Polo") gptel-model (car (last first))))) ;; **Safe model lister** - uses your LST directly (defun my/gpt-list-models () (interactive) (let* ((current-name (gptel-backend-name gptel-backend)) (lst (cl-find-if (lambda (l) (string-match-p (car l) current-name)) *gpt-list*)) (models (when lst (cddr lst))) (model (completing-read (format "Model [%s]: " current-name) models nil t))) (setq gptel-model model) (message "Model: %s" model))) ;; Backend cycler (defun my/gpt-cycle () (interactive) (let* ((current-name (gptel-backend-name gptel-backend)) (idx (cl-position-if (lambda (l) (string= current-name (gptel-backend-name (%gpt-make-backend l)))) *gpt-list*)) (next-idx (mod (1+ (or idx -1)) (length *gpt-list*))) (next-lst (elt *gpt-list* next-idx))) (setq gptel-backend (%gpt-make-backend next-lst (format "%s-%s" (caddr next-lst) (car next-lst))) gptel-model (car (last next-lst))) (message "Switched: %s (%s)" (gptel-backend-name gptel-backend) gptel-model))) ;; Keys (global-set-key (kbd "C-c S") #'gptel-send) (global-set-key (kbd "C-c G") #'my/gpt-cycle) ) ;;; 02-git.el --- Git and project management -*- lexical-binding: t -*- ;;; Commentary: ;; Magit configuration and diff-hl for git integration ;;; Code: (defvar magit-status-mode-map) (defvar magit-auto-revert-mode) (defvar diff-hl-draw-borders) (declare-function magit-git-command-topdir "magit-process") (declare-function magit-unstaged-files "magit-git") (declare-function magit-push-current-to-upstream "magit-push") (declare-function transient-append-suffix "transient") (declare-function my-magit-commit "git") (declare-function my-magit-commit-with-msg "git") (declare-function my-pick "func") (declare-function transient-setup "transient") (unless (fboundp 'h3--safe-transient-append) (defun h3--safe-transient-append (prefix suffix spec) "Append SPEC to transient PREFIX at SUFFIX without aborting init on API drift." (when (fboundp 'transient-append-suffix) (condition-case err (transient-append-suffix prefix suffix spec) (error (message "Skipping transient patch for %s %s: %s" prefix suffix (error-message-string err))))))) (unless (fboundp 'my-magit-restore-popup) (defun my-magit-restore-popup () "Fallback restore dispatcher when transient menu is unavailable." (interactive) (if (fboundp 'transient-setup) (transient-setup 'my-magit-restore-popup) (message "Transient is unavailable; use direct restore commands instead.")))) (use-package magit :ensure t :bind ("C-x g" . magit-status) :config (setq magit-auto-revert-mode nil) (with-eval-after-load 'magit (when (boundp 'magit-status-mode-map) (define-key magit-status-mode-map (kbd "r") #'my-magit-restore-popup))) (defun my-magit-commit () (interactive) (magit-git-command-topdir "git add .") (magit-git-command-topdir "git diff-index --quiet HEAD") (magit-git-command-topdir "git commit -a -m 'magit auto commit'") ) (defun my-magit-commit-with-msg () (interactive) (magit-git-command-topdir (format "git commit -a -m '%s'" (read-string "commit msg: "))) ) (defun my-magit-push () (interactive) (my-magit-commit) (magit-push-current-to-upstream nil) ) (defun my-magit-push-with-msg () (interactive) (my-magit-commit-with-msg) (magit-push-current-to-upstream nil) ) ;; (transient-get-suffix 'magit-commit "--fuckyou") ;; (transient-get-suffix 'magit-commit "-a") ;; (transient-get-suffix 'magit-commit "x") ;; -a is the slot, first item fo the region (with-eval-after-load 'transient (h3--safe-transient-append 'magit-commit "-a" '("-foo" "commit all" "--foo")) (h3--safe-transient-append 'magit-commit "c" '("x" "stage all and commit with no msg" my-magit-commit)) (h3--safe-transient-append 'magit-commit "c" '("X" "stage all and commit with msg" my-magit-commit-with-msg)) (h3--safe-transient-append 'magit-push "-f" '("-bar" "commit all" "--bar")) (h3--safe-transient-append 'magit-push "p" '("x" "auto push with no msg" my-magit-push)) (h3--safe-transient-append 'magit-push "p" '("X" "auto push with msg" my-magit-push-with-msg))) (with-eval-after-load 'transient (eval '(transient-define-prefix my-magit-restore-popup () "My custom restore options." ["Restore Options" ("m" "Custom Message" "--help")] ["Restore Options" ("1" "single file" my-magit-restore-single-file) ("u" "all unstaged files" my-magit-restore-unstaged)]))) (defun my-magit-restore-single-file () (interactive) (let* ((cands (magit-unstaged-files)) (index (my-pick cands "single file to restore: ")) ) (when index (magit-git-command-topdir (format "git restore %s" (elt cands index)) ) )) ) (defun my-magit-restore-unstaged () (interactive) (let ((cands (magit-unstaged-files))) (mapc (lambda (file) (magit-git-command-topdir (format "git restore %s" file))) cands) )) ) (use-package diff-hl :ensure t :hook ((prog-mode . diff-hl-mode) (magit-post-refresh . diff-hl-magit-post-refresh)) :config (setq diff-hl-draw-borders nil)) (provide '02-git) ;;; 02-git.el ends here ;; -*- lexical-binding: t -*- (defvar dockerfile-mode-map) (defvar dockerfile-mode-command) (declare-function dockerfile-build-buffer "dockerfile-mode") (declare-function dockerfile-build-no-cache-buffer "dockerfile-mode") (defun dockerfile-set-image-name-from-dir () "Set `dockerfile-image-name' to the name of the current buffer's directory." (let* ((dir (file-name-directory (buffer-file-name))) (dirname (file-name-nondirectory (directory-file-name dir)))) (setq-local dockerfile-image-name (format "%s:latest" dirname)))) (defun h3--dockerfile-bind-keys () "Bind dockerfile helpers once dockerfile-mode keymap is available." (when (boundp 'dockerfile-mode-map) (define-key dockerfile-mode-map (kbd "C-c b") #'dockerfile-build-buffer) (define-key dockerfile-mode-map (kbd "C-c d") #'dockerfile-build-no-cache-buffer))) (use-package yaml-mode :ensure t :defer t ) (use-package systemd :ensure t :defer t :mode (("\\.service\\'" . systemd-mode) ("\\.timer\\'" . systemd-mode) ("\\.target\\'" . systemd-mode) ("\\.mount\\'" . systemd-mode) ("\\.automount\\'" . systemd-mode) ("\\.slice\\'" . systemd-mode) ("\\.socket\\'" . systemd-mode) ("\\.path\\'" . systemd-mode)) ) (use-package dockerfile-mode :ensure t :defer t :mode ("Dockerfile\\'" . dockerfile-mode) :hook (dockerfile-mode . dockerfile-set-image-name-from-dir) :config (setq dockerfile-mode-command "podman") (h3--dockerfile-bind-keys) ) (add-hook 'makefile-mode-hook (lambda () (let ((str "make ")) (setq-local compile-command str)))) ;; -*- lexical-binding: t -*- ;; (global-set-key (kbd "C-/") 'er/expand-region) (declare-function mc/mark-all-in-region-regexp "mc-mark-more") (declare-function mc/mark-previous-like-this "mc-mark-more") (declare-function mc/mark-next-like-this "mc-mark-more") (declare-function mc/skip-to-previous-like-this "mc-mark-more") (declare-function mc/skip-to-next-like-this "mc-mark-more") (use-package multiple-cursors :ensure t) (global-set-key (kbd "M-/") #'mc/mark-all-in-region-regexp) (global-set-key (kbd "M--") #'mc/mark-previous-like-this) (global-set-key (kbd "M-=") #'mc/mark-next-like-this) (global-set-key (kbd "M-_") #'mc/skip-to-previous-like-this) (global-set-key (kbd "M-+") #'mc/skip-to-next-like-this) ;; -*- lexical-binding: t -*- (defvar *code-dir* "~/code/" "Base directory for code repositories.") (defvar +personal-dir+ (let ((pdir (concat *code-dir* "h3vault/"))) (if (file-directory-p pdir) pdir)) "Path to personal configuration vault, if it exists.") (defvar h3-load-personal-config (not noninteractive) "When non-nil, load personal h3vault configuration.") (defun h3--load-personal-config () "Load personal init.el when available, without aborting startup on failures." (let ((init-file (and +personal-dir+ (concat +personal-dir+ "init.el")))) (when (and h3-load-personal-config init-file (file-exists-p init-file)) (condition-case err (progn (load init-file) (when (fboundp 'gnus-no-server) (gnus-no-server))) (error (message "Skipping personal config %s: %s" init-file (error-message-string err))))))) (h3--load-personal-config) (defun load-lang (lang) "Load language setup for LANG from *code-dir*/h3LANG/elisp/setup.el." (let ((ff (format "%sh3%s/elisp/setup.el" *code-dir* (symbol-name lang)))) (when (file-exists-p ff) (condition-case err (load ff) (error (message "Warning: Failed to load %s: %s" ff (error-message-string err))))))) (mapc #'load-lang '(cl c py flutter uml)) ;;; 08-eglot.el --- LSP via Eglot (Python only) -*- lexical-binding: t -*- ;;; Commentary: ;; Minimal LSP setup using built-in Eglot (Emacs 29+). ;; Currently enabled for Python only (requires pyright). ;; ;; To add more languages: ;; 1. Install the language server (e.g., npm install -g yaml-language-server) ;; 2. Add to :hook below (e.g., (yaml-mode . eglot-ensure)) ;; ;; See DEV.org for details. ;;; Code: (use-package eglot :ensure nil ; Built-in since Emacs 29 :defer t ; Lazy load - only when needed :hook (python-mode . eglot-ensure) ; Auto-start for Python files :custom (eglot-autoshutdown t) ; Shutdown server when last buffer closes (eglot-sync-connect nil) ; Don't block Emacs on connection (eglot-events-buffer-size 0) ; Disable event logging (performance) :config ;; Better performance for LSP communication (setq read-process-output-max (* 1024 1024))) ; 1MB ;;; 08-eglot.el ends here ;; -*- lexical-binding: t -*- (defvar eat-mode-map) (defvar eat-semi-char-mode-map) (defvar eat-terminal) (defvar eat--line-mode) (defvar h3-eat-enable-abbrev-expansion t) (defvar h3-eat-always-new-session t "When non-nil, `eat' and `eat-other-window' create a new session by default.") (defvar global-abbrev-table) (defvar-local h3--eat-abbrev-current-word "" "Current word typed in eat for abbrev expansion.") (declare-function eat-self-input "eat") (declare-function eat-term-send-string-as-yank "eat") (declare-function eat-term-parameter "eat") (declare-function eat-semi-char-mode "eat") (declare-function eat-line-mode "eat") (declare-function eat--1 "eat") (declare-function my-send-region-to-term "05-ansi-term") (declare-function h3--terminal-discard-key "05-ansi-term") (declare-function hl-line-unhighlight "hl-line") (declare-function abbrev-expansion "abbrev") (defun h3--eat-word-char-p (char) "Return non-nil when CHAR is part of an abbrev token." (or (and (>= char ?a) (<= char ?z)) (and (>= char ?A) (<= char ?Z)) (and (>= char ?0) (<= char ?9)) (memq char '(?_ ?-)))) (defun h3--eat-separator-char-p (char) "Return non-nil when CHAR should trigger abbrev expansion." (and (characterp char) (< char 128) (not (h3--eat-word-char-p char)))) (defun h3--eat-reset-abbrev-state () "Reset tracked word state for eat abbrev expansion." (setq h3--eat-abbrev-current-word "")) (defun h3--eat-expand-current-word () "Expand tracked eat word using `global-abbrev-table' when possible." (let* ((word h3--eat-abbrev-current-word) (expansion (and (> (length word) 0) (abbrev-expansion word global-abbrev-table)))) (when (and expansion (not (string= expansion word))) ;; Send backspaces as a single string to ensure they're processed together (let ((backspaces (make-string (length word) ?\C-?))) (process-send-string (eat-term-parameter eat-terminal 'eat--process) backspaces)) ;; Send the expansion via eat's yank function for proper display (eat-term-send-string-as-yank eat-terminal expansion)))) (defun h3--eat-send-backspace () "Send backspace and keep tracked eat abbrev state in sync." (interactive) (when (> (length h3--eat-abbrev-current-word) 0) (setq h3--eat-abbrev-current-word (substring h3--eat-abbrev-current-word 0 -1))) ;; Call the original function without triggering advice (let ((h3-eat-enable-abbrev-expansion nil)) (eat-self-input 1 ?\C-?))) (defun h3--eat-send-return () "Send return and reset tracked eat abbrev state." (interactive) (h3--eat-reset-abbrev-state) ;; Call the original function without triggering advice (let ((h3-eat-enable-abbrev-expansion nil)) (eat-self-input 1 ?\C-m))) (defun h3--eat-self-input-with-abbrev (orig-fun n c) "Send key to eat and expand global abbrevs on separators. ORIG-FUN is the original eat-self-input function." (if (and h3-eat-enable-abbrev-expansion (characterp c) (< c 128)) (cond ((h3--eat-word-char-p c) (setq h3--eat-abbrev-current-word (concat h3--eat-abbrev-current-word (char-to-string c))) (funcall orig-fun n c)) (t (when (h3--eat-separator-char-p c) (h3--eat-expand-current-word)) (h3--eat-reset-abbrev-state) (funcall orig-fun n c))) (funcall orig-fun n c))) (defun h3--eat-self-input-advice (orig-fun n c) "Around advice for `eat-self-input' to support abbrev expansion." (h3--eat-self-input-with-abbrev orig-fun n c)) (defun h3--eat-install-self-input-advice () "Install abbrev-aware advice for `eat-self-input' once." (unless (advice-member-p #'h3--eat-self-input-advice #'eat-self-input) (advice-add 'eat-self-input :around #'h3--eat-self-input-advice))) (defun h3--eat-toggle-mode () "Toggle between eat line-mode (edit) and semi-char-mode (terminal)." (interactive) (if (bound-and-true-p eat--line-mode) (progn (eat-semi-char-mode) ;; Restore terminal cursor behavior (kill-local-variable 'cursor-type) (kill-local-variable 'visible-cursor)) (progn (eat-line-mode) ;; Make cursor visible in line-mode (setq-local cursor-type 'box) (setq-local visible-cursor t) ;; Ensure hl-line is disabled (setq-local hl-line-range-function (lambda () nil)) (hl-line-mode -1) (hl-line-unhighlight)))) (defun h3--eat-bind-keys () "Bind eat keys only when keymaps are available." (when (and (boundp 'eat-mode-map) (keymapp eat-mode-map)) ;; Bind function keys and special keys (define-key eat-mode-map (kbd "C-c ") #'my-send-region-to-term) (define-key eat-mode-map (kbd "C-c ") #'h3--terminal-discard-key) (define-key eat-mode-map (kbd "C-c ") #'h3--terminal-discard-key) (define-key eat-mode-map [backspace] #'h3--eat-send-backspace) (define-key eat-mode-map (kbd "DEL") #'h3--eat-send-backspace) (define-key eat-mode-map [return] #'h3--eat-send-return) (define-key eat-mode-map (kbd "RET") #'h3--eat-send-return) ;; Toggle between line-mode (edit) and semi-char-mode (terminal) (define-key eat-mode-map (kbd "C-c C-t") #'h3--eat-toggle-mode))) (defun h3--eat-display-buffer-other-window (buffer) "Display eat BUFFER in another window." (pop-to-buffer buffer '((display-buffer-reuse-window display-buffer-pop-up-window) (inhibit-same-window . t) (reusable-frames . visible)))) (defun h3--eat-read-program-from-prefix (arg) "Read shell command when ARG requests custom program." (when (equal arg '(16)) (read-shell-command "Run program: " (or explicit-shell-file-name (getenv "ESHELL") shell-file-name)))) (defun h3-eat (&optional program arg) "Open eat session in another window." (interactive (let ((arg current-prefix-arg)) (list (h3--eat-read-program-from-prefix arg) arg))) (let ((session-arg (if (and h3-eat-always-new-session (null arg)) t arg))) (eat--1 program session-arg #'h3--eat-display-buffer-other-window))) (defun h3-eat-other-window (&optional program arg) "Open eat session in another window." (interactive (let ((arg current-prefix-arg)) (list (h3--eat-read-program-from-prefix arg) arg))) (h3-eat program arg)) (defun h3--eat-install-window-overrides () "Ensure `eat' commands always display in another window." (unless (advice-member-p #'h3-eat #'eat) (advice-add 'eat :override #'h3-eat)) (unless (advice-member-p #'h3-eat-other-window #'eat-other-window) (advice-add 'eat-other-window :override #'h3-eat-other-window))) (defun my/eat-disable-hl-line () "Disable hl-line highlighting in eat by making it invisible." (run-with-idle-timer 0.1 nil (lambda () (when (eq major-mode 'eat-mode) (setq-local hl-line-range-function (lambda () nil)) (hl-line-mode -1) (hl-line-unhighlight))))) (defun my/eat-line-mode-setup () "Ensure cursor is visible when entering eat line-mode." (when (bound-and-true-p eat--line-mode) ;; Make cursor visible in line-mode (setq-local cursor-type 'box) (setq-local visible-cursor t) ;; Ensure hl-line is disabled (setq-local hl-line-range-function (lambda () nil)) (hl-line-mode -1) (hl-line-unhighlight))) (use-package eat :ensure t :hook (eat-mode . my/eat-disable-hl-line) :config ;; Configure eat to pass through certain key sequences to Emacs (setq eat-enable-yank-to-terminal t) ;; Configure keys that should NOT be sent to terminal ;; These will be handled by Emacs instead (with-eval-after-load 'eat (when (boundp 'eat-semi-char-non-bound-keys) ;; Add C-c prefix keys that should invoke Emacs commands (dolist (key '([?\C-c ?t] ; my-toggle-terminal [?\C-c ?T] ; my-send-region-to-term [?\C-c ?g])) ; my/insert-greek-letter (add-to-list 'eat-semi-char-non-bound-keys key)))) (h3--eat-bind-keys) (h3--eat-install-window-overrides) (h3--eat-install-self-input-advice) ;; Add advice to make cursor visible in line-mode (advice-add 'eat-line-mode :after #'my/eat-line-mode-setup))