Move all *.el files to ./lisp
This commit is contained in:
587
lisp/bungee.el
Normal file
587
lisp/bungee.el
Normal file
@@ -0,0 +1,587 @@
|
||||
;;; bungee.el --- Fast symbol navigation with Elisp-based caching -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Bungee provides fast symbol navigation for C++ and QML files using
|
||||
;; an Elisp-based cache system. It can work standalone or with the
|
||||
;; Python symbol_finder.py for indexing.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'json)
|
||||
(require 'cl-lib)
|
||||
(require 'xref nil t)
|
||||
(require 'pulse nil t)
|
||||
(require 'grep nil t)
|
||||
|
||||
(defgroup bungee nil
|
||||
"Fast symbol navigation with caching."
|
||||
:group 'tools)
|
||||
|
||||
(defcustom bungee-cache-directory ".symbol_cache"
|
||||
"Directory for symbol cache files."
|
||||
:type 'string
|
||||
:group 'bungee)
|
||||
|
||||
(defcustom bungee-python-indexer nil
|
||||
"Path to Python indexer script (optional). If nil, use Elisp indexer."
|
||||
:type '(choice (const :tag "Use Elisp indexer" nil)
|
||||
(file :tag "Python script path"))
|
||||
:group 'bungee)
|
||||
|
||||
(defcustom bungee-auto-update t
|
||||
"Automatically update cache when files change."
|
||||
:type 'boolean
|
||||
:group 'bungee)
|
||||
|
||||
(defcustom bungee-save-json-cache nil
|
||||
"Also save cache in JSON format for Python compatibility."
|
||||
:type 'boolean
|
||||
:group 'bungee)
|
||||
|
||||
;; Cache data structures
|
||||
(defvar bungee--symbol-cache nil
|
||||
"In-memory symbol cache. Hash table mapping file paths to symbol lists.")
|
||||
|
||||
(defvar bungee--index-cache nil
|
||||
"In-memory index cache. Hash table mapping file paths to modification times.")
|
||||
|
||||
(defvar bungee--cache-loaded nil
|
||||
"Whether the cache has been loaded from disk.")
|
||||
|
||||
(cl-defstruct bungee-symbol
|
||||
"A symbol in the codebase."
|
||||
name ; Symbol name
|
||||
file-path ; Absolute file path
|
||||
line-number ; Line number (1-based)
|
||||
symbol-type ; 'class, 'function, 'property, etc.
|
||||
context) ; Line content for context
|
||||
|
||||
;; Cache management functions
|
||||
(defun bungee--cache-dir ()
|
||||
"Get the cache directory path."
|
||||
(let ((root (or (and (fboundp 'project-current)
|
||||
(project-current)
|
||||
(fboundp 'project-root)
|
||||
(ignore-errors
|
||||
(car (project-roots (project-current)))))
|
||||
default-directory)))
|
||||
(expand-file-name bungee-cache-directory root)))
|
||||
|
||||
(defun bungee--cache-file-path (filename)
|
||||
"Get path for cache FILENAME."
|
||||
(expand-file-name filename (bungee--cache-dir)))
|
||||
|
||||
(defun bungee--load-cache ()
|
||||
"Load cache from disk into memory."
|
||||
(let ((cache-file (bungee--cache-file-path "bungee-cache.el"))
|
||||
(json-symbols-file (bungee--cache-file-path "symbols.json"))
|
||||
(json-index-file (bungee--cache-file-path "index.json")))
|
||||
|
||||
;; Initialize hash tables
|
||||
(setq bungee--symbol-cache (make-hash-table :test 'equal))
|
||||
(setq bungee--index-cache (make-hash-table :test 'equal))
|
||||
|
||||
(cond
|
||||
;; Prefer Elisp cache if it exists
|
||||
((file-exists-p cache-file)
|
||||
(load cache-file nil t)
|
||||
;; Convert loaded lists back to symbol structs
|
||||
(let ((new-cache (make-hash-table :test 'equal)))
|
||||
(maphash (lambda (file-path symbol-lists)
|
||||
(puthash file-path
|
||||
(mapcar (lambda (s)
|
||||
(make-bungee-symbol
|
||||
:name (nth 0 s)
|
||||
:file-path (nth 1 s)
|
||||
:line-number (nth 2 s)
|
||||
:symbol-type (nth 3 s)
|
||||
:context (nth 4 s)))
|
||||
symbol-lists)
|
||||
new-cache))
|
||||
bungee--symbol-cache)
|
||||
(setq bungee--symbol-cache new-cache))
|
||||
(message "Loaded Elisp cache: %d files" (hash-table-count bungee--index-cache)))
|
||||
|
||||
;; Fall back to JSON if available
|
||||
((and (file-exists-p json-symbols-file) (file-exists-p json-index-file))
|
||||
(bungee--load-json-cache json-symbols-file json-index-file)
|
||||
(message "Loaded JSON cache: %d files" (hash-table-count bungee--index-cache)))
|
||||
|
||||
(t
|
||||
(message "No cache found. Run `bungee-index-directory' to create one.")))
|
||||
|
||||
(setq bungee--cache-loaded t)))
|
||||
|
||||
(defun bungee--load-json-cache (symbols-file index-file)
|
||||
"Load JSON cache from SYMBOLS-FILE and INDEX-FILE."
|
||||
;; Load symbols
|
||||
(when (file-exists-p symbols-file)
|
||||
(let* ((json-object-type 'hash-table)
|
||||
(json-array-type 'list)
|
||||
(json-key-type 'string)
|
||||
(symbols-data (json-read-file symbols-file)))
|
||||
(maphash (lambda (file-path symbols-list)
|
||||
(let ((symbols (mapcar (lambda (s)
|
||||
(make-bungee-symbol
|
||||
:name (gethash "name" s)
|
||||
:file-path (gethash "file_path" s)
|
||||
:line-number (gethash "line_number" s)
|
||||
:symbol-type (intern (gethash "symbol_type" s))
|
||||
:context (gethash "context" s)))
|
||||
symbols-list)))
|
||||
(puthash file-path symbols bungee--symbol-cache)))
|
||||
symbols-data)))
|
||||
|
||||
;; Load index
|
||||
(when (file-exists-p index-file)
|
||||
(let* ((json-object-type 'hash-table)
|
||||
(json-key-type 'string)
|
||||
(index-data (json-read-file index-file)))
|
||||
(maphash (lambda (file-path info)
|
||||
(puthash file-path (gethash "mtime" info) bungee--index-cache))
|
||||
index-data))))
|
||||
|
||||
(defun bungee--ensure-cache ()
|
||||
"Ensure cache is loaded."
|
||||
(unless bungee--cache-loaded
|
||||
(bungee--load-cache)))
|
||||
|
||||
(defun bungee--save-cache ()
|
||||
"Save in-memory cache to disk as Elisp code."
|
||||
(let ((cache-dir (bungee--cache-dir)))
|
||||
(unless (file-exists-p cache-dir)
|
||||
(make-directory cache-dir t))
|
||||
|
||||
;; Save as Elisp file for fast loading
|
||||
(with-temp-file (bungee--cache-file-path "bungee-cache.el")
|
||||
(insert ";;; bungee-cache.el --- Bungee symbol cache -*- lexical-binding: t; -*-\n")
|
||||
(insert ";;; This file is auto-generated. Do not edit.\n\n")
|
||||
|
||||
;; Save symbol cache
|
||||
(insert "(setq bungee--symbol-cache (make-hash-table :test 'equal))\n\n")
|
||||
(maphash (lambda (file-path symbols)
|
||||
(insert (format "(puthash %S\n '(" (abbreviate-file-name file-path)))
|
||||
(dolist (symbol symbols)
|
||||
(insert (format "\n %S"
|
||||
(list (bungee-symbol-name symbol)
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
(bungee-symbol-symbol-type symbol)
|
||||
(bungee-symbol-context symbol)))))
|
||||
(insert ")\n bungee--symbol-cache)\n\n"))
|
||||
bungee--symbol-cache)
|
||||
|
||||
;; Save index cache
|
||||
(insert "(setq bungee--index-cache (make-hash-table :test 'equal))\n\n")
|
||||
(maphash (lambda (file-path mtime)
|
||||
(insert (format "(puthash %S %S bungee--index-cache)\n"
|
||||
(abbreviate-file-name file-path) mtime)))
|
||||
bungee--index-cache)
|
||||
|
||||
(insert "\n;;; bungee-cache.el ends here\n"))
|
||||
|
||||
;; Optionally save as JSON for compatibility with Python tool
|
||||
(when bungee-save-json-cache
|
||||
(bungee--save-json-cache))))
|
||||
|
||||
(defun bungee--save-json-cache ()
|
||||
"Save cache in JSON format for Python compatibility."
|
||||
(let ((cache-dir (bungee--cache-dir)))
|
||||
;; Save symbols
|
||||
(let ((symbols-file (expand-file-name "symbols.json" cache-dir))
|
||||
(symbols-data (make-hash-table :test 'equal)))
|
||||
(maphash (lambda (file-path symbols)
|
||||
(puthash file-path
|
||||
(mapcar (lambda (s)
|
||||
`((name . ,(bungee-symbol-name s))
|
||||
(file_path . ,(bungee-symbol-file-path s))
|
||||
(line_number . ,(bungee-symbol-line-number s))
|
||||
(symbol_type . ,(symbol-name (bungee-symbol-symbol-type s)))
|
||||
(context . ,(bungee-symbol-context s))))
|
||||
symbols)
|
||||
symbols-data))
|
||||
bungee--symbol-cache)
|
||||
(with-temp-file symbols-file
|
||||
(insert (json-encode symbols-data))))
|
||||
|
||||
;; Save index
|
||||
(let ((index-file (expand-file-name "index.json" cache-dir))
|
||||
(index-data (make-hash-table :test 'equal)))
|
||||
(maphash (lambda (file-path mtime)
|
||||
(puthash file-path
|
||||
`((mtime . ,mtime)
|
||||
(symbol_count . ,(length (gethash file-path bungee--symbol-cache))))
|
||||
index-data))
|
||||
bungee--index-cache)
|
||||
(with-temp-file index-file
|
||||
(insert (json-encode index-data))))))
|
||||
|
||||
(defun bungee--file-cached-p (file-path)
|
||||
"Check if FILE-PATH is cached and up to date."
|
||||
(bungee--ensure-cache)
|
||||
(let ((cached-mtime (gethash file-path bungee--index-cache))
|
||||
(current-mtime (and (file-exists-p file-path)
|
||||
(float-time (nth 5 (file-attributes file-path))))))
|
||||
(and cached-mtime current-mtime (>= cached-mtime current-mtime))))
|
||||
|
||||
;; Elisp-based parsing
|
||||
(defun bungee--parse-qml-file (file-path)
|
||||
"Parse a QML file and extract symbols."
|
||||
(let ((symbols '())
|
||||
(case-fold-search nil))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file-path)
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Find QML types
|
||||
(save-excursion
|
||||
(while (re-search-forward "^\\s-*\\([A-Z]\\w*\\)\\s-*{" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'class
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))
|
||||
|
||||
;; Find properties (including readonly)
|
||||
;; Format: [readonly] property <type> <name>
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*\\(?:readonly\\s-+\\)?property\\s-+[\\w.<>]+\\s-+\\([a-zA-Z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'property
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find signals
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*signal\\s-+\\([a-zA-Z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'signal
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find functions
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*function\\s-+\\([a-zA-Z_]\\w*\\)\\s-*(" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'function
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find ids
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*id:\\s-*\\([a-zA-Z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'variable
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))
|
||||
|
||||
(nreverse symbols)))
|
||||
|
||||
(defun bungee--parse-cpp-file (file-path)
|
||||
"Parse a C++ file and extract symbols."
|
||||
(let ((symbols '())
|
||||
(case-fold-search nil))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file-path)
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Find classes/structs
|
||||
(save-excursion
|
||||
(while (re-search-forward "^\\s-*\\(?:class\\|struct\\|union\\)\\s-+\\([A-Za-z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'class
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))
|
||||
|
||||
;; Find namespaces
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*namespace\\s-+\\([A-Za-z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'namespace
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find enums
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*enum\\s-+\\(?:class\\s-+\\)?\\([A-Za-z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'enum
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Basic function detection (simplified)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*\\(?:\\w+\\s-+\\)*\\([A-Za-z_]\\w*\\)\\s-*([^)]*)[^;{]*{" nil t)
|
||||
(let ((name (match-string 1)))
|
||||
(unless (member name '("if" "while" "for" "switch" "catch"))
|
||||
(push (make-bungee-symbol
|
||||
:name name
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'function
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))))
|
||||
|
||||
(nreverse symbols)))
|
||||
|
||||
(defun bungee--index-file (file-path &optional force)
|
||||
"Index FILE-PATH. If FORCE is non-nil, reindex even if cached."
|
||||
(bungee--ensure-cache)
|
||||
(when (or force (not (bungee--file-cached-p file-path)))
|
||||
(let ((symbols (cond
|
||||
((string-match-p "\\.qml\\'" file-path)
|
||||
(bungee--parse-qml-file file-path))
|
||||
((string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'" file-path)
|
||||
(bungee--parse-cpp-file file-path))
|
||||
(t nil))))
|
||||
(when symbols
|
||||
(puthash file-path symbols bungee--symbol-cache)
|
||||
(puthash file-path (float-time (nth 5 (file-attributes file-path))) bungee--index-cache)
|
||||
(message "Indexed %s: %d symbols" file-path (length symbols))))))
|
||||
|
||||
;; Symbol lookup functions
|
||||
(defun bungee-find-symbol (symbol-name &optional exact-match)
|
||||
"Find all symbols matching SYMBOL-NAME. If EXACT-MATCH is non-nil, use exact matching."
|
||||
(bungee--ensure-cache)
|
||||
(let ((results '()))
|
||||
(maphash (lambda (_ symbols)
|
||||
(dolist (symbol symbols)
|
||||
(when (if exact-match
|
||||
(string= (bungee-symbol-name symbol) symbol-name)
|
||||
(string-match-p (regexp-quote symbol-name)
|
||||
(bungee-symbol-name symbol)))
|
||||
(push symbol results))))
|
||||
bungee--symbol-cache)
|
||||
(sort results (lambda (a b)
|
||||
(or (string< (bungee-symbol-name a) (bungee-symbol-name b))
|
||||
(< (bungee-symbol-line-number a) (bungee-symbol-line-number b)))))))
|
||||
|
||||
(defun bungee-find-definition (symbol-name)
|
||||
"Find the most likely definition of SYMBOL-NAME."
|
||||
(let ((symbols (bungee-find-symbol symbol-name t)))
|
||||
;; Prioritize by symbol type
|
||||
(or (cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'class)) symbols)
|
||||
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'namespace)) symbols)
|
||||
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'function)) symbols)
|
||||
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'property)) symbols)
|
||||
(car symbols))))
|
||||
|
||||
;; Interactive commands
|
||||
(defun bungee-jump-to-definition ()
|
||||
"Jump to definition of symbol at point."
|
||||
(interactive)
|
||||
(let* ((symbol-name (or (thing-at-point 'symbol t)
|
||||
(read-string "Symbol: ")))
|
||||
(symbol (bungee-find-definition symbol-name)))
|
||||
(if symbol
|
||||
(progn
|
||||
(push-mark)
|
||||
(find-file (bungee-symbol-file-path symbol))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (bungee-symbol-line-number symbol)))
|
||||
(when (fboundp 'pulse-momentary-highlight-one-line)
|
||||
(pulse-momentary-highlight-one-line (point)))
|
||||
(message "Found: %s" (bungee-symbol-context symbol)))
|
||||
(message "No definition found for '%s'" symbol-name))))
|
||||
|
||||
(defun bungee-find-references ()
|
||||
"Find all references to symbol at point."
|
||||
(interactive)
|
||||
(let* ((symbol-name (or (thing-at-point 'symbol t)
|
||||
(read-string "Find references to: ")))
|
||||
(symbols (bungee-find-symbol symbol-name)))
|
||||
(if symbols
|
||||
(let ((buffer (get-buffer-create "*Bungee References*")))
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (format "References to '%s':\n\n" symbol-name))
|
||||
(dolist (symbol symbols)
|
||||
(insert (format "%s:%d: %s [%s]\n"
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
(bungee-symbol-context symbol)
|
||||
(bungee-symbol-symbol-type symbol))))
|
||||
(goto-char (point-min))
|
||||
(grep-mode)))
|
||||
(display-buffer buffer))
|
||||
(message "No references found for '%s'" symbol-name))))
|
||||
|
||||
(defun bungee-index-directory (&optional directory force)
|
||||
"Index all files in DIRECTORY. If FORCE is non-nil, reindex all files."
|
||||
(interactive "DDirectory to index: \nP")
|
||||
(let* ((dir (or directory default-directory))
|
||||
(files (directory-files-recursively
|
||||
dir
|
||||
"\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
|
||||
nil
|
||||
(lambda (d) (not (or (string-match-p "/\\." d)
|
||||
(string-match-p "/node_modules" d)
|
||||
(string-match-p "/CMakeFiles" d))))))
|
||||
(count 0))
|
||||
(dolist (file files)
|
||||
(when (or force (not (bungee--file-cached-p file)))
|
||||
(bungee--index-file file force)
|
||||
(setq count (1+ count))))
|
||||
(bungee--save-cache)
|
||||
(message "Indexed %d files" count)))
|
||||
|
||||
(defun bungee-index-current-file ()
|
||||
"Index or reindex the current file."
|
||||
(interactive)
|
||||
(when buffer-file-name
|
||||
(bungee--index-file buffer-file-name t)
|
||||
(bungee--save-cache)))
|
||||
|
||||
(defun bungee-cache-status ()
|
||||
"Show cache status."
|
||||
(interactive)
|
||||
(bungee--ensure-cache)
|
||||
(let ((file-count (hash-table-count bungee--index-cache))
|
||||
(symbol-count 0))
|
||||
(maphash (lambda (_ symbols)
|
||||
(setq symbol-count (+ symbol-count (length symbols))))
|
||||
bungee--symbol-cache)
|
||||
(message "Bungee cache: %d files, %d symbols" file-count symbol-count)))
|
||||
|
||||
(defun bungee-clear-cache ()
|
||||
"Clear the in-memory cache."
|
||||
(interactive)
|
||||
(setq bungee--symbol-cache nil
|
||||
bungee--index-cache nil
|
||||
bungee--cache-loaded nil)
|
||||
(message "Bungee cache cleared"))
|
||||
|
||||
;; Python indexer integration (optional)
|
||||
(defun bungee-index-with-python (&optional force)
|
||||
"Index using Python script if configured."
|
||||
(interactive "P")
|
||||
(if bungee-python-indexer
|
||||
(let* ((root (or (when (fboundp 'project-root)
|
||||
(car (project-roots (project-current))))
|
||||
default-directory))
|
||||
(cmd (format "python3 %s --index %s --root %s --cache-dir %s"
|
||||
(shell-quote-argument bungee-python-indexer)
|
||||
(if force "--force" "")
|
||||
(shell-quote-argument root)
|
||||
(shell-quote-argument (bungee--cache-dir)))))
|
||||
(message "Running: %s" cmd)
|
||||
(shell-command cmd)
|
||||
(bungee-clear-cache)
|
||||
(bungee--load-cache))
|
||||
(message "Python indexer not configured. Use `bungee-index-directory' instead.")))
|
||||
|
||||
;; Auto-update on save
|
||||
(defun bungee--after-save-hook ()
|
||||
"Update index after saving a file."
|
||||
(when (and bungee-auto-update
|
||||
buffer-file-name
|
||||
(string-match-p "\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
|
||||
buffer-file-name))
|
||||
(bungee--index-file buffer-file-name t)
|
||||
(bungee--save-cache)))
|
||||
|
||||
;; Minor mode
|
||||
(defvar bungee-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(define-key map (kbd "M-?") 'bungee-find-references)
|
||||
(define-key map (kbd "C-c b i") 'bungee-index-directory)
|
||||
(define-key map (kbd "C-c b f") 'bungee-index-current-file)
|
||||
(define-key map (kbd "C-c b s") 'bungee-cache-status)
|
||||
(define-key map (kbd "C-c b c") 'bungee-clear-cache)
|
||||
(define-key map (kbd "C-c b p") 'bungee-index-with-python)
|
||||
map)
|
||||
"Keymap for bungee-mode.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode bungee-mode
|
||||
"Minor mode for fast symbol navigation with caching."
|
||||
:lighter " Bungee"
|
||||
:keymap bungee-mode-map
|
||||
(if bungee-mode
|
||||
(add-hook 'after-save-hook 'bungee--after-save-hook nil t)
|
||||
(remove-hook 'after-save-hook 'bungee--after-save-hook t)))
|
||||
|
||||
;;;###autoload
|
||||
(define-globalized-minor-mode global-bungee-mode
|
||||
bungee-mode
|
||||
(lambda ()
|
||||
(when (and (not (minibufferp))
|
||||
buffer-file-name
|
||||
(string-match-p "\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
|
||||
buffer-file-name))
|
||||
(bungee-mode 1))))
|
||||
|
||||
;; xref integration
|
||||
(when (fboundp 'xref-make)
|
||||
(defun bungee-xref-backend () 'bungee)
|
||||
|
||||
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql bungee)))
|
||||
(thing-at-point 'symbol t))
|
||||
|
||||
(cl-defmethod xref-backend-definitions ((_backend (eql bungee)) identifier)
|
||||
(let ((symbol (bungee-find-definition identifier)))
|
||||
(when symbol
|
||||
(list (xref-make (bungee-symbol-context symbol)
|
||||
(xref-make-file-location
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
0))))))
|
||||
|
||||
(cl-defmethod xref-backend-references ((_backend (eql bungee)) identifier)
|
||||
(mapcar (lambda (symbol)
|
||||
(xref-make (bungee-symbol-context symbol)
|
||||
(xref-make-file-location
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
0)))
|
||||
(bungee-find-symbol identifier)))
|
||||
|
||||
(add-hook 'bungee-mode-hook
|
||||
(lambda ()
|
||||
(add-hook 'xref-backend-functions 'bungee-xref-backend nil t))))
|
||||
|
||||
(provide 'bungee)
|
||||
;;; bungee.el ends here
|
||||
464
lisp/developer-dark-theme.el
Normal file
464
lisp/developer-dark-theme.el
Normal file
@@ -0,0 +1,464 @@
|
||||
;;; developer-dark-theme.el --- A balanced dark theme for C, Python, and QML -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2024
|
||||
|
||||
;; Author: Developer
|
||||
;; Version: 1.0
|
||||
;; Package-Requires: ((emacs "24.3"))
|
||||
;; Keywords: faces, themes
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; A carefully crafted dark theme with balanced contrast.
|
||||
;; Optimized for C/C++, Python, and QML development.
|
||||
;; Not too faded, not too extreme - just right.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(deftheme developer-dark
|
||||
"A balanced dark theme for programming.")
|
||||
|
||||
(let ((class '((class color) (min-colors 89)))
|
||||
;; Base colors
|
||||
(bg-main "#1a1d23") ; Dark blue-grey background
|
||||
(bg-dim "#14171c") ; Darker for modeline inactive
|
||||
(bg-active "#252930") ; Lighter for selections
|
||||
(bg-region "#2d3640") ; Selection background
|
||||
(bg-hl-line "#222630") ; Current line highlight
|
||||
|
||||
;; Foreground colors
|
||||
(fg-main "#d4d8df") ; Main text - good readability
|
||||
(fg-dim "#969ba7") ; Comments, less important
|
||||
(fg-bright "#e8ecf2") ; Emphasized text
|
||||
|
||||
;; Syntax colors - balanced, not too bright
|
||||
(red "#e06c75") ; Errors, important warnings
|
||||
(red-bright "#ff7a85") ; Bright red for critical
|
||||
(green "#98c379") ; Strings, success
|
||||
(green-bright "#b5e890") ; Bright green
|
||||
(yellow "#e5c07b") ; Warnings, special keywords
|
||||
(yellow-bright "#ffd787") ; Bright yellow
|
||||
(blue "#61afef") ; Functions, primary keywords
|
||||
(blue-bright "#74c7ff") ; Bright blue
|
||||
(magenta "#c678dd") ; Constants, special syntax
|
||||
(magenta-bright "#e198ff") ; Bright magenta
|
||||
(cyan "#56b6c2") ; Types, classes
|
||||
(cyan-bright "#7fd8e8") ; Bright cyan
|
||||
(orange "#d19a66") ; Numbers, preprocessor
|
||||
(purple "#a984d4") ; Special identifiers
|
||||
|
||||
;; UI colors
|
||||
(border "#3a4049") ; Borders, separators
|
||||
(cursor "#61afef") ; Cursor color
|
||||
(fringe "#1e2228") ; Fringe background
|
||||
(modeline-bg "#232830") ; Active modeline
|
||||
(modeline-fg "#b8bcc5")) ; Modeline text
|
||||
|
||||
(custom-theme-set-faces
|
||||
'developer-dark
|
||||
|
||||
;; Basic faces
|
||||
`(default ((,class (:background ,bg-main :foreground ,fg-main))))
|
||||
`(cursor ((,class (:background ,cursor))))
|
||||
`(region ((,class (:background ,bg-region :extend t))))
|
||||
`(highlight ((,class (:background ,bg-active))))
|
||||
`(hl-line ((,class (:background ,bg-hl-line :extend t))))
|
||||
`(fringe ((,class (:background ,fringe :foreground ,fg-dim))))
|
||||
`(vertical-border ((,class (:foreground ,border))))
|
||||
`(window-divider ((,class (:foreground ,border))))
|
||||
`(window-divider-first-pixel ((,class (:foreground ,border))))
|
||||
`(window-divider-last-pixel ((,class (:foreground ,border))))
|
||||
|
||||
;; Font lock faces - syntax highlighting
|
||||
`(font-lock-builtin-face ((,class (:foreground ,blue))))
|
||||
`(font-lock-comment-face ((,class (:foreground ,fg-dim :slant italic))))
|
||||
`(font-lock-comment-delimiter-face ((,class (:foreground ,fg-dim))))
|
||||
`(font-lock-constant-face ((,class (:foreground ,magenta))))
|
||||
`(font-lock-doc-face ((,class (:foreground ,green))))
|
||||
`(font-lock-function-name-face ((,class (:foreground ,blue :weight medium))))
|
||||
`(font-lock-keyword-face ((,class (:foreground ,purple :weight medium))))
|
||||
`(font-lock-negation-char-face ((,class (:foreground ,red))))
|
||||
`(font-lock-preprocessor-face ((,class (:foreground ,orange))))
|
||||
`(font-lock-regexp-grouping-backslash ((,class (:foreground ,yellow))))
|
||||
`(font-lock-regexp-grouping-construct ((,class (:foreground ,purple))))
|
||||
`(font-lock-string-face ((,class (:foreground ,green))))
|
||||
`(font-lock-type-face ((,class (:foreground ,cyan))))
|
||||
`(font-lock-variable-name-face ((,class (:foreground ,fg-main))))
|
||||
`(font-lock-warning-face ((,class (:foreground ,yellow :weight bold))))
|
||||
|
||||
;; C/C++ specific
|
||||
`(c-annotation-face ((,class (:foreground ,magenta))))
|
||||
|
||||
;; Line numbers
|
||||
`(line-number ((,class (:background ,bg-main :foreground ,fg-dim))))
|
||||
`(line-number-current-line ((,class (:background ,bg-hl-line :foreground ,yellow :weight bold))))
|
||||
|
||||
;; Mode line
|
||||
`(mode-line ((,class (:background ,modeline-bg :foreground ,modeline-fg :box (:line-width 1 :color ,border)))))
|
||||
`(mode-line-inactive ((,class (:background ,bg-dim :foreground ,fg-dim :box (:line-width 1 :color ,border)))))
|
||||
`(mode-line-buffer-id ((,class (:weight bold :foreground ,blue))))
|
||||
`(mode-line-emphasis ((,class (:foreground ,fg-bright :weight bold))))
|
||||
`(mode-line-highlight ((,class (:background ,bg-active))))
|
||||
|
||||
;; Search and replace
|
||||
`(isearch ((,class (:background ,yellow :foreground ,bg-main :weight bold))))
|
||||
`(isearch-fail ((,class (:background ,red :foreground ,fg-bright))))
|
||||
`(lazy-highlight ((,class (:background ,bg-active :foreground ,yellow))))
|
||||
`(match ((,class (:background ,green :foreground ,bg-main))))
|
||||
|
||||
;; Diff-hl (git changes in fringe)
|
||||
`(diff-hl-change ((,class (:foreground ,blue :background ,blue))))
|
||||
`(diff-hl-delete ((,class (:foreground ,red :background ,red))))
|
||||
`(diff-hl-insert ((,class (:foreground ,green :background ,green))))
|
||||
|
||||
;; Diff mode
|
||||
`(diff-added ((,class (:background "#1e3a28" :foreground ,green :extend t))))
|
||||
`(diff-removed ((,class (:background "#3a1e28" :foreground ,red :extend t))))
|
||||
`(diff-changed ((,class (:background "#2a2a3a" :extend t))))
|
||||
`(diff-header ((,class (:background ,bg-dim :foreground ,fg-bright :weight bold))))
|
||||
`(diff-file-header ((,class (:background ,bg-dim :foreground ,blue :weight bold))))
|
||||
`(diff-hunk-header ((,class (:background ,bg-active :foreground ,purple))))
|
||||
|
||||
;; Company (auto-completion)
|
||||
`(company-tooltip ((,class (:background ,bg-active :foreground ,fg-main))))
|
||||
`(company-tooltip-selection ((,class (:background ,blue :foreground ,bg-main))))
|
||||
`(company-tooltip-common ((,class (:foreground ,yellow :weight bold))))
|
||||
`(company-tooltip-common-selection ((,class (:foreground ,bg-main :weight bold))))
|
||||
`(company-tooltip-annotation ((,class (:foreground ,cyan))))
|
||||
`(company-tooltip-annotation-selection ((,class (:foreground ,bg-main))))
|
||||
`(company-scrollbar-bg ((,class (:background ,bg-dim))))
|
||||
`(company-scrollbar-fg ((,class (:background ,border))))
|
||||
`(company-preview ((,class (:background ,bg-active :foreground ,green))))
|
||||
`(company-preview-common ((,class (:background ,bg-active :foreground ,yellow))))
|
||||
|
||||
;; Helm
|
||||
`(helm-header ((,class (:background ,bg-dim :foreground ,fg-main :weight bold :extend t))))
|
||||
`(helm-source-header ((,class (:background ,bg-active :foreground ,fg-bright :weight bold :extend t))))
|
||||
`(helm-selection ((,class (:background ,bg-region :weight bold :extend t))))
|
||||
`(helm-visible-mark ((,class (:background ,blue :foreground ,bg-main))))
|
||||
`(helm-candidate-number ((,class (:foreground ,green :weight bold))))
|
||||
`(helm-separator ((,class (:foreground ,red))))
|
||||
`(helm-match ((,class (:foreground ,yellow :weight bold))))
|
||||
`(helm-M-x-key ((,class (:foreground ,orange :weight bold))))
|
||||
`(helm-buffer-not-saved ((,class (:foreground ,red))))
|
||||
`(helm-buffer-process ((,class (:foreground ,magenta))))
|
||||
`(helm-buffer-saved-out ((,class (:foreground ,yellow))))
|
||||
`(helm-buffer-size ((,class (:foreground ,fg-dim))))
|
||||
`(helm-buffer-directory ((,class (:foreground ,blue :weight bold))))
|
||||
`(helm-buffer-file ((,class (:foreground ,fg-main))))
|
||||
`(helm-ff-directory ((,class (:foreground ,blue :weight bold))))
|
||||
`(helm-ff-file ((,class (:foreground ,fg-main))))
|
||||
`(helm-ff-executable ((,class (:foreground ,green))))
|
||||
`(helm-ff-symlink ((,class (:foreground ,cyan))))
|
||||
`(helm-ff-prefix ((,class (:background ,yellow :foreground ,bg-main))))
|
||||
|
||||
;; Treemacs
|
||||
`(treemacs-directory-face ((,class (:foreground ,blue))))
|
||||
`(treemacs-file-face ((,class (:foreground ,fg-main))))
|
||||
`(treemacs-root-face ((,class (:foreground ,green :weight bold))))
|
||||
`(treemacs-git-modified-face ((,class (:foreground ,orange))))
|
||||
`(treemacs-git-added-face ((,class (:foreground ,green))))
|
||||
`(treemacs-git-untracked-face ((,class (:foreground ,fg-dim))))
|
||||
`(treemacs-git-renamed-face ((,class (:foreground ,yellow))))
|
||||
`(treemacs-git-deleted-face ((,class (:foreground ,red))))
|
||||
|
||||
;; Magit
|
||||
`(magit-branch-current ((,class (:foreground ,green :weight bold))))
|
||||
`(magit-branch-local ((,class (:foreground ,blue))))
|
||||
`(magit-branch-remote ((,class (:foreground ,magenta))))
|
||||
`(magit-diff-added ((,class (:background "#1e3a28" :foreground ,green :extend t))))
|
||||
`(magit-diff-added-highlight ((,class (:background "#2a4a34" :foreground ,green :extend t))))
|
||||
`(magit-diff-removed ((,class (:background "#3a1e28" :foreground ,red :extend t))))
|
||||
`(magit-diff-removed-highlight ((,class (:background "#4a2a34" :foreground ,red :extend t))))
|
||||
`(magit-diff-context ((,class (:foreground ,fg-dim :extend t))))
|
||||
`(magit-diff-context-highlight ((,class (:background ,bg-hl-line :foreground ,fg-main :extend t))))
|
||||
`(magit-section-heading ((,class (:foreground ,blue :weight bold))))
|
||||
`(magit-section-highlight ((,class (:background ,bg-hl-line :extend t))))
|
||||
`(magit-hash ((,class (:foreground ,fg-dim))))
|
||||
|
||||
;; LSP
|
||||
`(lsp-face-highlight-textual ((,class (:background ,bg-active))))
|
||||
`(lsp-face-highlight-read ((,class (:background ,bg-active))))
|
||||
`(lsp-face-highlight-write ((,class (:background ,bg-region))))
|
||||
`(lsp-ui-doc-background ((,class (:background ,bg-active))))
|
||||
`(lsp-ui-doc-border ((,class (:foreground ,border))))
|
||||
`(lsp-ui-doc-header ((,class (:background ,blue :foreground ,bg-main :weight bold))))
|
||||
`(lsp-ui-peek-peek ((,class (:background ,bg-active))))
|
||||
`(lsp-ui-peek-list ((,class (:background ,bg-dim))))
|
||||
`(lsp-ui-peek-filename ((,class (:foreground ,orange :weight bold))))
|
||||
`(lsp-ui-peek-selection ((,class (:background ,bg-region))))
|
||||
`(lsp-ui-peek-highlight ((,class (:background ,yellow :foreground ,bg-main))))
|
||||
`(lsp-ui-sideline-code-action ((,class (:foreground ,yellow))))
|
||||
|
||||
;; Flycheck
|
||||
`(flycheck-error ((,class (:underline (:style wave :color ,red)))))
|
||||
`(flycheck-warning ((,class (:underline (:style wave :color ,yellow)))))
|
||||
`(flycheck-info ((,class (:underline (:style wave :color ,cyan)))))
|
||||
`(flycheck-fringe-error ((,class (:foreground ,red))))
|
||||
`(flycheck-fringe-warning ((,class (:foreground ,yellow))))
|
||||
`(flycheck-fringe-info ((,class (:foreground ,cyan))))
|
||||
|
||||
;; Rainbow delimiters
|
||||
`(rainbow-delimiters-depth-1-face ((,class (:foreground ,blue))))
|
||||
`(rainbow-delimiters-depth-2-face ((,class (:foreground ,green))))
|
||||
`(rainbow-delimiters-depth-3-face ((,class (:foreground ,yellow))))
|
||||
`(rainbow-delimiters-depth-4-face ((,class (:foreground ,cyan))))
|
||||
`(rainbow-delimiters-depth-5-face ((,class (:foreground ,magenta))))
|
||||
`(rainbow-delimiters-depth-6-face ((,class (:foreground ,orange))))
|
||||
`(rainbow-delimiters-depth-7-face ((,class (:foreground ,blue-bright))))
|
||||
`(rainbow-delimiters-depth-8-face ((,class (:foreground ,green-bright))))
|
||||
`(rainbow-delimiters-depth-9-face ((,class (:foreground ,red-bright))))
|
||||
|
||||
;; Org mode
|
||||
`(org-level-1 ((,class (:foreground ,blue :weight bold))))
|
||||
`(org-level-2 ((,class (:foreground ,green :weight bold))))
|
||||
`(org-level-3 ((,class (:foreground ,yellow :weight bold))))
|
||||
`(org-level-4 ((,class (:foreground ,cyan :weight bold))))
|
||||
`(org-level-5 ((,class (:foreground ,magenta :weight bold))))
|
||||
`(org-level-6 ((,class (:foreground ,orange :weight bold))))
|
||||
`(org-level-7 ((,class (:foreground ,purple :weight bold))))
|
||||
`(org-level-8 ((,class (:foreground ,fg-main :weight bold))))
|
||||
`(org-link ((,class (:foreground ,blue :underline t))))
|
||||
`(org-code ((,class (:background ,bg-active :foreground ,orange))))
|
||||
`(org-block ((,class (:background ,bg-dim :extend t))))
|
||||
`(org-block-begin-line ((,class (:background ,bg-dim :foreground ,fg-dim :extend t))))
|
||||
`(org-block-end-line ((,class (:background ,bg-dim :foreground ,fg-dim :extend t))))
|
||||
|
||||
;; Markdown
|
||||
`(markdown-header-face-1 ((,class (:foreground ,blue :weight bold))))
|
||||
`(markdown-header-face-2 ((,class (:foreground ,green :weight bold))))
|
||||
`(markdown-header-face-3 ((,class (:foreground ,yellow :weight bold))))
|
||||
`(markdown-header-face-4 ((,class (:foreground ,cyan :weight bold))))
|
||||
`(markdown-header-face-5 ((,class (:foreground ,magenta :weight bold))))
|
||||
`(markdown-header-face-6 ((,class (:foreground ,orange :weight bold))))
|
||||
`(markdown-code-face ((,class (:background ,bg-active :foreground ,orange))))
|
||||
`(markdown-inline-code-face ((,class (:background ,bg-active :foreground ,orange))))
|
||||
`(markdown-link-face ((,class (:foreground ,blue :underline t))))
|
||||
`(markdown-url-face ((,class (:foreground ,cyan :underline t))))
|
||||
|
||||
;; Which-key
|
||||
`(which-key-key-face ((,class (:foreground ,green :weight bold))))
|
||||
`(which-key-separator-face ((,class (:foreground ,fg-dim))))
|
||||
`(which-key-note-face ((,class (:foreground ,fg-dim :slant italic))))
|
||||
`(which-key-command-description-face ((,class (:foreground ,fg-main))))
|
||||
`(which-key-group-description-face ((,class (:foreground ,purple))))
|
||||
|
||||
;; Minibuffer
|
||||
`(minibuffer-prompt ((,class (:foreground ,blue :weight bold))))
|
||||
`(completions-common-part ((,class (:foreground ,yellow :weight bold))))
|
||||
`(completions-first-difference ((,class (:foreground ,orange :weight bold))))
|
||||
|
||||
;; Messages
|
||||
`(success ((,class (:foreground ,green :weight bold))))
|
||||
`(warning ((,class (:foreground ,yellow :weight bold))))
|
||||
`(error ((,class (:foreground ,red :weight bold))))
|
||||
|
||||
;; Links
|
||||
`(link ((,class (:foreground ,blue :underline t))))
|
||||
`(link-visited ((,class (:foreground ,purple :underline t))))
|
||||
|
||||
;; Buttons
|
||||
`(button ((,class (:foreground ,cyan :underline t))))
|
||||
`(custom-button ((,class (:background ,bg-active :foreground ,fg-main :box (:line-width 1 :color ,border)))))
|
||||
`(custom-button-mouse ((,class (:background ,bg-region :foreground ,fg-bright))))
|
||||
`(custom-button-pressed ((,class (:background ,bg-region :foreground ,fg-bright))))
|
||||
|
||||
;; Widgets
|
||||
`(widget-field ((,class (:background ,bg-active :foreground ,fg-main))))
|
||||
`(widget-single-line-field ((,class (:background ,bg-active :foreground ,fg-main))))
|
||||
|
||||
;; Origami (code folding)
|
||||
`(origami-fold-header-face ((,class (:foreground ,fg-dim :box nil))))
|
||||
`(origami-fold-fringe-face ((,class (:foreground ,fg-dim))))
|
||||
`(origami-fold-replacement-face ((,class (:foreground ,fg-dim))))
|
||||
|
||||
;; QML mode specific
|
||||
`(qml-operator-face ((,class (:foreground ,orange))))
|
||||
|
||||
;; Python mode specific
|
||||
`(python-info-docstring-face ((,class (:foreground ,green :slant italic))))
|
||||
|
||||
;; Show paren
|
||||
`(show-paren-match ((,class (:background ,bg-region :foreground ,yellow :weight bold))))
|
||||
`(show-paren-mismatch ((,class (:background ,red :foreground ,fg-bright :weight bold))))
|
||||
|
||||
;; Highlight indentation
|
||||
`(highlight-indentation-face ((,class (:background ,bg-dim))))
|
||||
`(highlight-indentation-current-column-face ((,class (:background ,bg-active))))
|
||||
|
||||
;; Whitespace
|
||||
`(whitespace-space ((,class (:foreground ,bg-active))))
|
||||
`(whitespace-tab ((,class (:foreground ,bg-active))))
|
||||
`(whitespace-trailing ((,class (:background ,red :foreground ,yellow))))
|
||||
`(whitespace-line ((,class (:background ,bg-active :foreground ,red))))
|
||||
|
||||
;; Terminal
|
||||
`(term-color-black ((,class (:foreground ,bg-main :background ,bg-main))))
|
||||
`(term-color-red ((,class (:foreground ,red :background ,red))))
|
||||
`(term-color-green ((,class (:foreground ,green :background ,green))))
|
||||
`(term-color-yellow ((,class (:foreground ,yellow :background ,yellow))))
|
||||
`(term-color-blue ((,class (:foreground ,blue :background ,blue))))
|
||||
`(term-color-magenta ((,class (:foreground ,magenta :background ,magenta))))
|
||||
`(term-color-cyan ((,class (:foreground ,cyan :background ,cyan))))
|
||||
`(term-color-white ((,class (:foreground ,fg-main :background ,fg-main))))
|
||||
|
||||
;; Ansi colors
|
||||
`(ansi-color-black ((,class (:foreground ,bg-main :background ,bg-main))))
|
||||
`(ansi-color-red ((,class (:foreground ,red :background ,red))))
|
||||
`(ansi-color-green ((,class (:foreground ,green :background ,green))))
|
||||
`(ansi-color-yellow ((,class (:foreground ,yellow :background ,yellow))))
|
||||
`(ansi-color-blue ((,class (:foreground ,blue :background ,blue))))
|
||||
`(ansi-color-magenta ((,class (:foreground ,magenta :background ,magenta))))
|
||||
`(ansi-color-cyan ((,class (:foreground ,cyan :background ,cyan))))
|
||||
`(ansi-color-white ((,class (:foreground ,fg-main :background ,fg-main))))
|
||||
|
||||
;; mu4e - Email client
|
||||
`(mu4e-header-face ((,class (:foreground ,fg-main))))
|
||||
`(mu4e-header-highlight-face ((,class (:background ,bg-region :weight bold :extend t))))
|
||||
`(mu4e-header-marks-face ((,class (:foreground ,yellow :weight bold))))
|
||||
`(mu4e-header-title-face ((,class (:foreground ,blue :weight bold))))
|
||||
`(mu4e-header-key-face ((,class (:foreground ,green :weight bold))))
|
||||
`(mu4e-header-value-face ((,class (:foreground ,fg-main))))
|
||||
`(mu4e-unread-face ((,class (:foreground ,cyan :weight bold))))
|
||||
`(mu4e-flagged-face ((,class (:foreground ,red :weight bold))))
|
||||
`(mu4e-replied-face ((,class (:foreground ,green))))
|
||||
`(mu4e-forwarded-face ((,class (:foreground ,blue))))
|
||||
`(mu4e-draft-face ((,class (:foreground ,orange))))
|
||||
`(mu4e-trashed-face ((,class (:foreground ,fg-dim :strike-through t))))
|
||||
`(mu4e-cited-1-face ((,class (:foreground ,blue))))
|
||||
`(mu4e-cited-2-face ((,class (:foreground ,green))))
|
||||
`(mu4e-cited-3-face ((,class (:foreground ,yellow))))
|
||||
`(mu4e-cited-4-face ((,class (:foreground ,cyan))))
|
||||
`(mu4e-cited-5-face ((,class (:foreground ,magenta))))
|
||||
`(mu4e-cited-6-face ((,class (:foreground ,orange))))
|
||||
`(mu4e-cited-7-face ((,class (:foreground ,purple))))
|
||||
`(mu4e-compose-separator-face ((,class (:foreground ,fg-dim :strike-through t))))
|
||||
`(mu4e-compose-header-face ((,class (:foreground ,fg-dim))))
|
||||
`(mu4e-contact-face ((,class (:foreground ,blue))))
|
||||
`(mu4e-context-face ((,class (:foreground ,purple :weight bold))))
|
||||
`(mu4e-highlight-face ((,class (:background ,bg-active))))
|
||||
`(mu4e-link-face ((,class (:foreground ,blue :underline t))))
|
||||
`(mu4e-modeline-face ((,class (:foreground ,yellow :weight bold))))
|
||||
`(mu4e-moved-face ((,class (:foreground ,magenta))))
|
||||
`(mu4e-ok-face ((,class (:foreground ,green :weight bold))))
|
||||
`(mu4e-region-code ((,class (:background ,bg-active))))
|
||||
`(mu4e-special-header-value-face ((,class (:foreground ,cyan))))
|
||||
`(mu4e-system-face ((,class (:foreground ,fg-dim :slant italic))))
|
||||
`(mu4e-title-face ((,class (:foreground ,blue :weight bold))))
|
||||
`(mu4e-url-number-face ((,class (:foreground ,orange :weight bold))))
|
||||
`(mu4e-view-body-face ((,class (:foreground ,fg-main))))
|
||||
`(mu4e-warning-face ((,class (:foreground ,yellow :weight bold))))
|
||||
`(mu4e-attach-number-face ((,class (:foreground ,orange :weight bold))))
|
||||
|
||||
;; mu4e headers specific
|
||||
`(mu4e-headers-date-face ((,class (:foreground ,fg-dim))))
|
||||
`(mu4e-headers-from-face ((,class (:foreground ,blue))))
|
||||
`(mu4e-headers-subject-face ((,class (:foreground ,fg-main))))
|
||||
`(mu4e-headers-size-face ((,class (:foreground ,fg-dim))))
|
||||
`(mu4e-headers-seen-face ((,class (:foreground ,fg-dim))))
|
||||
`(mu4e-headers-unread-face ((,class (:foreground ,cyan :weight bold))))
|
||||
`(mu4e-headers-flagged-face ((,class (:foreground ,red :weight bold))))
|
||||
`(mu4e-headers-new-face ((,class (:foreground ,green :weight bold))))
|
||||
`(mu4e-headers-passed-face ((,class (:foreground ,blue))))
|
||||
`(mu4e-headers-replied-face ((,class (:foreground ,green))))
|
||||
`(mu4e-headers-trashed-face ((,class (:foreground ,fg-dim :strike-through t))))
|
||||
`(mu4e-headers-draft-face ((,class (:foreground ,orange))))
|
||||
|
||||
;; elfeed - RSS/News reader
|
||||
`(elfeed-log-date-face ((,class (:foreground ,fg-dim))))
|
||||
`(elfeed-log-debug-level-face ((,class (:foreground ,fg-dim))))
|
||||
`(elfeed-log-error-level-face ((,class (:foreground ,red))))
|
||||
`(elfeed-log-info-level-face ((,class (:foreground ,blue))))
|
||||
`(elfeed-log-warn-level-face ((,class (:foreground ,yellow))))
|
||||
|
||||
;; elfeed search buffer
|
||||
`(elfeed-search-date-face ((,class (:foreground ,purple))))
|
||||
`(elfeed-search-feed-face ((,class (:foreground ,cyan))))
|
||||
`(elfeed-search-filter-face ((,class (:foreground ,green :weight bold))))
|
||||
`(elfeed-search-last-update-face ((,class (:foreground ,blue))))
|
||||
`(elfeed-search-tag-face ((,class (:foreground ,yellow))))
|
||||
`(elfeed-search-title-face ((,class (:foreground ,fg-main))))
|
||||
`(elfeed-search-unread-count-face ((,class (:foreground ,green :weight bold))))
|
||||
`(elfeed-search-unread-title-face ((,class (:foreground ,fg-bright :weight bold))))
|
||||
|
||||
;; Custom elfeed tag faces (override hardcoded colors from elfeed-config.el)
|
||||
`(elfeed-face-tag-news ((,class (:foreground ,orange)))) ; Was "#8B4513" (saddle brown)
|
||||
`(elfeed-face-tag-tech ((,class (:foreground ,blue)))) ; Was "#4682B4" (steel blue)
|
||||
`(elfeed-face-tag-security ((,class (:foreground ,red)))) ; Was "#DC143C" (crimson)
|
||||
`(elfeed-face-tag-programming ((,class (:foreground ,green)))) ; Was "#228B22" (forest green)
|
||||
`(elfeed-face-tag-opensource ((,class (:foreground ,cyan))))
|
||||
|
||||
;; elfeed show buffer (article view)
|
||||
`(elfeed-show-author-face ((,class (:foreground ,blue))))
|
||||
`(elfeed-show-feed-face ((,class (:foreground ,cyan))))
|
||||
`(elfeed-show-tag-face ((,class (:foreground ,yellow))))
|
||||
`(elfeed-show-title-face ((,class (:foreground ,fg-bright :weight bold :height 1.3))))
|
||||
|
||||
;; Special tags - customize these for your own tags
|
||||
`(elfeed-search-starred-title-face ((,class (:foreground ,yellow :weight bold))))
|
||||
`(elfeed-search-mustread-title-face ((,class (:foreground ,red :weight bold))))
|
||||
`(elfeed-search-later-title-face ((,class (:foreground ,orange))))
|
||||
`(elfeed-search-important-title-face ((,class (:foreground ,magenta :weight bold))))
|
||||
|
||||
;; Gnus (sometimes used with mu4e)
|
||||
`(gnus-header-content ((,class (:foreground ,fg-main))))
|
||||
`(gnus-header-from ((,class (:foreground ,blue :weight bold))))
|
||||
`(gnus-header-subject ((,class (:foreground ,green))))
|
||||
`(gnus-header-name ((,class (:foreground ,purple))))
|
||||
`(gnus-header-newsgroups ((,class (:foreground ,yellow))))
|
||||
|
||||
;; Message mode (email composition)
|
||||
`(message-header-name ((,class (:foreground ,purple :weight bold))))
|
||||
`(message-header-cc ((,class (:foreground ,blue))))
|
||||
`(message-header-newsgroups ((,class (:foreground ,yellow))))
|
||||
`(message-header-other ((,class (:foreground ,cyan))))
|
||||
`(message-header-subject ((,class (:foreground ,green :weight bold))))
|
||||
`(message-header-to ((,class (:foreground ,blue :weight bold))))
|
||||
`(message-header-xheader ((,class (:foreground ,fg-dim))))
|
||||
`(message-mml ((,class (:foreground ,orange :weight bold))))
|
||||
`(message-separator ((,class (:foreground ,fg-dim :strike-through t))))
|
||||
`(message-cited-text ((,class (:foreground ,fg-dim :slant italic))))
|
||||
`(message-cited-text-1 ((,class (:foreground ,blue))))
|
||||
`(message-cited-text-2 ((,class (:foreground ,green))))
|
||||
`(message-cited-text-3 ((,class (:foreground ,yellow))))
|
||||
`(message-cited-text-4 ((,class (:foreground ,cyan))))
|
||||
|
||||
;; Notmuch (alternative email client)
|
||||
`(notmuch-crypto-decryption ((,class (:foreground ,green))))
|
||||
`(notmuch-crypto-signature-bad ((,class (:foreground ,red))))
|
||||
`(notmuch-crypto-signature-good ((,class (:foreground ,green))))
|
||||
`(notmuch-crypto-signature-good-key ((,class (:foreground ,green :weight bold))))
|
||||
`(notmuch-crypto-signature-unknown ((,class (:foreground ,yellow))))
|
||||
`(notmuch-search-date ((,class (:foreground ,purple))))
|
||||
`(notmuch-search-count ((,class (:foreground ,fg-dim))))
|
||||
`(notmuch-search-flagged-face ((,class (:foreground ,red))))
|
||||
`(notmuch-search-matching-authors ((,class (:foreground ,blue))))
|
||||
`(notmuch-search-non-matching-authors ((,class (:foreground ,fg-dim))))
|
||||
`(notmuch-search-subject ((,class (:foreground ,fg-main))))
|
||||
`(notmuch-search-unread-face ((,class (:foreground ,cyan :weight bold))))
|
||||
`(notmuch-tag-added ((,class (:foreground ,green :weight bold))))
|
||||
`(notmuch-tag-deleted ((,class (:foreground ,red :strike-through t))))
|
||||
`(notmuch-tag-face ((,class (:foreground ,yellow))))
|
||||
`(notmuch-tag-flagged ((,class (:foreground ,red :weight bold))))
|
||||
`(notmuch-tag-unread ((,class (:foreground ,cyan :weight bold))))
|
||||
`(notmuch-tree-match-author-face ((,class (:foreground ,blue))))
|
||||
`(notmuch-tree-match-date-face ((,class (:foreground ,purple))))
|
||||
`(notmuch-tree-match-subject-face ((,class (:foreground ,fg-main))))
|
||||
`(notmuch-tree-match-tag-face ((,class (:foreground ,yellow))))
|
||||
`(notmuch-tree-no-match-face ((,class (:foreground ,fg-dim))))))
|
||||
|
||||
;;;###autoload
|
||||
(when load-file-name
|
||||
(add-to-list 'custom-theme-load-path
|
||||
(file-name-as-directory (file-name-directory load-file-name))))
|
||||
|
||||
;;;###autoload
|
||||
(defun developer-dark-theme-reload ()
|
||||
"Reload the developer-dark theme."
|
||||
(interactive)
|
||||
(disable-theme 'developer-dark)
|
||||
(load-theme 'developer-dark t))
|
||||
|
||||
(provide-theme 'developer-dark)
|
||||
|
||||
;;; developer-dark-theme.el ends here
|
||||
767
lisp/elfeed-config.el
Normal file
767
lisp/elfeed-config.el
Normal file
@@ -0,0 +1,767 @@
|
||||
;;; elfeed-config.el --- Elfeed RSS reader configuration -*- lexical-binding: t -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Configuration for Elfeed RSS reader with custom faces and filtering functions
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Install required packages if not already installed
|
||||
(use-package elfeed
|
||||
:ensure t
|
||||
:bind (("C-x w" . elfeed))
|
||||
:config
|
||||
;; Set default search filter to show entries from last 2 weeks
|
||||
(setq elfeed-search-filter "@2-weeks-ago +unread")
|
||||
|
||||
;; Store database in .emacs.d
|
||||
(setq elfeed-db-directory (expand-file-name "elfeed" user-emacs-directory))
|
||||
|
||||
;; Refresh feeds on startup
|
||||
(setq elfeed-search-remain-on-entry t)
|
||||
|
||||
;; Set update interval
|
||||
(setq elfeed-search-title-max-width 100)
|
||||
(setq elfeed-search-title-min-width 30)
|
||||
|
||||
;; Sorting configuration
|
||||
(setq elfeed-sort-order 'descending)
|
||||
(setq elfeed-search-clipboard-type 'CLIPBOARD)
|
||||
|
||||
;; Async configuration for non-blocking updates
|
||||
;; Use curl for better performance and async fetching
|
||||
(setq elfeed-use-curl t)
|
||||
(elfeed-set-timeout 30)
|
||||
|
||||
;; Increase number of concurrent fetches for faster updates
|
||||
(setq elfeed-curl-max-connections 10)
|
||||
|
||||
;; Don't block Emacs while fetching
|
||||
(setq elfeed-curl-extra-arguments '("--insecure" "--location"))
|
||||
|
||||
;; Make search updates async
|
||||
(setq elfeed-search-update-hook nil)
|
||||
|
||||
;; Background update function that doesn't block UI
|
||||
(defun elfeed-update-async ()
|
||||
"Update elfeed feeds asynchronously without blocking the UI."
|
||||
(interactive)
|
||||
(message "Starting background feed update...")
|
||||
(elfeed-update)
|
||||
(run-with-timer 1 nil
|
||||
(lambda ()
|
||||
(message "Feed update complete!"))))
|
||||
|
||||
;; Store timer references so we can cancel them
|
||||
(defvar elfeed-update-timer-30min nil
|
||||
"Timer for 30-minute elfeed updates.")
|
||||
|
||||
;; Auto-update feeds every 30 minutes in the background
|
||||
;; Delayed start to avoid impacting startup performance
|
||||
(setq elfeed-update-timer-30min
|
||||
(run-with-timer (* 5 60) (* 30 60) #'elfeed-update-async))
|
||||
|
||||
;; Custom function for fuzzy relative timestamps
|
||||
(defun my-elfeed-search-format-date (date)
|
||||
"Format DATE as a fuzzy relative time string."
|
||||
(let* ((now (float-time))
|
||||
(time (float-time date))
|
||||
(diff (- now time))
|
||||
(sec diff)
|
||||
(min (/ diff 60))
|
||||
(hour (/ diff 3600))
|
||||
(day (/ diff 86400))
|
||||
(week (/ diff 604800))
|
||||
(month (/ diff 2592000))
|
||||
(year (/ diff 31536000)))
|
||||
(cond
|
||||
((< sec 60) "just now")
|
||||
((< min 2) "1 min ago")
|
||||
((< min 60) (format "%d mins ago" (truncate min)))
|
||||
((< hour 2) "1 hour ago")
|
||||
((< hour 24) (format "%d hours ago" (truncate hour)))
|
||||
((< day 2) "yesterday")
|
||||
((< day 7) (format "%d days ago" (truncate day)))
|
||||
((< week 2) "1 week ago")
|
||||
((< week 4) (format "%d weeks ago" (truncate week)))
|
||||
((< month 2) "1 month ago")
|
||||
((< month 12) (format "%d months ago" (truncate month)))
|
||||
((< year 2) "1 year ago")
|
||||
(t (format "%d years ago" (truncate year))))))
|
||||
|
||||
;; Override the elfeed print function after elfeed loads
|
||||
(with-eval-after-load 'elfeed-search
|
||||
(defun elfeed-search-print-entry--default (entry)
|
||||
"Print ENTRY to the buffer with custom date format."
|
||||
(let* ((date (elfeed-entry-date entry))
|
||||
(date-str (my-elfeed-search-format-date date))
|
||||
(date-str (elfeed-format-column date-str 15 :left))
|
||||
(title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
|
||||
(title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
|
||||
(feed (elfeed-entry-feed entry))
|
||||
(feed-title
|
||||
(when feed
|
||||
(or (elfeed-meta feed :title) (elfeed-feed-title feed))))
|
||||
(tags (mapcar #'symbol-name (elfeed-entry-tags entry)))
|
||||
(tags-str (mapconcat
|
||||
(lambda (s) (propertize s 'face 'elfeed-search-tag-face))
|
||||
tags ","))
|
||||
(title-width (- (window-width) 10 elfeed-search-trailing-width 15))
|
||||
(title-column (elfeed-format-column
|
||||
title (elfeed-clamp
|
||||
elfeed-search-title-min-width
|
||||
title-width
|
||||
elfeed-search-title-max-width)
|
||||
:left)))
|
||||
(insert (propertize date-str 'face 'elfeed-search-date-face) " ")
|
||||
(insert (propertize title-column 'face title-faces 'kbd-help title) " ")
|
||||
(when feed-title
|
||||
(insert (propertize feed-title 'face 'elfeed-search-feed-face) " "))
|
||||
(when tags
|
||||
(insert "(" tags-str ")"))))
|
||||
|
||||
;; Set our custom function as the printer
|
||||
(setq elfeed-search-print-entry-function #'elfeed-search-print-entry--default))
|
||||
|
||||
;; Use standard date format (this is just for the column specification)
|
||||
(setq elfeed-search-date-format '("%Y-%m-%d" 15 :left))
|
||||
|
||||
;; Face customization for different tags
|
||||
(defface elfeed-face-tag-news
|
||||
'((t :foreground "#8B4513"))
|
||||
"Face for news-tagged entries")
|
||||
|
||||
(defface elfeed-face-tag-tech
|
||||
'((t :foreground "#4682B4"))
|
||||
"Face for tech-tagged entries")
|
||||
|
||||
(defface elfeed-face-tag-security
|
||||
'((t :foreground "#DC143C"))
|
||||
"Face for security-tagged entries")
|
||||
|
||||
(defface elfeed-face-tag-programming
|
||||
'((t :foreground "#228B22"))
|
||||
"Face for programming-tagged entries")
|
||||
|
||||
(defface elfeed-face-tag-opensource
|
||||
'((t :foreground "#FF8C00"))
|
||||
"Face for opensource-tagged entries")
|
||||
|
||||
;; Apply faces to tags
|
||||
(setq elfeed-search-face-alist
|
||||
'((news elfeed-face-tag-news)
|
||||
(tech elfeed-face-tag-tech)
|
||||
(security elfeed-face-tag-security)
|
||||
(programming elfeed-face-tag-programming)
|
||||
(opensource elfeed-face-tag-opensource))))
|
||||
|
||||
(use-package elfeed-org
|
||||
:ensure t
|
||||
:after elfeed
|
||||
:init
|
||||
;; Ensure elfeed-feeds variable exists
|
||||
(defvar elfeed-feeds nil)
|
||||
:config
|
||||
;; Load feeds from org file
|
||||
(setq rmh-elfeed-org-files (list (expand-file-name "elfeed.org" user-emacs-directory)))
|
||||
;; Initialize elfeed-org properly
|
||||
(elfeed-org)
|
||||
;; Process the org files to populate elfeed-feeds
|
||||
(when (fboundp 'rmh-elfeed-org-process)
|
||||
(rmh-elfeed-org-process rmh-elfeed-org-files rmh-elfeed-org-tree-id)))
|
||||
|
||||
;; Optional: Web browser for opening links
|
||||
;; Detect the operating system and set the appropriate browser
|
||||
(cond
|
||||
((eq system-type 'darwin) ; macOS
|
||||
(setq browse-url-browser-function 'browse-url-default-macosx-browser))
|
||||
((eq system-type 'gnu/linux) ; Linux
|
||||
(setq browse-url-browser-function 'browse-url-firefox))
|
||||
(t ; Default fallback
|
||||
(setq browse-url-browser-function 'browse-url-default-browser)))
|
||||
|
||||
;; Keybindings for elfeed
|
||||
(with-eval-after-load 'elfeed
|
||||
(define-key elfeed-search-mode-map (kbd "j") 'next-line)
|
||||
(define-key elfeed-search-mode-map (kbd "k") 'previous-line)
|
||||
(define-key elfeed-search-mode-map (kbd "m") 'elfeed-search-toggle-all-star)
|
||||
(define-key elfeed-search-mode-map (kbd "u") 'elfeed-search-toggle-all-unread)
|
||||
(define-key elfeed-search-mode-map (kbd "U") 'elfeed-update-async)
|
||||
(define-key elfeed-search-mode-map (kbd "f") 'elfeed-search-live-filter))
|
||||
|
||||
;; Function to reload elfeed-org configuration
|
||||
(defun elfeed-org-reload ()
|
||||
"Reload elfeed feeds from org files."
|
||||
(interactive)
|
||||
(when (featurep 'elfeed-org)
|
||||
(setq elfeed-feeds nil)
|
||||
(rmh-elfeed-org-process rmh-elfeed-org-files rmh-elfeed-org-tree-id)
|
||||
(message "Elfeed feeds reloaded from org files. %d feeds loaded." (length elfeed-feeds))))
|
||||
|
||||
;; Store timer reference for hourly updates
|
||||
(defvar elfeed-update-timer-hourly nil
|
||||
"Timer for hourly elfeed updates.")
|
||||
|
||||
;; Update feeds every hour
|
||||
(setq elfeed-update-timer-hourly
|
||||
(run-at-time 0 (* 60 60) 'elfeed-update))
|
||||
|
||||
;; Functions to control auto-updates
|
||||
(defun elfeed-stop-auto-updates ()
|
||||
"Stop all automatic elfeed feed updates."
|
||||
(interactive)
|
||||
(when (timerp elfeed-update-timer-30min)
|
||||
(cancel-timer elfeed-update-timer-30min)
|
||||
(setq elfeed-update-timer-30min nil))
|
||||
(when (timerp elfeed-update-timer-hourly)
|
||||
(cancel-timer elfeed-update-timer-hourly)
|
||||
(setq elfeed-update-timer-hourly nil))
|
||||
(message "Elfeed auto-updates stopped."))
|
||||
|
||||
(defun elfeed-start-auto-updates ()
|
||||
"Start automatic elfeed feed updates."
|
||||
(interactive)
|
||||
(elfeed-stop-auto-updates) ; Stop any existing timers first
|
||||
(setq elfeed-update-timer-30min
|
||||
(run-with-timer (* 5 60) (* 30 60) #'elfeed-update-async))
|
||||
(setq elfeed-update-timer-hourly
|
||||
(run-at-time 0 (* 60 60) 'elfeed-update))
|
||||
(message "Elfeed auto-updates started."))
|
||||
|
||||
;; Sorting functions
|
||||
(defun elfeed-sort-by-date-ascending ()
|
||||
"Sort elfeed entries by date ascending (oldest first)."
|
||||
(interactive)
|
||||
(setq elfeed-sort-order 'ascending)
|
||||
(setf elfeed-search-sort-function nil) ; nil means use default date sorting
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted by date: oldest first"))
|
||||
|
||||
(defun elfeed-sort-by-date-descending ()
|
||||
"Sort elfeed entries by date descending (newest first)."
|
||||
(interactive)
|
||||
(setq elfeed-sort-order 'descending)
|
||||
(setf elfeed-search-sort-function nil) ; nil means use default date sorting
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted by date: newest first"))
|
||||
|
||||
(defun elfeed-sort-by-title ()
|
||||
"Sort elfeed entries alphabetically by title."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(string< (downcase (elfeed-entry-title a))
|
||||
(downcase (elfeed-entry-title b)))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted alphabetically by title"))
|
||||
|
||||
(defun elfeed-sort-by-title-reverse ()
|
||||
"Sort elfeed entries reverse alphabetically by title."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(string> (downcase (elfeed-entry-title a))
|
||||
(downcase (elfeed-entry-title b)))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted reverse alphabetically by title"))
|
||||
|
||||
(defun elfeed-sort-by-feed ()
|
||||
"Sort elfeed entries by feed source name."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(let ((feed-a (elfeed-feed-title (elfeed-entry-feed a)))
|
||||
(feed-b (elfeed-feed-title (elfeed-entry-feed b))))
|
||||
(or (string< feed-a feed-b)
|
||||
(and (string= feed-a feed-b)
|
||||
(> (elfeed-entry-date a) (elfeed-entry-date b)))))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted by feed source"))
|
||||
|
||||
(defun elfeed-sort-by-tags ()
|
||||
"Sort elfeed entries by their first tag alphabetically."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(let ((tags-a (mapcar #'symbol-name (elfeed-entry-tags a)))
|
||||
(tags-b (mapcar #'symbol-name (elfeed-entry-tags b))))
|
||||
(string< (or (car (sort tags-a #'string<)) "")
|
||||
(or (car (sort tags-b #'string<)) "")))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted by tags"))
|
||||
|
||||
(defun elfeed-sort-by-author ()
|
||||
"Sort elfeed entries by author (if available)."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(let ((author-a (or (elfeed-meta a :author) ""))
|
||||
(author-b (or (elfeed-meta b :author) "")))
|
||||
(string< author-a author-b))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted by author"))
|
||||
|
||||
(defun elfeed-sort-by-unread-first ()
|
||||
"Sort elfeed entries with unread entries first, then by date."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(let ((a-unread (member 'unread (elfeed-entry-tags a)))
|
||||
(b-unread (member 'unread (elfeed-entry-tags b))))
|
||||
(cond
|
||||
((and a-unread (not b-unread)) t)
|
||||
((and (not a-unread) b-unread) nil)
|
||||
(t (> (elfeed-entry-date a) (elfeed-entry-date b)))))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted: unread first"))
|
||||
|
||||
(defun elfeed-sort-by-starred-first ()
|
||||
"Sort elfeed entries with starred entries first, then by date."
|
||||
(interactive)
|
||||
(setf elfeed-search-sort-function
|
||||
(lambda (a b)
|
||||
(let ((a-star (member 'star (elfeed-entry-tags a)))
|
||||
(b-star (member 'star (elfeed-entry-tags b))))
|
||||
(cond
|
||||
((and a-star (not b-star)) t)
|
||||
((and (not a-star) b-star) nil)
|
||||
(t (> (elfeed-entry-date a) (elfeed-entry-date b)))))))
|
||||
(elfeed-search-update :force)
|
||||
(message "Sorted: starred first"))
|
||||
|
||||
(defun elfeed-sort-reset ()
|
||||
"Reset to default sorting (by date, newest first)."
|
||||
(interactive)
|
||||
(setq elfeed-sort-order 'descending)
|
||||
(setf elfeed-search-sort-function nil) ; nil means use default date sorting
|
||||
(elfeed-search-update :force)
|
||||
(message "Reset to default sorting (newest first)"))
|
||||
|
||||
;; Helper function to show only specific categories
|
||||
(defun elfeed-show-news ()
|
||||
"Show only news entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +news"))
|
||||
|
||||
(defun elfeed-show-tech ()
|
||||
"Show only tech entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +tech"))
|
||||
|
||||
(defun elfeed-show-security ()
|
||||
"Show only security entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +security"))
|
||||
|
||||
(defun elfeed-show-programming ()
|
||||
"Show only programming entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +programming"))
|
||||
|
||||
(defun elfeed-show-opensource ()
|
||||
"Show only open source entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +opensource"))
|
||||
|
||||
;; Location-based filters
|
||||
(defun elfeed-show-munich ()
|
||||
"Show only Munich/Bavaria news."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +munich"))
|
||||
|
||||
(defun elfeed-show-bavaria ()
|
||||
"Show only Bavaria news."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +bavaria"))
|
||||
|
||||
(defun elfeed-show-germany ()
|
||||
"Show only German news."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +germany"))
|
||||
|
||||
(defun elfeed-show-europe ()
|
||||
"Show only European/EU news."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +eu"))
|
||||
|
||||
(defun elfeed-show-us ()
|
||||
"Show only US news."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +us"))
|
||||
|
||||
(defun elfeed-show-world ()
|
||||
"Show only world news."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +world"))
|
||||
|
||||
;; Language filters
|
||||
(defun elfeed-show-german-language ()
|
||||
"Show only German language feeds."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +de"))
|
||||
|
||||
;; Programming language filters
|
||||
(defun elfeed-show-cpp ()
|
||||
"Show only C++ entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +cpp"))
|
||||
|
||||
(defun elfeed-show-python ()
|
||||
"Show only Python entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +python"))
|
||||
|
||||
(defun elfeed-show-qt ()
|
||||
"Show only Qt entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread +qt"))
|
||||
|
||||
;; Combined filters
|
||||
(defun elfeed-show-today ()
|
||||
"Show entries from today only."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@1-day-ago +unread"))
|
||||
|
||||
(defun elfeed-show-starred ()
|
||||
"Show starred entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "+star"))
|
||||
|
||||
(defun elfeed-show-all ()
|
||||
"Show all unread entries."
|
||||
(interactive)
|
||||
(elfeed-search-set-filter "@2-weeks-ago +unread"))
|
||||
|
||||
(defun elfeed-mark-all-read ()
|
||||
"Mark all entries as read."
|
||||
(interactive)
|
||||
(mark-whole-buffer)
|
||||
(elfeed-search-untag-all-unread))
|
||||
|
||||
;; Advanced filter prompt
|
||||
(defun elfeed-filter-by-source ()
|
||||
"Filter by specific feed source."
|
||||
(interactive)
|
||||
(let* ((feeds (elfeed-feed-list))
|
||||
(titles (mapcar (lambda (url)
|
||||
(or (plist-get (elfeed-db-get-feed url) :title)
|
||||
url))
|
||||
feeds))
|
||||
(selected (completing-read "Select feed: " titles)))
|
||||
(elfeed-search-set-filter
|
||||
(format "@2-weeks-ago +unread =%s" selected))))
|
||||
|
||||
;; Custom search function
|
||||
(defun elfeed-search-custom ()
|
||||
"Prompt for a custom search filter."
|
||||
(interactive)
|
||||
(let ((filter (read-string "Enter filter: " elfeed-search-filter)))
|
||||
(elfeed-search-set-filter filter)))
|
||||
|
||||
;; Help function to show available filters
|
||||
(defun elfeed-show-filter-help ()
|
||||
"Show available filter and sorting keybindings."
|
||||
(interactive)
|
||||
(with-current-buffer (get-buffer-create "*Elfeed Help*")
|
||||
(erase-buffer)
|
||||
(insert "=== Elfeed Keybindings ===\n\n")
|
||||
(insert "LOCATION FILTERS (l + key):\n")
|
||||
(insert " l m - Munich news\n")
|
||||
(insert " l b - Bavaria news\n")
|
||||
(insert " l g - Germany news\n")
|
||||
(insert " l e - Europe/EU news\n")
|
||||
(insert " l u - US news\n")
|
||||
(insert " l w - World news\n")
|
||||
(insert " l l - German language feeds\n")
|
||||
(insert " l a - All unread entries\n\n")
|
||||
|
||||
(insert "CATEGORY FILTERS (C + key):\n")
|
||||
(insert " C n - News\n")
|
||||
(insert " C t - Technology\n")
|
||||
(insert " C s - Security\n")
|
||||
(insert " C p - Programming\n")
|
||||
(insert " C o - Open Source\n")
|
||||
(insert " C c - C++\n")
|
||||
(insert " C y - Python\n")
|
||||
(insert " C q - Qt\n")
|
||||
(insert " C * - Starred entries\n")
|
||||
(insert " C d - Today's entries\n")
|
||||
(insert " C a - All unread\n\n")
|
||||
|
||||
(insert "ORDERING/SORTING (o + key):\n")
|
||||
(insert " o d - Date descending (newest first)\n")
|
||||
(insert " o D - Date ascending (oldest first)\n")
|
||||
(insert " o t - Title (A-Z)\n")
|
||||
(insert " o T - Title (Z-A)\n")
|
||||
(insert " o f - Feed source\n")
|
||||
(insert " o g - Tags/categories\n")
|
||||
(insert " o a - Author\n")
|
||||
(insert " o u - Unread first\n")
|
||||
(insert " o s - Starred first\n")
|
||||
(insert " o r - Reset to default\n\n")
|
||||
|
||||
(insert "OTHER KEYS:\n")
|
||||
(insert " RET - Read entry\n")
|
||||
(insert " r - Mark as read\n")
|
||||
(insert " u - Mark as unread\n")
|
||||
(insert " m - Toggle star\n")
|
||||
(insert " s - Live filter\n")
|
||||
(insert " S - Set filter\n")
|
||||
(insert " c - Clear filter\n")
|
||||
(insert " g - Refresh\n")
|
||||
(insert " G - Fetch feeds\n")
|
||||
(insert " U - Update feeds\n")
|
||||
(insert " b - Browse URL\n")
|
||||
(insert " B - Open in browser\n")
|
||||
(insert " E - Open in EWW\n")
|
||||
(insert " q - Quit\n")
|
||||
(insert " ? - This help\n")
|
||||
|
||||
(goto-char (point-min))
|
||||
(special-mode)
|
||||
(display-buffer (current-buffer))))
|
||||
|
||||
;; Article reading functions
|
||||
(defun elfeed-show-entry-in-eww ()
|
||||
"Open the current elfeed entry in eww browser."
|
||||
(interactive)
|
||||
(let ((entry (elfeed-search-selected :single)))
|
||||
(when entry
|
||||
(eww (elfeed-entry-link entry))
|
||||
(add-hook 'eww-after-render-hook 'eww-readable nil t))))
|
||||
|
||||
(defun elfeed-search-eww-open (&optional use-generic-p)
|
||||
"Open the current elfeed entry in eww.
|
||||
If USE-GENERIC-P is non-nil, use eww-readable after loading."
|
||||
(interactive "P")
|
||||
(let ((entries (elfeed-search-selected)))
|
||||
(cl-loop for entry in entries
|
||||
do (elfeed-untag entry 'unread)
|
||||
when (elfeed-entry-link entry)
|
||||
do (eww (elfeed-entry-link entry)))
|
||||
(when use-generic-p
|
||||
(add-hook 'eww-after-render-hook 'eww-readable nil t))
|
||||
(unless (use-region-p) (forward-line))
|
||||
(elfeed-search-update-entry)))
|
||||
|
||||
(defun elfeed-show-eww-open (&optional use-generic-p)
|
||||
"Open the current elfeed show entry in eww.
|
||||
If USE-GENERIC-P is non-nil, use eww-readable after loading."
|
||||
(interactive "P")
|
||||
(let ((link (elfeed-entry-link elfeed-show-entry)))
|
||||
(when link
|
||||
(eww link)
|
||||
(when use-generic-p
|
||||
(add-hook 'eww-after-render-hook 'eww-readable nil t)))))
|
||||
|
||||
;; Fetch and display article content in elfeed-show buffer
|
||||
(defun elfeed-show-refresh-mail-style ()
|
||||
"Refresh the current elfeed entry, fetching full content and displaying it."
|
||||
(interactive)
|
||||
(let ((link (elfeed-entry-link elfeed-show-entry)))
|
||||
(when link
|
||||
(message "Fetching full article content...")
|
||||
(url-retrieve
|
||||
link
|
||||
(lambda (status)
|
||||
(if (plist-get status :error)
|
||||
(message "Error fetching article: %s" (plist-get status :error))
|
||||
(let ((html (buffer-string))
|
||||
(inhibit-read-only t))
|
||||
(with-current-buffer (get-buffer "*elfeed-entry*")
|
||||
(erase-buffer)
|
||||
(insert (format "Title: %s\n" (elfeed-entry-title elfeed-show-entry)))
|
||||
(insert (format "Feed: %s\n" (elfeed-feed-title (elfeed-entry-feed elfeed-show-entry))))
|
||||
(insert (format "Date: %s\n" (format-time-string "%Y-%m-%d %H:%M" (elfeed-entry-date elfeed-show-entry))))
|
||||
(insert (format "Link: %s\n\n" link))
|
||||
(insert "--- Full Article ---\n\n")
|
||||
(let ((shr-width (- (window-width) 5))
|
||||
(shr-max-image-proportion 0.7))
|
||||
(shr-render-region (point) (point-max)))
|
||||
(goto-char (point-min))))))))))
|
||||
|
||||
;; Enhanced readable mode for elfeed
|
||||
(defun elfeed-show-readable ()
|
||||
"Make the current elfeed entry more readable by extracting main content."
|
||||
(interactive)
|
||||
(let ((inhibit-read-only t))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (search-forward "\n\n" nil t)
|
||||
(let ((shr-width (min 80 (- (window-width) 5)))
|
||||
(shr-max-image-proportion 0.6)
|
||||
(shr-use-fonts nil))
|
||||
(shr-render-region (point) (point-max)))))
|
||||
(text-scale-increase 1)
|
||||
(olivetti-mode 1)))
|
||||
|
||||
;; Toggle between summary and full article
|
||||
(defvar-local elfeed-show-full-article-p nil
|
||||
"Whether the full article is currently displayed.")
|
||||
|
||||
(defun elfeed-show-toggle-full-article ()
|
||||
"Toggle between entry summary and full article content."
|
||||
(interactive)
|
||||
(if elfeed-show-full-article-p
|
||||
(progn
|
||||
(elfeed-show-refresh)
|
||||
(setq-local elfeed-show-full-article-p nil)
|
||||
(message "Showing summary"))
|
||||
(let ((link (elfeed-entry-link elfeed-show-entry)))
|
||||
(when link
|
||||
(message "Fetching full article...")
|
||||
(url-retrieve
|
||||
link
|
||||
(lambda (status)
|
||||
(if (plist-get status :error)
|
||||
(message "Error fetching article: %s" (plist-get status :error))
|
||||
(let ((html (buffer-substring (point) (point-max)))
|
||||
(inhibit-read-only t))
|
||||
(with-current-buffer (get-buffer "*elfeed-entry*")
|
||||
(let ((pos (point)))
|
||||
(erase-buffer)
|
||||
(elfeed-show-refresh)
|
||||
(goto-char (point-max))
|
||||
(insert "\n\n--- Full Article ---\n\n")
|
||||
(let ((start (point)))
|
||||
(insert html)
|
||||
(shr-render-region start (point-max))
|
||||
(goto-char pos))
|
||||
(setq-local elfeed-show-full-article-p t)
|
||||
(message "Showing full article")))))))))))
|
||||
|
||||
;; Open in external browser as fallback
|
||||
(defun elfeed-open-in-browser ()
|
||||
"Open current entry in external browser."
|
||||
(interactive)
|
||||
(let ((entry (if (eq major-mode 'elfeed-show-mode)
|
||||
elfeed-show-entry
|
||||
(elfeed-search-selected :single))))
|
||||
(when entry
|
||||
(browse-url (elfeed-entry-link entry)))))
|
||||
|
||||
(defun elfeed-show-open-image-at-point ()
|
||||
"Open image at point in external viewer (useful in terminal mode)."
|
||||
(interactive)
|
||||
(let ((url (or (get-text-property (point) 'image-url)
|
||||
(get-text-property (point) 'shr-url)
|
||||
(thing-at-point 'url))))
|
||||
(if url
|
||||
(progn
|
||||
(if (display-graphic-p)
|
||||
;; In GUI mode, try to display inline
|
||||
(browse-url url)
|
||||
;; In terminal mode, open with external viewer
|
||||
(start-process "image-viewer" nil "xdg-open" url))
|
||||
(message "Opening image: %s" url))
|
||||
(message "No image found at point"))))
|
||||
|
||||
;; Create ordering/sorting keymap
|
||||
(defvar elfeed-ordering-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "d") 'elfeed-sort-by-date-descending)
|
||||
(define-key map (kbd "D") 'elfeed-sort-by-date-ascending)
|
||||
(define-key map (kbd "t") 'elfeed-sort-by-title)
|
||||
(define-key map (kbd "T") 'elfeed-sort-by-title-reverse)
|
||||
(define-key map (kbd "f") 'elfeed-sort-by-feed)
|
||||
(define-key map (kbd "g") 'elfeed-sort-by-tags)
|
||||
(define-key map (kbd "a") 'elfeed-sort-by-author)
|
||||
(define-key map (kbd "u") 'elfeed-sort-by-unread-first)
|
||||
(define-key map (kbd "s") 'elfeed-sort-by-starred-first)
|
||||
(define-key map (kbd "r") 'elfeed-sort-reset)
|
||||
map)
|
||||
"Keymap for ordering/sorting entries in elfeed.")
|
||||
|
||||
;; Create location filter keymap
|
||||
(defvar elfeed-location-filter-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "m") 'elfeed-show-munich)
|
||||
(define-key map (kbd "b") 'elfeed-show-bavaria)
|
||||
(define-key map (kbd "g") 'elfeed-show-germany)
|
||||
(define-key map (kbd "e") 'elfeed-show-europe)
|
||||
(define-key map (kbd "u") 'elfeed-show-us)
|
||||
(define-key map (kbd "w") 'elfeed-show-world)
|
||||
(define-key map (kbd "a") 'elfeed-show-all)
|
||||
(define-key map (kbd "l") 'elfeed-show-german-language)
|
||||
map)
|
||||
"Keymap for location-based filters in elfeed.")
|
||||
|
||||
;; Create category filter keymap
|
||||
(defvar elfeed-category-filter-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "n") 'elfeed-show-news)
|
||||
(define-key map (kbd "t") 'elfeed-show-tech)
|
||||
(define-key map (kbd "s") 'elfeed-show-security)
|
||||
(define-key map (kbd "p") 'elfeed-show-programming)
|
||||
(define-key map (kbd "o") 'elfeed-show-opensource)
|
||||
(define-key map (kbd "c") 'elfeed-show-cpp)
|
||||
(define-key map (kbd "y") 'elfeed-show-python)
|
||||
(define-key map (kbd "q") 'elfeed-show-qt)
|
||||
(define-key map (kbd "*") 'elfeed-show-starred)
|
||||
(define-key map (kbd "a") 'elfeed-show-all)
|
||||
(define-key map (kbd "d") 'elfeed-show-today)
|
||||
map)
|
||||
"Keymap for category-based filters in elfeed.")
|
||||
|
||||
;; Bind keys for article viewing
|
||||
(with-eval-after-load 'elfeed
|
||||
;; In search mode
|
||||
(define-key elfeed-search-mode-map (kbd "E") 'elfeed-search-eww-open)
|
||||
(define-key elfeed-search-mode-map (kbd "B") 'elfeed-open-in-browser)
|
||||
|
||||
;; Bind location filters with 'l' prefix
|
||||
(define-key elfeed-search-mode-map (kbd "l") elfeed-location-filter-map)
|
||||
|
||||
;; Bind category filters with 'c' prefix (note: 'c' currently clears filter, so using 'C')
|
||||
(define-key elfeed-search-mode-map (kbd "C") elfeed-category-filter-map)
|
||||
|
||||
;; Bind ordering/sorting with 'o' prefix
|
||||
(define-key elfeed-search-mode-map (kbd "o") elfeed-ordering-map)
|
||||
|
||||
;; Bind help function
|
||||
(define-key elfeed-search-mode-map (kbd "?") 'elfeed-show-filter-help)
|
||||
|
||||
;; In show mode
|
||||
(define-key elfeed-show-mode-map (kbd "E") 'elfeed-show-eww-open)
|
||||
(define-key elfeed-show-mode-map (kbd "R") 'elfeed-show-readable)
|
||||
(define-key elfeed-show-mode-map (kbd "F") 'elfeed-show-toggle-full-article)
|
||||
(define-key elfeed-show-mode-map (kbd "B") 'elfeed-open-in-browser)
|
||||
(define-key elfeed-show-mode-map (kbd "I") 'elfeed-show-open-image-at-point))
|
||||
|
||||
;; Disable line numbers in elfeed buffers and increase font size
|
||||
(add-hook 'elfeed-show-mode-hook
|
||||
(lambda ()
|
||||
(display-line-numbers-mode -1)
|
||||
(setq-local display-line-numbers nil)
|
||||
(text-scale-set 1) ; Increase font size by 1 step
|
||||
;; Handle images based on display type
|
||||
(if (display-graphic-p)
|
||||
;; GUI mode - show images inline
|
||||
(progn
|
||||
(setq-local shr-inhibit-images nil)
|
||||
(setq-local shr-blocked-images nil))
|
||||
;; Terminal mode - show placeholders
|
||||
(setq-local shr-inhibit-images t))))
|
||||
|
||||
(add-hook 'elfeed-search-mode-hook
|
||||
(lambda ()
|
||||
(display-line-numbers-mode -1)
|
||||
(setq-local display-line-numbers nil)))
|
||||
|
||||
;; Disable line numbers in EWW
|
||||
(add-hook 'eww-mode-hook
|
||||
(lambda ()
|
||||
(display-line-numbers-mode -1)
|
||||
(setq-local display-line-numbers nil)))
|
||||
|
||||
;; Additional function to force disable line numbers in elfeed
|
||||
(defun elfeed-disable-line-numbers ()
|
||||
"Forcefully disable line numbers in elfeed buffers."
|
||||
(interactive)
|
||||
(when (or (eq major-mode 'elfeed-search-mode)
|
||||
(eq major-mode 'elfeed-show-mode))
|
||||
(display-line-numbers-mode -1)
|
||||
(setq-local display-line-numbers nil)
|
||||
(message "Line numbers disabled in elfeed")))
|
||||
|
||||
(provide 'elfeed-config)
|
||||
;;; elfeed-config.el ends here
|
||||
241
lisp/emacs-dev-config-modern.el
Normal file
241
lisp/emacs-dev-config-modern.el
Normal file
@@ -0,0 +1,241 @@
|
||||
;;; emacs-dev-config-modern.el --- Modern development configuration using Eglot -*- lexical-binding: t -*-
|
||||
;;; Commentary:
|
||||
;;; Development configuration using built-in Eglot instead of lsp-mode
|
||||
;;; This is a lighter, faster alternative to the original dev config
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defvar dev-mode-modern-enabled nil
|
||||
"Flag indicating whether modern development mode is enabled.")
|
||||
|
||||
(defvar dev-mode-modern-packages
|
||||
'(;; Core development tools
|
||||
eglot ; Only needed for Emacs < 29
|
||||
corfu corfu-terminal cape ; Modern completion
|
||||
consult-eglot ; Consult integration with Eglot
|
||||
flycheck ; Can still use alongside Flymake
|
||||
yasnippet
|
||||
projectile
|
||||
ggtags
|
||||
multiple-cursors expand-region
|
||||
hl-todo rainbow-delimiters
|
||||
origami ; Code folding
|
||||
|
||||
;; Version control
|
||||
magit
|
||||
forge ; GitHub/GitLab integration
|
||||
magit-delta ; Better diffs if delta is installed
|
||||
treemacs-magit
|
||||
|
||||
;; Languages
|
||||
clang-format
|
||||
qml-mode
|
||||
|
||||
;; Debugging
|
||||
dap-mode)
|
||||
"List of packages for modern development mode.")
|
||||
|
||||
(defun dev-mode-modern-ensure-packages ()
|
||||
"Ensure all modern development packages are installed."
|
||||
(dolist (package dev-mode-modern-packages)
|
||||
(unless (or (package-installed-p package)
|
||||
(and (eq package 'eglot) (fboundp 'eglot))) ; Eglot is built-in for Emacs 29+
|
||||
(package-refresh-contents)
|
||||
(package-install package))))
|
||||
|
||||
(defun dev-mode-modern-setup-eglot ()
|
||||
"Setup Eglot for modern development."
|
||||
;; Load eglot configuration
|
||||
(require 'init-eglot)
|
||||
|
||||
;; Additional Eglot configuration for development
|
||||
(with-eval-after-load 'eglot
|
||||
;; Enable format on save for specific modes
|
||||
(dolist (mode '(c-mode-hook c++-mode-hook python-mode-hook))
|
||||
(add-hook mode #'eglot-format-buffer-on-save))
|
||||
|
||||
;; Configure Consult-Eglot if available
|
||||
(when (fboundp 'consult-eglot-symbols)
|
||||
(define-key eglot-mode-map (kbd "C-c l s") 'consult-eglot-symbols))))
|
||||
|
||||
(defun dev-mode-modern-setup-completion ()
|
||||
"Setup modern completion with Corfu."
|
||||
;; Corfu is already configured in init-completion.el
|
||||
;; Add development-specific configurations here
|
||||
(with-eval-after-load 'corfu
|
||||
;; More aggressive completion in programming modes
|
||||
(add-hook 'prog-mode-hook
|
||||
(lambda ()
|
||||
(setq-local corfu-auto-delay 0.1)
|
||||
(setq-local corfu-auto-prefix 1)))))
|
||||
|
||||
(defun dev-mode-modern-setup-yasnippet ()
|
||||
"Configure yasnippet for code snippets."
|
||||
(use-package yasnippet
|
||||
:ensure t
|
||||
:config
|
||||
(yas-global-mode 1)
|
||||
;; Load snippets from the snippets directory if it exists
|
||||
(let ((snippets-dir (expand-file-name "snippets" user-emacs-directory)))
|
||||
(when (file-directory-p snippets-dir)
|
||||
(add-to-list 'yas-snippet-dirs snippets-dir)))))
|
||||
|
||||
(defun dev-mode-modern-setup-flycheck ()
|
||||
"Configure Flycheck alongside Flymake."
|
||||
(use-package flycheck
|
||||
:ensure t
|
||||
:init
|
||||
;; Use Flycheck for modes not well-supported by Flymake
|
||||
(add-hook 'sh-mode-hook 'flycheck-mode)
|
||||
(add-hook 'json-mode-hook 'flycheck-mode)
|
||||
(add-hook 'yaml-mode-hook 'flycheck-mode)
|
||||
:config
|
||||
(setq flycheck-display-errors-delay 0.3)))
|
||||
|
||||
(defun dev-mode-modern-setup-projectile ()
|
||||
"Configure projectile for project management."
|
||||
;; Already configured in init-project.el
|
||||
;; Add development-specific configurations here
|
||||
(with-eval-after-load 'projectile
|
||||
(define-key projectile-command-map (kbd "t") 'projectile-test-project)
|
||||
(define-key projectile-command-map (kbd "c") 'projectile-compile-project)))
|
||||
|
||||
(defun dev-mode-modern-setup-magit ()
|
||||
"Configure Magit for version control."
|
||||
(use-package magit
|
||||
:ensure t
|
||||
:bind (("C-x g" . magit-status)
|
||||
("C-x M-g" . magit-dispatch)
|
||||
("C-c g" . magit-file-dispatch))
|
||||
:config
|
||||
(setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))
|
||||
|
||||
;; Forge for GitHub/GitLab integration
|
||||
(use-package forge
|
||||
:ensure t
|
||||
:after magit)
|
||||
|
||||
;; Magit-delta for better diffs (if delta is installed)
|
||||
(when (executable-find "delta")
|
||||
(use-package magit-delta
|
||||
:ensure t
|
||||
:hook (magit-mode . magit-delta-mode))))
|
||||
|
||||
(defun dev-mode-modern-setup-debugging ()
|
||||
"Configure debugging with dap-mode."
|
||||
(use-package dap-mode
|
||||
:ensure t
|
||||
:commands (dap-debug dap-debug-edit-template)
|
||||
;; Don't auto-enable - only load when explicitly needed for debugging
|
||||
:config
|
||||
;; Python debugging
|
||||
(require 'dap-python)
|
||||
;; C/C++ debugging
|
||||
(require 'dap-gdb-lldb)
|
||||
;; Don't auto-configure globally - causes severe performance issues
|
||||
;; Enable manually when debugging: M-x dap-auto-configure-mode
|
||||
;; (dap-auto-configure-mode 1) ; Commented out for performance
|
||||
))
|
||||
|
||||
(defun dev-mode-modern-setup-languages ()
|
||||
"Configure language-specific settings."
|
||||
;; C/C++ formatting
|
||||
(use-package clang-format
|
||||
:ensure t
|
||||
:bind (:map c-mode-map
|
||||
("C-c C-f" . clang-format-buffer)
|
||||
:map c++-mode-map
|
||||
("C-c C-f" . clang-format-buffer)))
|
||||
|
||||
;; QML support
|
||||
(use-package qml-mode
|
||||
:ensure t
|
||||
:mode "\\.qml\\'"
|
||||
;; Removed qml-mode hook - Qt5 has no language server
|
||||
))
|
||||
|
||||
(defun dev-mode-modern-setup-editing-tools ()
|
||||
"Setup advanced editing tools for development."
|
||||
;; These are now in init-qol.el, but we can add dev-specific configs
|
||||
(with-eval-after-load 'hl-todo
|
||||
(add-hook 'prog-mode-hook #'hl-todo-mode))
|
||||
|
||||
(with-eval-after-load 'rainbow-delimiters
|
||||
(add-hook 'prog-mode-hook #'rainbow-delimiters-mode))
|
||||
|
||||
;; Origami for code folding
|
||||
(use-package origami
|
||||
:ensure t
|
||||
:hook (prog-mode . origami-mode)
|
||||
:bind (:map origami-mode-map
|
||||
("C-c o f" . origami-toggle-node)
|
||||
("C-c o o" . origami-open-node)
|
||||
("C-c o c" . origami-close-node)
|
||||
("C-c o a" . origami-close-all-nodes)
|
||||
("C-c o A" . origami-open-all-nodes))))
|
||||
|
||||
(defun dev-mode-modern-setup-keybindings ()
|
||||
"Setup development-specific keybindings."
|
||||
;; Compile commands
|
||||
(global-set-key (kbd "<f5>") 'compile)
|
||||
(global-set-key (kbd "<f6>") 'recompile)
|
||||
|
||||
;; Testing - use C-c C-t prefix to avoid conflict with CUA copy
|
||||
(global-set-key (kbd "C-c C-t p") 'projectile-test-project)
|
||||
(global-set-key (kbd "C-c C-t f") 'projectile-test-file)
|
||||
|
||||
;; Navigation
|
||||
(global-set-key (kbd "M-.") 'xref-find-definitions)
|
||||
(global-set-key (kbd "M-,") 'xref-pop-marker-stack)
|
||||
(global-set-key (kbd "M-?") 'xref-find-references))
|
||||
|
||||
;;;###autoload
|
||||
(defun enable-dev-mode-modern ()
|
||||
"Enable modern development mode with Eglot and other tools."
|
||||
(interactive)
|
||||
(if dev-mode-modern-enabled
|
||||
(message "Modern development mode is already enabled")
|
||||
(message "Enabling modern development mode...")
|
||||
;; Ensure packages are installed
|
||||
(dev-mode-modern-ensure-packages)
|
||||
;; Set up all development features
|
||||
(dev-mode-modern-setup-eglot)
|
||||
(dev-mode-modern-setup-completion)
|
||||
(dev-mode-modern-setup-yasnippet)
|
||||
(dev-mode-modern-setup-flycheck)
|
||||
(dev-mode-modern-setup-projectile)
|
||||
(dev-mode-modern-setup-magit)
|
||||
(dev-mode-modern-setup-debugging)
|
||||
(dev-mode-modern-setup-languages)
|
||||
(dev-mode-modern-setup-editing-tools)
|
||||
(dev-mode-modern-setup-keybindings)
|
||||
;; Load tree-sitter if available
|
||||
(when (file-exists-p (expand-file-name "lisp/init-treesitter.el" user-emacs-directory))
|
||||
(require 'init-treesitter))
|
||||
;; Stop elfeed auto-updates to prevent UI lag
|
||||
(when (fboundp 'elfeed-stop-auto-updates)
|
||||
(elfeed-stop-auto-updates))
|
||||
(setq dev-mode-modern-enabled t)
|
||||
(message "Modern development mode enabled! Eglot will auto-start for supported files. Elfeed auto-updates disabled.")))
|
||||
|
||||
;;;###autoload
|
||||
(defun disable-dev-mode-modern ()
|
||||
"Disable modern development mode."
|
||||
(interactive)
|
||||
(if (not dev-mode-modern-enabled)
|
||||
(message "Modern development mode is not enabled")
|
||||
(message "Disabling modern development mode...")
|
||||
;; Shutdown all Eglot servers
|
||||
(when (fboundp 'eglot-shutdown-all)
|
||||
(eglot-shutdown-all))
|
||||
;; Disable some modes
|
||||
(yas-global-mode -1)
|
||||
(global-flycheck-mode -1)
|
||||
;; Re-enable elfeed auto-updates
|
||||
(when (fboundp 'elfeed-start-auto-updates)
|
||||
(elfeed-start-auto-updates))
|
||||
(setq dev-mode-modern-enabled nil)
|
||||
(message "Modern development mode disabled. Elfeed auto-updates re-enabled.")))
|
||||
|
||||
(provide 'emacs-dev-config-modern)
|
||||
;;; emacs-dev-config-modern.el ends here
|
||||
619
lisp/emacs-dev-config.el
Normal file
619
lisp/emacs-dev-config.el
Normal file
@@ -0,0 +1,619 @@
|
||||
;;; -*- lexical-binding: t -*-
|
||||
;;; emacs-dev-config.el --- Development configuration for Emacs
|
||||
;;; Commentary:
|
||||
;;; Development-related configurations including LSP, languages, debugging, etc.
|
||||
;;; Use M-x enable-dev-mode to activate this configuration
|
||||
;;; Use M-x disable-dev-mode to deactivate this configuration
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defvar dev-mode-enabled nil
|
||||
"Flag indicating whether development mode is enabled.")
|
||||
|
||||
(defvar dev-mode-packages
|
||||
`(;; Development tools
|
||||
lsp-mode lsp-ui lsp-treemacs
|
||||
company company-box
|
||||
flycheck yasnippet
|
||||
projectile
|
||||
ggtags multiple-cursors expand-region
|
||||
hl-todo rainbow-delimiters
|
||||
origami ;; Code folding
|
||||
|
||||
;; Version control
|
||||
magit
|
||||
,@(when (executable-find "delta") '(magit-delta)) ;; Only if delta is installed
|
||||
treemacs-magit
|
||||
|
||||
;; Helm integration for development
|
||||
helm-lsp helm-xref helm-projectile
|
||||
|
||||
;; Languages
|
||||
clang-format qml-mode company-qml
|
||||
|
||||
;; Debugging
|
||||
dap-mode)
|
||||
"List of packages required for development mode.")
|
||||
|
||||
(defun dev-mode-ensure-packages ()
|
||||
"Ensure all development packages are installed."
|
||||
(dolist (package dev-mode-packages)
|
||||
(unless (package-installed-p package)
|
||||
(package-refresh-contents)
|
||||
(package-install package))))
|
||||
|
||||
(defun dev-mode-setup-lsp ()
|
||||
"Configure LSP mode for development."
|
||||
(use-package lsp-mode
|
||||
:ensure t
|
||||
:hook ((c-mode c++-mode python-mode) . lsp-deferred) ; Removed qml-mode - Qt5 has no LSP
|
||||
:commands (lsp lsp-deferred)
|
||||
:config
|
||||
(setq lsp-keymap-prefix "C-c l")
|
||||
(setq lsp-idle-delay 0.5)
|
||||
(setq lsp-log-io nil)
|
||||
(setq lsp-completion-enable t)
|
||||
(setq lsp-headerline-breadcrumb-enable t)
|
||||
(setq lsp-enable-snippet t)
|
||||
(setq lsp-enable-semantic-highlighting t))
|
||||
|
||||
(use-package lsp-ui
|
||||
:ensure t
|
||||
:commands lsp-ui-mode
|
||||
:config
|
||||
(setq lsp-ui-doc-enable t)
|
||||
(setq lsp-ui-doc-position 'bottom)
|
||||
(setq lsp-ui-doc-delay 1)
|
||||
(setq lsp-ui-sideline-enable t)
|
||||
(setq lsp-ui-sideline-show-hover t)
|
||||
(setq lsp-ui-sideline-show-diagnostics t)
|
||||
(setq lsp-ui-sideline-show-code-actions t))
|
||||
|
||||
(use-package helm-lsp
|
||||
:ensure t
|
||||
:commands helm-lsp-workspace-symbol)
|
||||
|
||||
(use-package lsp-treemacs
|
||||
:ensure t
|
||||
:commands lsp-treemacs-errors-list))
|
||||
|
||||
(defun dev-mode-setup-company ()
|
||||
"Configure company mode for auto-completion."
|
||||
(use-package company
|
||||
:ensure t
|
||||
:hook (after-init . global-company-mode)
|
||||
:bind (:map company-active-map
|
||||
("C-n" . company-select-next)
|
||||
("C-p" . company-select-previous)
|
||||
("TAB" . company-complete-selection))
|
||||
:config
|
||||
(setq company-idle-delay 0.2)
|
||||
(setq company-minimum-prefix-length 1)
|
||||
(setq company-selection-wrap-around t)
|
||||
(setq company-show-numbers t)
|
||||
(setq company-tooltip-align-annotations t))
|
||||
|
||||
(use-package company-box
|
||||
:ensure t
|
||||
:hook (company-mode . company-box-mode)))
|
||||
|
||||
(defun dev-mode-setup-flycheck ()
|
||||
"Configure flycheck for syntax checking."
|
||||
(use-package flycheck
|
||||
:ensure t
|
||||
:init (global-flycheck-mode)
|
||||
:config
|
||||
(setq flycheck-display-errors-delay 0.3)
|
||||
(setq flycheck-gcc-language-standard "c++17")
|
||||
(setq flycheck-clang-language-standard "c++17")))
|
||||
|
||||
(defun dev-mode-setup-yasnippet ()
|
||||
"Configure yasnippet for code snippets."
|
||||
(use-package yasnippet
|
||||
:ensure t
|
||||
:config
|
||||
(yas-global-mode 1)))
|
||||
|
||||
(defun dev-mode-setup-projectile ()
|
||||
"Configure projectile for project management."
|
||||
(use-package projectile
|
||||
:ensure t
|
||||
:init
|
||||
(projectile-mode +1)
|
||||
:bind-keymap ("C-c p" . projectile-command-map)
|
||||
:config
|
||||
(setq projectile-completion-system 'helm)
|
||||
(setq projectile-switch-project-action #'projectile-dired)
|
||||
(setq projectile-enable-caching t))
|
||||
|
||||
(use-package helm-projectile
|
||||
:ensure t
|
||||
:after (helm projectile)
|
||||
:config
|
||||
(helm-projectile-on)))
|
||||
|
||||
(defun dev-mode-setup-ggtags ()
|
||||
"Configure ggtags for code navigation."
|
||||
(use-package ggtags
|
||||
:ensure t
|
||||
:hook ((c-mode c++-mode python-mode) . ggtags-mode)
|
||||
:bind (:map ggtags-mode-map
|
||||
("C-c g s" . ggtags-find-other-symbol)
|
||||
("C-c g h" . ggtags-view-tag-history)
|
||||
("C-c g r" . ggtags-find-reference)
|
||||
("C-c g f" . ggtags-find-file)
|
||||
("C-c g c" . ggtags-create-tags))
|
||||
:config
|
||||
(setq ggtags-completing-read-function nil)
|
||||
(setq ggtags-navigation-mode-lighter nil)
|
||||
(setq ggtags-mode-line-project-name nil)))
|
||||
|
||||
(defun dev-mode-setup-origami ()
|
||||
"Configure Origami for code folding."
|
||||
(use-package origami
|
||||
:ensure t
|
||||
:config
|
||||
;; Define global keybindings for origami (using C-c o prefix to avoid conflict)
|
||||
(global-set-key (kbd "C-c o f") 'origami-toggle-node)
|
||||
(global-set-key (kbd "C-c o o") 'origami-open-node)
|
||||
(global-set-key (kbd "C-c o c") 'origami-close-node)
|
||||
(global-set-key (kbd "C-c o a") 'origami-close-all-nodes)
|
||||
(global-set-key (kbd "C-c o A") 'origami-open-all-nodes)
|
||||
(global-set-key (kbd "C-c o t") 'origami-toggle-all-nodes)
|
||||
(global-set-key (kbd "C-c o r") 'origami-recursively-toggle-node)
|
||||
(global-set-key (kbd "C-c o R") 'origami-open-node-recursively)
|
||||
(global-set-key (kbd "C-c o n") 'origami-next-fold)
|
||||
(global-set-key (kbd "C-c o p") 'origami-previous-fold)
|
||||
(global-set-key (kbd "C-c o s") 'origami-show-only-node)
|
||||
(global-set-key (kbd "C-c o u") 'origami-undo)
|
||||
(global-set-key (kbd "C-c o d") 'origami-redo)
|
||||
|
||||
;; Setup origami
|
||||
(setq origami-show-fold-header t)
|
||||
|
||||
;; Enable origami mode globally for programming modes
|
||||
(global-origami-mode 1)
|
||||
|
||||
;; Add hook to ensure origami works in prog-mode buffers
|
||||
(add-hook 'prog-mode-hook 'origami-mode)
|
||||
|
||||
;; Initialize parsers
|
||||
(origami-mode 1)
|
||||
|
||||
;; Face customization for fold markers (only if faces exist)
|
||||
(when (facep 'origami-fold-header-face)
|
||||
(set-face-attribute 'origami-fold-header-face nil
|
||||
:box nil
|
||||
:foreground "dim gray"))
|
||||
(when (facep 'origami-fold-fringe-face)
|
||||
(set-face-attribute 'origami-fold-fringe-face nil
|
||||
:foreground "dim gray"))))
|
||||
|
||||
(defun dev-mode-setup-editing-tools ()
|
||||
"Configure advanced editing tools."
|
||||
(use-package multiple-cursors
|
||||
:ensure t
|
||||
:bind (("C-S-l" . mc/edit-lines)
|
||||
("C-S-d" . mc/mark-all-like-this)
|
||||
("C->" . mc/mark-next-like-this)
|
||||
("C-<" . mc/mark-previous-like-this)
|
||||
("C-c M n" . mc/skip-to-next-like-this)
|
||||
("C-c M p" . mc/skip-to-previous-like-this)
|
||||
("C-S-<mouse-1>" . mc/add-cursor-on-click)))
|
||||
|
||||
(use-package expand-region
|
||||
:ensure t
|
||||
:bind ("C-=" . er/expand-region))
|
||||
|
||||
(use-package hl-todo
|
||||
:ensure t
|
||||
:hook (prog-mode . hl-todo-mode)
|
||||
:config
|
||||
(setq hl-todo-keyword-faces
|
||||
'(("TODO" . "#FF0000")
|
||||
("FIXME" . "#FF0000")
|
||||
("DEBUG" . "#A020F0")
|
||||
("GOTCHA" . "#FF4500")
|
||||
("STUB" . "#1E90FF"))))
|
||||
|
||||
(use-package rainbow-delimiters
|
||||
:ensure t
|
||||
:hook (prog-mode . rainbow-delimiters-mode)))
|
||||
|
||||
(defun dev-mode-setup-languages ()
|
||||
"Configure language-specific settings."
|
||||
;; C/C++ Configuration
|
||||
(use-package cc-mode
|
||||
:ensure nil
|
||||
:mode (("\\.cpp\\'" . c++-mode)
|
||||
("\\.hpp\\'" . c++-mode)
|
||||
("\\.cc\\'" . c++-mode)
|
||||
("\\.hh\\'" . c++-mode)
|
||||
("\\.cxx\\'" . c++-mode)
|
||||
("\\.hxx\\'" . c++-mode))
|
||||
:config
|
||||
(setq c-default-style "linux"
|
||||
c-basic-offset 4)
|
||||
(add-hook 'c++-mode-hook 'electric-pair-local-mode)
|
||||
(add-hook 'c++-mode-hook
|
||||
(lambda ()
|
||||
(setq compile-command
|
||||
(concat "g++ -Wall -Wextra -std=c++17 -g -o "
|
||||
(file-name-sans-extension (buffer-name))
|
||||
" "
|
||||
(buffer-name)))
|
||||
(local-set-key (kbd "C-c c") 'compile)
|
||||
(local-set-key (kbd "C-c r") 'gdb)
|
||||
(local-set-key (kbd "C-c C-c") 'recompile))))
|
||||
|
||||
(use-package clang-format
|
||||
:ensure t
|
||||
:bind (:map c++-mode-map
|
||||
("C-c C-f" . clang-format-buffer)
|
||||
:map c-mode-map
|
||||
("C-c C-f" . clang-format-buffer)))
|
||||
|
||||
;; Python Configuration
|
||||
(use-package python
|
||||
:ensure nil
|
||||
:mode ("\\.py\\'" . python-mode)
|
||||
:config
|
||||
(setq python-indent-offset 4)
|
||||
(setq python-shell-interpreter "python3")
|
||||
(add-hook 'python-mode-hook
|
||||
(lambda ()
|
||||
(setq compile-command (concat "python3 " (buffer-name)))
|
||||
(local-set-key (kbd "C-c c") 'compile)
|
||||
(local-set-key (kbd "C-c r") 'run-python)
|
||||
(local-set-key (kbd "C-c C-c") 'python-shell-send-buffer))))
|
||||
|
||||
;; QML Configuration - Always use qml-mode for .qml files (not JavaScript mode)
|
||||
(use-package qml-mode
|
||||
:ensure t
|
||||
:mode ("\\.qml\\'" . qml-mode)
|
||||
:init
|
||||
;; Remove any potential js-mode associations for .qml files
|
||||
(setq auto-mode-alist (delete '("\\.qml\\'" . js-mode) auto-mode-alist))
|
||||
(setq auto-mode-alist (delete '("\\.qml\\'" . javascript-mode) auto-mode-alist))
|
||||
(setq auto-mode-alist (delete '("\\.qml\\'" . js2-mode) auto-mode-alist))
|
||||
(setq auto-mode-alist (delete '("\\.qml\\'" . js3-mode) auto-mode-alist))
|
||||
(setq auto-mode-alist (delete '("\\.qml\\'" . rjsx-mode) auto-mode-alist))
|
||||
:config
|
||||
;; Ensure qml-mode is at the front of auto-mode-alist
|
||||
(add-to-list 'auto-mode-alist '("\\.qml\\'" . qml-mode))
|
||||
(message "QML mode configured for .qml files"))
|
||||
|
||||
(use-package company-qml
|
||||
:ensure t
|
||||
:after (company qml-mode)
|
||||
:config
|
||||
(add-to-list 'company-backends 'company-qml)))
|
||||
|
||||
(defun dev-mode-setup-magit ()
|
||||
"Configure Magit for version control."
|
||||
(use-package magit
|
||||
:ensure t
|
||||
:bind (("C-x g" . magit-status)
|
||||
("C-x M-g" . magit-dispatch)
|
||||
("C-c g" . magit-file-dispatch))
|
||||
:config
|
||||
;; Configure Magit integration
|
||||
(setq vc-follow-symlinks t)
|
||||
(setq vc-display-status t) ; Enable to show VC status in mode line
|
||||
|
||||
;; Configure Magit status sections
|
||||
(setq magit-status-sections-hook
|
||||
'(magit-insert-status-headers
|
||||
magit-insert-merge-log
|
||||
magit-insert-rebase-sequence
|
||||
magit-insert-am-sequence
|
||||
magit-insert-sequencer-sequence
|
||||
magit-insert-bisect-output
|
||||
magit-insert-bisect-rest
|
||||
magit-insert-bisect-log
|
||||
magit-insert-untracked-files
|
||||
magit-insert-unstaged-changes
|
||||
magit-insert-staged-changes
|
||||
magit-insert-stashes
|
||||
magit-insert-unpushed-to-pushremote
|
||||
magit-insert-unpushed-to-upstream-or-recent
|
||||
magit-insert-unpulled-from-pushremote
|
||||
magit-insert-unpulled-from-upstream
|
||||
magit-insert-recent-commits))
|
||||
|
||||
(setq magit-log-section-commit-count 10)
|
||||
(setq magit-log-arguments '("--graph" "--color" "--decorate" "--all"))
|
||||
(setq magit-log-section-arguments '("--graph" "--color" "--decorate" "-n256" "--all"))
|
||||
(setq magit-log-margin '(t "%Y-%m-%d %H:%M " magit-log-margin-width t 18))
|
||||
(setq magit-log-show-refname-after-summary t)
|
||||
(setq magit-diff-refine-hunk 'all))
|
||||
|
||||
;; Only enable magit-delta if delta is installed
|
||||
(when (executable-find "delta")
|
||||
(use-package magit-delta
|
||||
:ensure t
|
||||
:hook (magit-mode . magit-delta-mode)))
|
||||
|
||||
(use-package treemacs-magit
|
||||
:ensure t
|
||||
:after (treemacs magit))
|
||||
|
||||
;; Custom Magit functions
|
||||
(defun magit-save-commit-as-patch ()
|
||||
"Save the commit at point as a patch file."
|
||||
(interactive)
|
||||
(let* ((commit (or (magit-commit-at-point)
|
||||
(error "No commit at point")))
|
||||
(default-name (format "%s.patch"
|
||||
(substring commit 0 (min 8 (length commit)))))
|
||||
(file (read-file-name "Save patch to file: "
|
||||
default-directory
|
||||
default-name
|
||||
nil
|
||||
default-name))
|
||||
(default-directory (magit-toplevel)))
|
||||
(if (zerop (shell-command
|
||||
(format "git format-patch -1 %s --stdout > %s"
|
||||
(shell-quote-argument commit)
|
||||
(shell-quote-argument (expand-file-name file)))))
|
||||
(message "Patch saved to %s" file)
|
||||
(error "Failed to save patch"))))
|
||||
|
||||
;; Set up keybindings for saving patches
|
||||
(with-eval-after-load 'magit
|
||||
(define-key magit-revision-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch)
|
||||
(define-key magit-log-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch)
|
||||
(define-key magit-log-select-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch))
|
||||
|
||||
;; Also set up hooks to ensure keybindings are available
|
||||
(add-hook 'magit-revision-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
(add-hook 'magit-log-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
(add-hook 'magit-log-select-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
|
||||
;; Optional: Integrate diff-hl with Magit when both are available
|
||||
;; Only enable if you want Magit to control diff-hl updates
|
||||
;; Comment out these lines if diff-hl has issues with Magit
|
||||
(when (and (fboundp 'diff-hl-mode)
|
||||
(not (bound-and-true-p diff-hl-disable-magit-integration)))
|
||||
(add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
|
||||
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))
|
||||
|
||||
;; Fix common Magit issues
|
||||
(defun magit-fix-refresh ()
|
||||
"Fix Magit refresh issues."
|
||||
(interactive)
|
||||
(setq magit-refresh-verbose t)
|
||||
(setq magit-git-executable (executable-find "git"))
|
||||
(when (and buffer-file-name (vc-backend buffer-file-name))
|
||||
(vc-refresh-state))
|
||||
(magit-refresh)
|
||||
(message "Magit refresh fixed. Git executable: %s" magit-git-executable))
|
||||
|
||||
;; Diagnose Magit issues
|
||||
(defun magit-diagnose ()
|
||||
"Diagnose Magit configuration."
|
||||
(interactive)
|
||||
(let ((diagnosis '()))
|
||||
(push (format "Git executable: %s" (executable-find "git")) diagnosis)
|
||||
(push (format "Magit git executable: %s" magit-git-executable) diagnosis)
|
||||
(push (format "Delta installed: %s" (if (executable-find "delta") "yes" "no")) diagnosis)
|
||||
(push (format "Magit-delta mode: %s" (if (bound-and-true-p magit-delta-mode) "enabled" "disabled")) diagnosis)
|
||||
(push (format "Default directory: %s" default-directory) diagnosis)
|
||||
(push (format "In git repo: %s" (magit-toplevel)) diagnosis)
|
||||
(push (format "Git version: %s" (magit-git-version)) diagnosis)
|
||||
(push (format "Magit version: %s" (magit-version)) diagnosis)
|
||||
(message (mapconcat 'identity (nreverse diagnosis) "\n"))))
|
||||
|
||||
;; Disable magit-delta if causing issues
|
||||
(defun magit-disable-delta ()
|
||||
"Disable magit-delta mode."
|
||||
(interactive)
|
||||
(when (fboundp 'magit-delta-mode)
|
||||
(magit-delta-mode -1)
|
||||
(remove-hook 'magit-mode-hook 'magit-delta-mode)
|
||||
(message "Magit-delta disabled. Using standard git diff."))))
|
||||
|
||||
(defun dev-mode-setup-debugging ()
|
||||
"Configure debugging support."
|
||||
(use-package dap-mode
|
||||
:ensure t
|
||||
:commands (dap-debug dap-debug-edit-template)
|
||||
;; Don't auto-enable - only load when explicitly needed
|
||||
:config
|
||||
(require 'dap-python)
|
||||
(require 'dap-gdb-lldb)
|
||||
;; Don't auto-configure globally - causes performance issues
|
||||
;; (dap-auto-configure-mode 1) ; Commented out for performance
|
||||
(setq dap-auto-configure-features '(sessions locals controls tooltip))))
|
||||
|
||||
(defun dev-mode-custom-functions ()
|
||||
"Define custom development functions."
|
||||
(defun generate-cpp-tags ()
|
||||
"Generate TAGS file for C++ project."
|
||||
(interactive)
|
||||
(let ((project-root (or (projectile-project-root) default-directory)))
|
||||
(shell-command
|
||||
(format "cd %s && find . -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.hh' -o -name '*.c' -o -name '*.h' | etags -"
|
||||
project-root))
|
||||
(visit-tags-table (concat project-root "TAGS"))
|
||||
(message "C++ TAGS file generated for project: %s" project-root)))
|
||||
|
||||
(defun generate-python-tags ()
|
||||
"Generate TAGS file for Python project."
|
||||
(interactive)
|
||||
(let ((project-root (or (projectile-project-root) default-directory)))
|
||||
(shell-command
|
||||
(format "cd %s && find . -name '*.py' | etags -"
|
||||
project-root))
|
||||
(visit-tags-table (concat project-root "TAGS"))
|
||||
(message "Python TAGS file generated for project: %s" project-root)))
|
||||
|
||||
(defun generate-all-tags ()
|
||||
"Generate TAGS file for both C++ and Python files."
|
||||
(interactive)
|
||||
(let ((project-root (or (projectile-project-root) default-directory)))
|
||||
(shell-command
|
||||
(format "cd %s && find . -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.hh' -o -name '*.c' -o -name '*.h' -o -name '*.py' | etags -"
|
||||
project-root))
|
||||
(visit-tags-table (concat project-root "TAGS"))
|
||||
(message "TAGS file generated for all supported files in: %s" project-root)))
|
||||
|
||||
(defun quick-compile-and-run ()
|
||||
"Quick compile and run current file."
|
||||
(interactive)
|
||||
(save-buffer)
|
||||
(cond
|
||||
((derived-mode-p 'c++-mode)
|
||||
(shell-command
|
||||
(format "g++ -std=c++17 -o %s %s && ./%s"
|
||||
(file-name-sans-extension (buffer-name))
|
||||
(buffer-name)
|
||||
(file-name-sans-extension (buffer-name)))))
|
||||
((derived-mode-p 'python-mode)
|
||||
(shell-command (format "python3 %s" (buffer-name))))
|
||||
(t (message "Unsupported file type for quick compile and run")))))
|
||||
|
||||
(defun dev-mode-setup-keybindings ()
|
||||
"Set up development-specific keybindings."
|
||||
;; Tag generation - use C-c C-t prefix to avoid conflict with CUA copy
|
||||
(global-set-key (kbd "C-c C-t c") 'generate-cpp-tags)
|
||||
(global-set-key (kbd "C-c C-t p") 'generate-python-tags)
|
||||
(global-set-key (kbd "C-c C-t a") 'generate-all-tags)
|
||||
(global-set-key (kbd "C-c C-q") 'quick-compile-and-run)
|
||||
(global-set-key (kbd "M-.") 'xref-find-definitions)
|
||||
(global-set-key (kbd "M-,") 'xref-pop-marker-stack)
|
||||
(global-set-key (kbd "C-M-.") 'xref-find-references)
|
||||
|
||||
;; Helper function to refresh origami in current buffer
|
||||
(defun origami-reset-buffer ()
|
||||
"Reset origami in the current buffer."
|
||||
(interactive)
|
||||
(when (bound-and-true-p origami-mode)
|
||||
(origami-mode -1)
|
||||
(origami-mode 1)
|
||||
(message "Origami reset in buffer")))
|
||||
|
||||
(global-set-key (kbd "C-c f 0") 'origami-reset-buffer))
|
||||
|
||||
(defun show-dev-mode-help ()
|
||||
"Show key bindings for development configuration."
|
||||
(interactive)
|
||||
(with-output-to-temp-buffer "*Development Mode Help*"
|
||||
(princ "=== Development Mode Key Bindings ===\n\n")
|
||||
(princ "EDITING (CUA MODE):\n")
|
||||
(princ " C-c : Copy\n")
|
||||
(princ " C-v : Paste\n")
|
||||
(princ " C-x : Cut\n")
|
||||
(princ " C-z : Undo\n")
|
||||
(princ " C-<return> : Rectangle selection\n\n")
|
||||
(princ "LSP COMMANDS:\n")
|
||||
(princ " C-c l : LSP prefix for all LSP commands\n")
|
||||
(princ " M-. : Go to definition\n")
|
||||
(princ " M-, : Return from definition\n")
|
||||
(princ " C-M-. : Find references\n\n")
|
||||
(princ "TAGS & NAVIGATION:\n")
|
||||
(princ " C-c C-t c : Generate C++ TAGS file\n")
|
||||
(princ " C-c C-t p : Generate Python TAGS file\n")
|
||||
(princ " C-c C-t a : Generate TAGS for all files\n\n")
|
||||
(princ "COMPILATION & EXECUTION:\n")
|
||||
(princ " C-c c : Compile current file\n")
|
||||
(princ " C-c r : Run (GDB for C++, Python REPL for Python)\n")
|
||||
(princ " C-c C-q : Quick compile and run\n")
|
||||
(princ " C-c C-c : Recompile (C++) or Send buffer to Python\n\n")
|
||||
(princ "PROJECT MANAGEMENT:\n")
|
||||
(princ " C-c p : Projectile commands prefix\n\n")
|
||||
(princ "VERSION CONTROL (MAGIT):\n")
|
||||
(princ " C-x g : Magit status\n")
|
||||
(princ " C-x M-g : Magit dispatch\n")
|
||||
(princ " C-c g : Magit file dispatch\n")
|
||||
(princ " C-c C-p : Save commit as patch (in Magit buffers)\n\n")
|
||||
(princ "EDITING:\n")
|
||||
(princ " C-= : Expand region\n")
|
||||
(princ " C-> : Mark next like this (multiple cursors)\n")
|
||||
(princ " C-< : Mark previous like this\n")
|
||||
(princ " C-S-d : Mark all like this\n\n")
|
||||
(princ "CODE FOLDING (ORIGAMI):\n")
|
||||
(princ " C-c f f : Toggle fold at point\n")
|
||||
(princ " C-c f o : Open fold\n")
|
||||
(princ " C-c f c : Close fold\n")
|
||||
(princ " C-c f a : Close all folds\n")
|
||||
(princ " C-c f A : Open all folds\n")
|
||||
(princ " C-c f t : Toggle all folds\n")
|
||||
(princ " C-c f r : Recursively toggle fold\n")
|
||||
(princ " C-c f n/p : Next/Previous fold\n")
|
||||
(princ " C-c f s : Show only current fold\n\n")
|
||||
(princ "LANGUAGE MODES:\n")
|
||||
(princ " .qml files : Always use qml-mode (not JavaScript mode)\n")
|
||||
(princ " .py files : Python mode with LSP support\n")
|
||||
(princ " .cpp/.hpp : C++ mode with LSP support\n\n")
|
||||
(princ "DEVELOPMENT MODE:\n")
|
||||
(princ " M-x enable-dev-mode : Enable development mode\n")
|
||||
(princ " M-x disable-dev-mode : Disable development mode\n")
|
||||
(princ " C-c h : Show this help\n")))
|
||||
|
||||
;;;###autoload
|
||||
(defun enable-dev-mode ()
|
||||
"Enable development mode with all programming tools."
|
||||
(interactive)
|
||||
(if dev-mode-enabled
|
||||
(message "Development mode is already enabled")
|
||||
(message "Enabling development mode...")
|
||||
;; Ensure packages are installed
|
||||
(dev-mode-ensure-packages)
|
||||
;; Set up all development features
|
||||
(dev-mode-setup-lsp)
|
||||
(dev-mode-setup-company)
|
||||
(dev-mode-setup-flycheck)
|
||||
(dev-mode-setup-yasnippet)
|
||||
(dev-mode-setup-projectile)
|
||||
(dev-mode-setup-ggtags)
|
||||
(dev-mode-setup-origami)
|
||||
(dev-mode-setup-editing-tools)
|
||||
(dev-mode-setup-languages)
|
||||
(dev-mode-setup-magit)
|
||||
(dev-mode-setup-debugging)
|
||||
(dev-mode-custom-functions)
|
||||
(dev-mode-setup-keybindings)
|
||||
;; Ensure QML files use qml-mode (override any JS mode associations)
|
||||
(add-to-list 'auto-mode-alist '("\\.qml\\'" . qml-mode))
|
||||
;; Set up help command
|
||||
(global-set-key (kbd "C-c h") 'show-dev-mode-help)
|
||||
;; Stop elfeed auto-updates to prevent UI lag
|
||||
(when (fboundp 'elfeed-stop-auto-updates)
|
||||
(elfeed-stop-auto-updates))
|
||||
(setq dev-mode-enabled t)
|
||||
(message "Development mode enabled! Press C-c h for help. QML files will use qml-mode. Elfeed auto-updates disabled.")))
|
||||
|
||||
;;;###autoload
|
||||
(defun disable-dev-mode ()
|
||||
"Disable development mode."
|
||||
(interactive)
|
||||
(if (not dev-mode-enabled)
|
||||
(message "Development mode is not enabled")
|
||||
(message "Disabling development mode...")
|
||||
;; Disable major modes that can be toggled
|
||||
(global-company-mode -1)
|
||||
(global-flycheck-mode -1)
|
||||
(yas-global-mode -1)
|
||||
(projectile-mode -1)
|
||||
(global-hl-todo-mode -1)
|
||||
;; Re-enable elfeed auto-updates
|
||||
(when (fboundp 'elfeed-start-auto-updates)
|
||||
(elfeed-start-auto-updates))
|
||||
;; Note: Some modes might require restarting Emacs to fully disable
|
||||
(setq dev-mode-enabled nil)
|
||||
(message "Development mode disabled. Elfeed auto-updates re-enabled. Some features may require restarting Emacs to fully disable.")))
|
||||
|
||||
;;;###autoload
|
||||
(defun dev-mode-status ()
|
||||
"Check if development mode is enabled."
|
||||
(interactive)
|
||||
(if dev-mode-enabled
|
||||
(message "Development mode is ENABLED")
|
||||
(message "Development mode is DISABLED")))
|
||||
|
||||
(provide 'emacs-dev-config)
|
||||
;;; emacs-dev-config.el ends here
|
||||
254
lisp/god-mode-config.el
Normal file
254
lisp/god-mode-config.el
Normal file
@@ -0,0 +1,254 @@
|
||||
;;; god-mode-config.el --- God mode configuration for modal editing -*- lexical-binding: t -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; God mode provides modal editing without leaving Emacs paradigm
|
||||
;; Press ESC to toggle god-mode, where you can use Emacs commands without modifier keys
|
||||
;; For example: In god-mode, 'xf' = C-x C-f, 'xs' = C-x C-s
|
||||
|
||||
;;; Code:
|
||||
|
||||
(use-package god-mode
|
||||
:ensure t
|
||||
:bind (("<escape>" . god-mode-all)
|
||||
("C-x C-1" . delete-other-windows)
|
||||
("C-x C-2" . split-window-below)
|
||||
("C-x C-3" . split-window-right)
|
||||
("C-x C-0" . delete-window))
|
||||
:config
|
||||
;; Define keybindings after god-mode is loaded and map exists
|
||||
(with-eval-after-load 'god-mode
|
||||
(when (boundp 'god-local-mode-map)
|
||||
(define-key god-local-mode-map (kbd ".") 'repeat)
|
||||
(define-key god-local-mode-map (kbd "[") 'backward-paragraph)
|
||||
(define-key god-local-mode-map (kbd "]") 'forward-paragraph)
|
||||
(define-key god-local-mode-map (kbd "i") 'god-mode-all)))
|
||||
;; Update cursor to indicate god-mode state
|
||||
(defun god-mode-update-cursor ()
|
||||
"Update cursor style based on god-mode state."
|
||||
(setq cursor-type (if (or god-local-mode buffer-read-only)
|
||||
'box
|
||||
'bar)))
|
||||
|
||||
;; Change cursor color based on state
|
||||
(defun god-mode-update-cursor-color ()
|
||||
"Change cursor color to indicate god-mode state."
|
||||
(set-cursor-color (if (or god-local-mode buffer-read-only)
|
||||
"#ff7a85" ; Red cursor in god-mode
|
||||
"#61afef"))) ; Blue cursor in insert mode
|
||||
|
||||
(add-hook 'god-mode-enabled-hook 'god-mode-update-cursor)
|
||||
(add-hook 'god-mode-disabled-hook 'god-mode-update-cursor)
|
||||
(add-hook 'god-mode-enabled-hook 'god-mode-update-cursor-color)
|
||||
(add-hook 'god-mode-disabled-hook 'god-mode-update-cursor-color)
|
||||
|
||||
;; Update modeline to show god-mode state
|
||||
(defun god-mode-update-modeline ()
|
||||
"Update modeline to indicate god-mode state."
|
||||
(let ((limited-colors-p (> 257 (length (defined-colors)))))
|
||||
(cond (god-local-mode (progn
|
||||
(set-face-attribute 'mode-line nil
|
||||
:foreground "#604000"
|
||||
:background "#fff29a")
|
||||
(set-face-attribute 'mode-line-inactive nil
|
||||
:foreground "#3f3000"
|
||||
:background "#fff3da")))
|
||||
(t (progn
|
||||
(set-face-attribute 'mode-line nil
|
||||
:foreground (face-attribute 'mode-line :foreground)
|
||||
:background (face-attribute 'mode-line :background))
|
||||
(set-face-attribute 'mode-line-inactive nil
|
||||
:foreground (face-attribute 'mode-line-inactive :foreground)
|
||||
:background (face-attribute 'mode-line-inactive :background)))))))
|
||||
|
||||
;; Lighter modeline indicator
|
||||
(defun my-god-mode-update-modeline ()
|
||||
"Minimal modeline indicator for god-mode."
|
||||
(cond
|
||||
((bound-and-true-p god-local-mode)
|
||||
(set-face-attribute 'mode-line nil
|
||||
:background "#2d3640" ; Slightly different background
|
||||
:box '(:line-width 2 :color "#ff7a85"))) ; Red border
|
||||
(t
|
||||
(set-face-attribute 'mode-line nil
|
||||
:background "#232830" ; Normal background
|
||||
:box '(:line-width 1 :color "#3a4049"))))) ; Normal border
|
||||
|
||||
;; Use the lighter modeline update
|
||||
(add-hook 'god-mode-enabled-hook 'my-god-mode-update-modeline)
|
||||
(add-hook 'god-mode-disabled-hook 'my-god-mode-update-modeline)
|
||||
|
||||
;; Make certain commands exit god-mode automatically
|
||||
(defun god-mode-self-insert-exit ()
|
||||
"Exit god-mode when self-inserting."
|
||||
(when god-local-mode
|
||||
(god-mode-all)))
|
||||
|
||||
;; Optional: Exit god-mode on certain commands
|
||||
;; (add-to-list 'god-exempt-major-modes 'dired-mode)
|
||||
;; (add-to-list 'god-exempt-major-modes 'magit-mode)
|
||||
|
||||
;; Better integration with isearch
|
||||
(define-key isearch-mode-map (kbd "<escape>") 'god-mode-isearch-activate)
|
||||
(define-key god-local-mode-map (kbd "/") 'isearch-forward)
|
||||
(define-key god-local-mode-map (kbd "?") 'isearch-backward)
|
||||
|
||||
(defun god-mode-isearch-activate ()
|
||||
"Exit isearch and activate god-mode."
|
||||
(interactive)
|
||||
(isearch-exit)
|
||||
(god-mode-all))
|
||||
|
||||
;; Quick navigation bindings in god-mode
|
||||
(define-key god-local-mode-map (kbd "j") 'next-line)
|
||||
(define-key god-local-mode-map (kbd "k") 'previous-line)
|
||||
(define-key god-local-mode-map (kbd "h") 'backward-char)
|
||||
(define-key god-local-mode-map (kbd "l") 'forward-char)
|
||||
(define-key god-local-mode-map (kbd "w") 'forward-word)
|
||||
(define-key god-local-mode-map (kbd "b") 'backward-word)
|
||||
(define-key god-local-mode-map (kbd "e") 'move-end-of-line)
|
||||
(define-key god-local-mode-map (kbd "a") 'move-beginning-of-line)
|
||||
(define-key god-local-mode-map (kbd "v") 'scroll-up-command)
|
||||
(define-key god-local-mode-map (kbd "V") 'scroll-down-command)
|
||||
(define-key god-local-mode-map (kbd "g") 'keyboard-quit) ; Like C-g
|
||||
(define-key god-local-mode-map (kbd "u") 'undo)
|
||||
(define-key god-local-mode-map (kbd "/") 'isearch-forward)
|
||||
(define-key god-local-mode-map (kbd "?") 'isearch-backward)
|
||||
(define-key god-local-mode-map (kbd ">") 'end-of-buffer)
|
||||
(define-key god-local-mode-map (kbd "<") 'beginning-of-buffer)
|
||||
(define-key god-local-mode-map (kbd "SPC") 'set-mark-command)
|
||||
|
||||
;; Special god-mode specific commands
|
||||
(define-key god-local-mode-map (kbd "z") 'god-mode-all) ; Quick toggle
|
||||
|
||||
;; Support for literal key insertion
|
||||
(define-key god-local-mode-map (kbd "q") 'quoted-insert) ; Like C-q
|
||||
|
||||
;; Window management in god-mode (no C-x needed)
|
||||
(define-key god-local-mode-map (kbd "1") 'delete-other-windows)
|
||||
(define-key god-local-mode-map (kbd "2") 'split-window-below)
|
||||
(define-key god-local-mode-map (kbd "3") 'split-window-right)
|
||||
(define-key god-local-mode-map (kbd "0") 'delete-window)
|
||||
(define-key god-local-mode-map (kbd "o") 'other-window))
|
||||
|
||||
;; God-mode indicator in modeline
|
||||
(defun god-mode-modeline-indicator ()
|
||||
"Return a string indicating god-mode state."
|
||||
(cond
|
||||
((bound-and-true-p god-local-mode)
|
||||
(propertize " GOD " 'face '(:background "#ff7a85" :foreground "#1a1d23" :weight bold)))
|
||||
(t "")))
|
||||
|
||||
;; Add to mode-line
|
||||
(setq-default mode-line-format
|
||||
(cons '(:eval (god-mode-modeline-indicator))
|
||||
(default-value 'mode-line-format)))
|
||||
|
||||
;; Integration with other modes
|
||||
(defun god-mode-helm-integration ()
|
||||
"Better integration with Helm."
|
||||
(require 'helm nil t)
|
||||
(when (featurep 'helm)
|
||||
(define-key god-local-mode-map (kbd "xx") 'helm-M-x)
|
||||
(define-key god-local-mode-map (kbd "xf") 'helm-find-files)
|
||||
(define-key god-local-mode-map (kbd "xb") 'helm-buffers-list)
|
||||
(define-key god-local-mode-map (kbd "xa") 'helm-apropos)
|
||||
(define-key god-local-mode-map (kbd "xr") 'helm-recentf)))
|
||||
|
||||
(with-eval-after-load 'helm
|
||||
(god-mode-helm-integration))
|
||||
|
||||
;; Quick toggle function
|
||||
(defun god-mode-toggle ()
|
||||
"Toggle god-mode."
|
||||
(interactive)
|
||||
(god-mode-all))
|
||||
|
||||
;; Helper functions for common operations
|
||||
(defun god-mode-kill-line ()
|
||||
"Kill line in god-mode style."
|
||||
(interactive)
|
||||
(if god-local-mode
|
||||
(kill-line)
|
||||
(god-mode-all)
|
||||
(kill-line)))
|
||||
|
||||
(defun god-mode-save-buffer ()
|
||||
"Save buffer, works in both modes."
|
||||
(interactive)
|
||||
(save-buffer)
|
||||
(when god-local-mode
|
||||
(message "Buffer saved (god-mode active)")))
|
||||
|
||||
;; Bind common operations
|
||||
(global-set-key (kbd "C-c g") 'god-mode-all) ; Alternative toggle
|
||||
|
||||
;; Visual feedback for god-mode state changes
|
||||
(defun god-mode-bell ()
|
||||
"Visual bell for god-mode state changes."
|
||||
(let ((buf (current-buffer)))
|
||||
(with-current-buffer buf
|
||||
(inverse-video-mode)
|
||||
(run-with-timer 0.1 nil (lambda ()
|
||||
(with-current-buffer buf
|
||||
(inverse-video-mode)))))))
|
||||
|
||||
;; Optional: Add bell on state change
|
||||
;; (add-hook 'god-mode-enabled-hook 'god-mode-bell)
|
||||
;; (add-hook 'god-mode-disabled-hook 'god-mode-bell)
|
||||
|
||||
;; Cheat sheet function
|
||||
(defun god-mode-cheat-sheet ()
|
||||
"Display god-mode cheat sheet."
|
||||
(interactive)
|
||||
(with-output-to-temp-buffer "*God Mode Cheat Sheet*"
|
||||
(princ "GOD MODE CHEAT SHEET\n")
|
||||
(princ "====================\n\n")
|
||||
(princ "ACTIVATION:\n")
|
||||
(princ " ESC or C-c g : Toggle god-mode\n")
|
||||
(princ " i or z : Exit god-mode (return to insert)\n\n")
|
||||
(princ "MOVEMENT (in god-mode):\n")
|
||||
(princ " h/j/k/l : Left/Down/Up/Right (Vim-style)\n")
|
||||
(princ " w/b : Forward/Backward word\n")
|
||||
(princ " a/e : Beginning/End of line\n")
|
||||
(princ " </>/ : Beginning/End of buffer\n")
|
||||
(princ " [/] : Previous/Next paragraph\n\n")
|
||||
(princ "EDITING:\n")
|
||||
(princ " d : C-d (delete char)\n")
|
||||
(princ " k : C-k (kill line)\n")
|
||||
(princ " u : Undo\n")
|
||||
(princ " SPC : Set mark\n")
|
||||
(princ " y : C-y (yank/paste)\n")
|
||||
(princ " w : C-w (kill region)\n\n")
|
||||
(princ "COMMANDS (no C- needed):\n")
|
||||
(princ " xs : Save file (C-x C-s)\n")
|
||||
(princ " xf : Find file (C-x C-f)\n")
|
||||
(princ " xb : Switch buffer (C-x b)\n")
|
||||
(princ " xk : Kill buffer (C-x k)\n")
|
||||
(princ " xx : M-x\n\n")
|
||||
(princ "WINDOWS:\n")
|
||||
(princ " 1/2/3/0 : Delete other/Split below/Split right/Delete window\n")
|
||||
(princ " o : Other window\n\n")
|
||||
(princ "SEARCH:\n")
|
||||
(princ " // : Search forward\n")
|
||||
(princ " ? : Search backward\n")
|
||||
(princ " n : C-n (next line or search result)\n\n")
|
||||
(princ "SPECIAL:\n")
|
||||
(princ " . : Repeat last command\n")
|
||||
(princ " g : Keyboard quit (C-g)\n")
|
||||
(princ " q : Quoted insert (C-q)\n\n")
|
||||
(princ "TIP: Most C- commands work by just dropping the C- in god-mode!\n")
|
||||
(princ " For C-x sequences, just type x then the letter.\n")
|
||||
(princ " For M-x, type xx\n")))
|
||||
|
||||
(global-set-key (kbd "C-c G") 'god-mode-cheat-sheet)
|
||||
|
||||
;; Make god-mode play nice with company
|
||||
(with-eval-after-load 'company
|
||||
(define-key god-local-mode-map (kbd "TAB") 'company-indent-or-complete-common)
|
||||
(add-hook 'company-mode-hook
|
||||
(lambda ()
|
||||
(when (bound-and-true-p god-local-mode)
|
||||
(god-mode-all)))))
|
||||
|
||||
(provide 'god-mode-config)
|
||||
;;; god-mode-config.el ends here
|
||||
63
lisp/init-bungee.el
Normal file
63
lisp/init-bungee.el
Normal file
@@ -0,0 +1,63 @@
|
||||
;;; init-bungee.el --- Initialize Bungee symbol finder -*- lexical-binding: t; -*-
|
||||
|
||||
;; Load and configure the Bungee package
|
||||
(load-file "~/.emacs.d/bungee.el")
|
||||
(require 'bungee)
|
||||
|
||||
;; Configure cache directory
|
||||
(setq bungee-cache-directory ".symbol_cache") ; Use standard cache dir
|
||||
|
||||
;; Optional: Use Python indexer for better parsing
|
||||
;; Only set if the file exists
|
||||
(let ((python-indexer-path "~/sources/bungee/symbol_finder.py"))
|
||||
(when (file-exists-p (expand-file-name python-indexer-path))
|
||||
(setq bungee-python-indexer python-indexer-path)))
|
||||
|
||||
;; Optional: Save JSON cache for Python tool compatibility
|
||||
(setq bungee-save-json-cache nil) ; Set to t if you need Python compatibility
|
||||
|
||||
;; Enable auto-update when saving files
|
||||
(setq bungee-auto-update t)
|
||||
|
||||
;; Enable Bungee mode globally for supported files
|
||||
(global-bungee-mode 1)
|
||||
|
||||
;; Override M-. in QML and C++ files to use Bungee
|
||||
(add-hook 'qml-mode-hook
|
||||
(lambda ()
|
||||
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(local-set-key (kbd "M-?") 'bungee-find-references)
|
||||
(local-set-key (kbd "M-,") 'pop-tag-mark)))
|
||||
|
||||
(add-hook 'c++-mode-hook
|
||||
(lambda ()
|
||||
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(local-set-key (kbd "M-?") 'bungee-find-references)
|
||||
(local-set-key (kbd "M-,") 'pop-tag-mark)))
|
||||
|
||||
;; For .qml files if qml-mode is not available
|
||||
(add-to-list 'auto-mode-alist '("\\.qml\\'" . js-mode))
|
||||
(add-hook 'js-mode-hook
|
||||
(lambda ()
|
||||
(when (and buffer-file-name
|
||||
(string-match-p "\\.qml\\'" buffer-file-name))
|
||||
(bungee-mode 1)
|
||||
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(local-set-key (kbd "M-?") 'bungee-find-references)
|
||||
(local-set-key (kbd "M-,") 'pop-tag-mark))))
|
||||
|
||||
;; Convenient commands
|
||||
(defun bungee-reindex-project ()
|
||||
"Reindex the entire project using Python indexer."
|
||||
(interactive)
|
||||
(if bungee-python-indexer
|
||||
(bungee-index-with-python t)
|
||||
(bungee-index-directory nil t)))
|
||||
|
||||
(defun bungee-reindex-current-project ()
|
||||
"Force reindex current project."
|
||||
(interactive)
|
||||
(bungee-index-directory nil t))
|
||||
|
||||
(provide 'init-bungee)
|
||||
;;; init-bungee.el ends here
|
||||
@@ -167,6 +167,38 @@
|
||||
(corfu-on-exact-match nil) ;; Configure handling of exact matches
|
||||
(corfu-scroll-margin 5) ;; Use scroll margin
|
||||
|
||||
:config
|
||||
;; Fix font issues with Corfu child frames
|
||||
(defun corfu--fix-child-frame-font (frame)
|
||||
"Ensure child frames don't inherit problematic font specs."
|
||||
frame)
|
||||
|
||||
;; Override the frame parameters to avoid font issues
|
||||
(setq corfu--frame-parameters
|
||||
'((no-accept-focus . t)
|
||||
(no-focus-on-map . t)
|
||||
(min-width . t)
|
||||
(min-height . t)
|
||||
(border-width . 0)
|
||||
(outer-border-width . 0)
|
||||
(internal-border-width . 1)
|
||||
(child-frame-border-width . 1)
|
||||
(left-fringe . 7)
|
||||
(right-fringe . 7)
|
||||
(vertical-scroll-bars)
|
||||
(horizontal-scroll-bars)
|
||||
(menu-bar-lines . 0)
|
||||
(tool-bar-lines . 0)
|
||||
(tab-bar-lines . 0)
|
||||
(tab-bar-lines-keep-state . t)
|
||||
(no-other-frame . t)
|
||||
(unsplittable . t)
|
||||
(undecorated . t)
|
||||
(cursor-type)
|
||||
(no-special-glyphs . t)
|
||||
(desktop-dont-save . t)
|
||||
(inhibit-double-buffering . t)))
|
||||
|
||||
:init
|
||||
(global-corfu-mode))
|
||||
|
||||
@@ -194,7 +226,10 @@
|
||||
(let ((root (or (projectile-project-root) default-directory)))
|
||||
(consult-ripgrep root)))
|
||||
|
||||
;; Quick access to ripgrep - C-c r for backward compatibility
|
||||
(global-set-key (kbd "C-c r") 'consult-ripgrep-project-root)
|
||||
;; Additional quick binding for project search
|
||||
(global-set-key (kbd "C-c /") 'consult-ripgrep-project-root)
|
||||
|
||||
;;; Make completion work nicely with Projectile
|
||||
(with-eval-after-load 'projectile
|
||||
|
||||
@@ -16,17 +16,107 @@
|
||||
(global-set-key (kbd "C-<left>") 'left-word)
|
||||
(global-set-key (kbd "C-<right>") 'right-word)
|
||||
|
||||
;; Word selection with C-Shift-left/right
|
||||
(global-set-key (kbd "C-S-<left>") 'left-word)
|
||||
(global-set-key (kbd "C-S-<right>") 'right-word)
|
||||
;; Custom functions for shift-selection with word movement
|
||||
(defun left-word-select ()
|
||||
"Move left by words, extending selection."
|
||||
(interactive "^")
|
||||
(left-word))
|
||||
|
||||
;; Make sure shift-selection works with these commands
|
||||
(put 'left-word 'CUA 'move)
|
||||
(put 'right-word 'CUA 'move)
|
||||
(defun right-word-select ()
|
||||
"Move right by words, extending selection."
|
||||
(interactive "^")
|
||||
(right-word))
|
||||
|
||||
;; Word selection with C-Shift-left/right
|
||||
(global-set-key (kbd "C-S-<left>") 'left-word-select)
|
||||
(global-set-key (kbd "C-S-<right>") 'right-word-select)
|
||||
|
||||
;; Mark these functions as shift-selectable
|
||||
(put 'left-word 'shift-selection-mode t)
|
||||
(put 'right-word 'shift-selection-mode t)
|
||||
(put 'left-word-select 'shift-selection-mode t)
|
||||
(put 'right-word-select 'shift-selection-mode t)
|
||||
|
||||
;; Additional selection keybindings for consistency
|
||||
;; Shift+Home/End to select to beginning/end of line
|
||||
(global-set-key (kbd "S-<home>") 'beginning-of-line-select)
|
||||
(global-set-key (kbd "S-<end>") 'end-of-line-select)
|
||||
|
||||
(defun beginning-of-line-select ()
|
||||
"Move to beginning of line, extending selection."
|
||||
(interactive "^")
|
||||
(beginning-of-line))
|
||||
|
||||
(defun end-of-line-select ()
|
||||
"Move to end of line, extending selection."
|
||||
(interactive "^")
|
||||
(end-of-line))
|
||||
|
||||
;; Ctrl+Shift+Home/End to select to beginning/end of buffer
|
||||
(global-set-key (kbd "C-S-<home>") 'beginning-of-buffer-select)
|
||||
(global-set-key (kbd "C-S-<end>") 'end-of-buffer-select)
|
||||
|
||||
(defun beginning-of-buffer-select ()
|
||||
"Move to beginning of buffer, extending selection."
|
||||
(interactive "^")
|
||||
(beginning-of-buffer))
|
||||
|
||||
(defun end-of-buffer-select ()
|
||||
"Move to end of buffer, extending selection."
|
||||
(interactive "^")
|
||||
(end-of-buffer))
|
||||
|
||||
;; Ensure shift-arrow keys work for character selection
|
||||
(global-set-key (kbd "S-<left>") 'left-char-select)
|
||||
(global-set-key (kbd "S-<right>") 'right-char-select)
|
||||
(global-set-key (kbd "S-<up>") 'previous-line-select)
|
||||
(global-set-key (kbd "S-<down>") 'next-line-select)
|
||||
|
||||
(defun left-char-select ()
|
||||
"Move left by character, extending selection."
|
||||
(interactive "^")
|
||||
(left-char))
|
||||
|
||||
(defun right-char-select ()
|
||||
"Move right by character, extending selection."
|
||||
(interactive "^")
|
||||
(right-char))
|
||||
|
||||
(defun previous-line-select ()
|
||||
"Move up by line, extending selection."
|
||||
(interactive "^")
|
||||
(previous-line))
|
||||
|
||||
(defun next-line-select ()
|
||||
"Move down by line, extending selection."
|
||||
(interactive "^")
|
||||
(next-line))
|
||||
|
||||
;;; Text manipulation
|
||||
(global-set-key (kbd "C-<return>") 'cua-set-rectangle-mark)
|
||||
|
||||
;; Diagnostic function for selection keybindings
|
||||
(defun diagnose-selection-keys ()
|
||||
"Check if selection keybindings are properly configured."
|
||||
(interactive)
|
||||
(with-output-to-temp-buffer "*Selection Keys Diagnostics*"
|
||||
(princ "=== SELECTION KEYBINDINGS DIAGNOSTICS ===\n\n")
|
||||
(princ (format "Shift-select mode: %s\n" (if shift-select-mode "ENABLED" "DISABLED")))
|
||||
(princ (format "Transient mark mode: %s\n\n" (if transient-mark-mode "ENABLED" "DISABLED")))
|
||||
(princ "Word selection keys:\n")
|
||||
(princ (format " C-S-<left>: %s\n" (key-binding (kbd "C-S-<left>"))))
|
||||
(princ (format " C-S-<right>: %s\n\n" (key-binding (kbd "C-S-<right>"))))
|
||||
(princ "Character selection keys:\n")
|
||||
(princ (format " S-<left>: %s\n" (key-binding (kbd "S-<left>"))))
|
||||
(princ (format " S-<right>: %s\n" (key-binding (kbd "S-<right>"))))
|
||||
(princ (format " S-<up>: %s\n" (key-binding (kbd "S-<up>"))))
|
||||
(princ (format " S-<down>: %s\n\n" (key-binding (kbd "S-<down>"))))
|
||||
(princ "Line selection keys:\n")
|
||||
(princ (format " S-<home>: %s\n" (key-binding (kbd "S-<home>"))))
|
||||
(princ (format " S-<end>: %s\n\n" (key-binding (kbd "S-<end>"))))
|
||||
(princ "If keys are not bound correctly, reload with:\n")
|
||||
(princ " M-x reload-emacs-config\n")))
|
||||
|
||||
;;; Anzu - show match information in mode line
|
||||
(use-package anzu
|
||||
:ensure t
|
||||
|
||||
78
lisp/init-magit.el
Normal file
78
lisp/init-magit.el
Normal file
@@ -0,0 +1,78 @@
|
||||
;;; init-magit.el --- Magit configuration -*- lexical-binding: t -*-
|
||||
;;; Commentary:
|
||||
;;; Git interface configuration with Magit
|
||||
|
||||
;;; Code:
|
||||
|
||||
(use-package magit
|
||||
:ensure t
|
||||
:defer t
|
||||
:bind (("C-x g" . magit-status)
|
||||
("C-x M-g" . magit-dispatch)
|
||||
("C-c g" . magit-file-dispatch))
|
||||
:config
|
||||
(setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)
|
||||
|
||||
;; Performance improvements
|
||||
(setq magit-refresh-status-buffer nil)
|
||||
(setq magit-diff-highlight-indentation nil)
|
||||
(setq magit-diff-highlight-trailing nil)
|
||||
|
||||
;; Custom function to save commits as patches
|
||||
(defun magit-save-commit-as-patch ()
|
||||
"Save the commit at point as a patch file."
|
||||
(interactive)
|
||||
(let* ((commit (or (magit-commit-at-point)
|
||||
(error "No commit at point")))
|
||||
(default-name (format "%s.patch"
|
||||
(substring commit 0 (min 8 (length commit)))))
|
||||
(file (read-file-name "Save patch to file: "
|
||||
default-directory
|
||||
default-name
|
||||
nil
|
||||
default-name))
|
||||
(default-directory (magit-toplevel)))
|
||||
(if (zerop (shell-command
|
||||
(format "git format-patch -1 %s --stdout > %s"
|
||||
(shell-quote-argument commit)
|
||||
(shell-quote-argument (expand-file-name file)))))
|
||||
(message "Patch saved to %s" file)
|
||||
(error "Failed to save patch"))))
|
||||
|
||||
;; Bind C-c C-p in all relevant Magit modes
|
||||
(define-key magit-revision-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch)
|
||||
(define-key magit-log-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch)
|
||||
(define-key magit-log-select-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch)
|
||||
|
||||
;; Also add to cherry mode and refs mode
|
||||
(with-eval-after-load 'magit-refs
|
||||
(define-key magit-refs-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch))
|
||||
(with-eval-after-load 'magit-cherry
|
||||
(define-key magit-cherry-mode-map (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
|
||||
;; Ensure keybindings persist through hooks
|
||||
(add-hook 'magit-revision-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
(add-hook 'magit-log-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
(add-hook 'magit-log-select-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
(add-hook 'magit-refs-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
(add-hook 'magit-cherry-mode-hook
|
||||
(lambda () (local-set-key (kbd "C-c C-p") 'magit-save-commit-as-patch)))
|
||||
|
||||
;; Optional: Magit-delta for better diffs (if delta is installed)
|
||||
(when (executable-find "delta")
|
||||
(use-package magit-delta
|
||||
:ensure t
|
||||
:defer t
|
||||
:hook (magit-mode . magit-delta-mode)))
|
||||
|
||||
;; Integration with diff-hl if available
|
||||
(with-eval-after-load 'diff-hl
|
||||
(add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
|
||||
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))
|
||||
|
||||
(provide 'init-magit)
|
||||
;;; init-magit.el ends here
|
||||
206
lisp/init-performance.el
Normal file
206
lisp/init-performance.el
Normal file
@@ -0,0 +1,206 @@
|
||||
;;; init-performance.el --- Performance optimizations and fixes -*- lexical-binding: t -*-
|
||||
;;; Commentary:
|
||||
;;; Comprehensive performance optimizations to prevent UI freezing, lag, and other issues
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;;; Garbage Collection Optimizations
|
||||
;; Increase garbage collection threshold during startup
|
||||
(setq gc-cons-threshold (* 100 1024 1024)) ; 100MB
|
||||
(setq gc-cons-percentage 0.6)
|
||||
|
||||
;; Reset after startup
|
||||
(add-hook 'emacs-startup-hook
|
||||
(lambda ()
|
||||
(setq gc-cons-threshold (* 16 1024 1024)) ; 16MB
|
||||
(setq gc-cons-percentage 0.1)))
|
||||
|
||||
;;;; File Watching and Auto-revert Optimizations
|
||||
(setq auto-revert-interval 5) ; Check every 5 seconds instead of 1
|
||||
(setq auto-revert-use-notify t) ; Use file system notifications
|
||||
(setq auto-revert-avoid-polling t) ; Don't poll, use notifications
|
||||
(setq global-auto-revert-non-file-buffers nil) ; Don't auto-revert non-file buffers
|
||||
|
||||
;;;; Display and Rendering Optimizations
|
||||
(setq idle-update-delay 2.0) ; Default is 0.5
|
||||
(setq redisplay-dont-pause t) ; Never pause redisplay
|
||||
(setq fast-but-imprecise-scrolling t) ; Faster scrolling
|
||||
(setq inhibit-compacting-font-caches t) ; Don't compact font caches during GC
|
||||
|
||||
;; Font-lock optimizations
|
||||
(setq jit-lock-defer-time 0.05) ; Slightly defer font-locking
|
||||
(setq jit-lock-stealth-time 5) ; Wait 5 seconds before stealth fontification
|
||||
(setq jit-lock-chunk-size 1000) ; Smaller chunks
|
||||
(setq jit-lock-stealth-load 20) ; Don't fontify when load is high
|
||||
|
||||
;; Disable bidirectional text rendering for performance
|
||||
(setq-default bidi-display-reordering nil)
|
||||
(setq-default bidi-paragraph-direction 'left-to-right)
|
||||
|
||||
;; Optimize long line handling
|
||||
(setq-default truncate-lines t)
|
||||
(setq line-move-visual nil)
|
||||
|
||||
;;;; Memory and Process Optimizations
|
||||
(setq undo-limit 80000) ; Default is 160000
|
||||
(setq undo-strong-limit 120000) ; Default is 240000
|
||||
(setq undo-outer-limit 12000000) ; Default is 24000000
|
||||
(setq read-process-output-max (* 1024 1024)) ; 1MB, default is 4096
|
||||
|
||||
;;;; X11 Specific Optimizations (for no-toolkit builds)
|
||||
(when (and (display-graphic-p)
|
||||
(not (memq window-system '(ns mac w32))))
|
||||
;; More aggressive redrawing for X11
|
||||
(setq idle-update-delay 0.1) ; Faster idle updates
|
||||
(setq redisplay-skip-fontification-on-input nil)) ; Don't skip font-lock
|
||||
|
||||
;;;; Timer Management Functions
|
||||
(defun clear-duplicate-idle-timers ()
|
||||
"Remove duplicate idle timers that may be causing performance issues."
|
||||
(let ((seen-timers '()))
|
||||
(dolist (timer timer-idle-list)
|
||||
(let ((timer-func (timer--function timer)))
|
||||
(if (member timer-func seen-timers)
|
||||
(cancel-timer timer)
|
||||
(push timer-func seen-timers))))))
|
||||
|
||||
(defun disable-qml-timers ()
|
||||
"Disable QML idle timers."
|
||||
(dolist (timer timer-idle-list)
|
||||
(when (and (timer--function timer)
|
||||
(eq (timer--function timer) 'qml-timer-handler))
|
||||
(cancel-timer timer))))
|
||||
|
||||
;; Run timer cleanup on load
|
||||
(clear-duplicate-idle-timers)
|
||||
(disable-qml-timers)
|
||||
|
||||
;;;; Mode-specific Performance Fixes
|
||||
|
||||
;; Disable DAP mode if it's somehow enabled (causes major lag)
|
||||
(when (boundp 'dap-mode)
|
||||
(dap-mode -1))
|
||||
(when (boundp 'dap-ui-mode)
|
||||
(dap-ui-mode -1))
|
||||
(when (boundp 'dap-auto-configure-mode)
|
||||
(dap-auto-configure-mode -1))
|
||||
|
||||
;; Disable LSP-UI doc if causing issues
|
||||
(when (boundp 'lsp-ui-doc-mode)
|
||||
(setq lsp-ui-doc-enable nil))
|
||||
|
||||
;; Prevent QML timers from being created
|
||||
(with-eval-after-load 'qml-mode
|
||||
(remove-hook 'qml-mode-hook 'qml-start-timer-handler)
|
||||
(fset 'qml-timer-handler 'ignore)
|
||||
(fset 'qml-start-timer-handler 'ignore))
|
||||
|
||||
;; Prevent LSP from activating in QML files
|
||||
(with-eval-after-load 'lsp-mode
|
||||
;; Remove QML mode from LSP's activation list
|
||||
(setq lsp--major-modes-for-activate
|
||||
(delete 'qml-mode lsp--major-modes-for-activate))
|
||||
;; Advise lsp-deferred to skip QML files
|
||||
(defadvice lsp-deferred (around no-lsp-for-qml activate)
|
||||
"Prevent LSP from starting in QML mode."
|
||||
(unless (eq major-mode 'qml-mode)
|
||||
ad-do-it)))
|
||||
|
||||
;;;; Large File Handling
|
||||
(defun my-large-file-hook ()
|
||||
"Disable expensive features in large files."
|
||||
(when (> (buffer-size) (* 1024 1024)) ; Files larger than 1MB
|
||||
(setq-local line-number-mode nil)
|
||||
(setq-local column-number-mode nil)
|
||||
(setq-local show-paren-mode nil)
|
||||
(setq-local font-lock-mode nil)
|
||||
(setq-local bidi-display-reordering nil)))
|
||||
|
||||
(defun check-large-file-performance ()
|
||||
"Disable expensive features in large files (512KB threshold)."
|
||||
(when (> (buffer-size) (* 512 1024)) ; Files larger than 512KB
|
||||
(when (bound-and-true-p rainbow-delimiters-mode)
|
||||
(rainbow-delimiters-mode -1))
|
||||
(when (bound-and-true-p diff-hl-mode)
|
||||
(diff-hl-mode -1))
|
||||
(when (bound-and-true-p undo-tree-mode)
|
||||
(undo-tree-mode -1))))
|
||||
|
||||
(add-hook 'find-file-hook 'my-large-file-hook)
|
||||
(add-hook 'find-file-hook 'check-large-file-performance)
|
||||
|
||||
;; Prevent timer accumulation when killing buffers
|
||||
(add-hook 'kill-buffer-hook
|
||||
(lambda ()
|
||||
(when (derived-mode-p 'qml-mode)
|
||||
(disable-qml-timers))))
|
||||
|
||||
;;;; Manual Performance Control Functions
|
||||
|
||||
(defun force-redraw ()
|
||||
"Force a complete redraw of the frame."
|
||||
(interactive)
|
||||
(redraw-frame))
|
||||
|
||||
(defun force-redraw-all ()
|
||||
"Force redraw of all frames and windows."
|
||||
(interactive)
|
||||
(dolist (frame (frame-list))
|
||||
(redraw-frame frame))
|
||||
(redisplay t))
|
||||
|
||||
(defun fix-performance-now ()
|
||||
"Fix all known performance issues immediately."
|
||||
(interactive)
|
||||
;; Disable DAP
|
||||
(when (boundp 'dap-mode) (dap-mode -1))
|
||||
(when (boundp 'dap-ui-mode) (dap-ui-mode -1))
|
||||
(when (boundp 'dap-auto-configure-mode) (dap-auto-configure-mode -1))
|
||||
|
||||
;; Clear timers
|
||||
(clear-duplicate-idle-timers)
|
||||
(disable-qml-timers)
|
||||
|
||||
;; Force garbage collection
|
||||
(garbage-collect)
|
||||
|
||||
(message "Performance fixes applied!"))
|
||||
|
||||
(defun diagnose-performance ()
|
||||
"Show information about potential performance issues."
|
||||
(interactive)
|
||||
(with-current-buffer (get-buffer-create "*Performance Diagnostic*")
|
||||
(erase-buffer)
|
||||
(insert "=== Emacs Performance Diagnostic ===\n\n")
|
||||
(insert (format "Garbage Collection Threshold: %s\n" gc-cons-threshold))
|
||||
(insert (format "Garbage Collection Percentage: %s\n" gc-cons-percentage))
|
||||
(insert (format "Auto-revert interval: %s\n" auto-revert-interval))
|
||||
(insert (format "Number of buffers: %s\n" (length (buffer-list))))
|
||||
(insert (format "Active minor modes: %s\n"
|
||||
(mapconcat 'symbol-name
|
||||
(delq nil (mapcar (lambda (m) (and (boundp m) (symbol-value m) m))
|
||||
minor-mode-list))
|
||||
", ")))
|
||||
(insert "\n=== Active Timers ===\n")
|
||||
(dolist (timer timer-list)
|
||||
(insert (format "%s\n" timer)))
|
||||
(insert "\n=== Idle Timers ===\n")
|
||||
(dolist (timer timer-idle-list)
|
||||
(insert (format "%s\n" timer)))
|
||||
(switch-to-buffer (current-buffer))))
|
||||
|
||||
;;;; Keybindings for Performance Control
|
||||
(global-set-key (kbd "C-c r r") 'force-redraw)
|
||||
(global-set-key (kbd "C-c r a") 'force-redraw-all)
|
||||
(global-set-key (kbd "C-c p f") 'fix-performance-now)
|
||||
(global-set-key (kbd "C-c p d") 'diagnose-performance)
|
||||
|
||||
;;;; Window Configuration Hook
|
||||
(add-hook 'window-configuration-change-hook
|
||||
(lambda ()
|
||||
(when (and (display-graphic-p)
|
||||
(not (memq window-system '(ns mac w32))))
|
||||
(redisplay))))
|
||||
|
||||
(provide 'init-performance)
|
||||
;;; init-performance.el ends here
|
||||
@@ -9,7 +9,7 @@
|
||||
:ensure t
|
||||
:defer t
|
||||
:commands deadgrep
|
||||
:bind (("C-c r" . deadgrep)))
|
||||
:bind (("C-c d g" . deadgrep)))
|
||||
|
||||
;;; Wgrep - editable grep buffers
|
||||
(use-package wgrep
|
||||
@@ -26,5 +26,166 @@
|
||||
:defer t
|
||||
:commands (ripgrep-regexp))
|
||||
|
||||
;;; Live search functions using consult-ripgrep
|
||||
(with-eval-after-load 'consult
|
||||
(defun search-project-for-symbol-at-point ()
|
||||
"Search project for symbol at point using consult-ripgrep."
|
||||
(interactive)
|
||||
(require 'projectile)
|
||||
(if (use-region-p)
|
||||
(consult-ripgrep (projectile-project-root)
|
||||
(buffer-substring-no-properties (region-beginning) (region-end)))
|
||||
(consult-ripgrep (projectile-project-root) (thing-at-point 'symbol))))
|
||||
|
||||
(defun search-project ()
|
||||
"Live search in project files using consult-ripgrep."
|
||||
(interactive)
|
||||
(require 'projectile)
|
||||
(consult-ripgrep (projectile-project-root)))
|
||||
|
||||
(defun search-current-directory ()
|
||||
"Live search in current directory using consult-ripgrep."
|
||||
(interactive)
|
||||
(consult-ripgrep default-directory)))
|
||||
|
||||
;;; Enhanced grep with live preview
|
||||
(use-package consult
|
||||
:defer t
|
||||
:config
|
||||
;; Configure ripgrep arguments for better results
|
||||
(setq consult-ripgrep-args
|
||||
"rg --null --line-buffered --color=never --max-columns=1000 --path-separator / --smart-case --no-heading --with-filename --line-number --search-zip")
|
||||
|
||||
;; Preview at point for grep commands
|
||||
(consult-customize
|
||||
consult-ripgrep consult-git-grep consult-grep
|
||||
:preview-key '(:debounce 0.4 any)))
|
||||
|
||||
;;; Set up search keymap
|
||||
(define-prefix-command 'search-map)
|
||||
(global-set-key (kbd "C-c s") 'search-map)
|
||||
|
||||
;;; Convenient keybindings for search - using deferred loading
|
||||
(define-key search-map (kbd "p")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(require 'projectile)
|
||||
(if (fboundp 'search-project)
|
||||
(call-interactively 'search-project)
|
||||
(consult-ripgrep (projectile-project-root)))))
|
||||
|
||||
(define-key search-map (kbd "s")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(require 'projectile)
|
||||
(if (fboundp 'search-project-for-symbol-at-point)
|
||||
(call-interactively 'search-project-for-symbol-at-point)
|
||||
(consult-ripgrep (projectile-project-root) (thing-at-point 'symbol)))))
|
||||
|
||||
(define-key search-map (kbd "d")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(consult-ripgrep default-directory)))
|
||||
|
||||
(define-key search-map (kbd "r")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(call-interactively 'consult-ripgrep)))
|
||||
|
||||
(define-key search-map (kbd "g")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(call-interactively 'consult-grep)))
|
||||
|
||||
(define-key search-map (kbd "G")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(call-interactively 'consult-git-grep)))
|
||||
|
||||
(define-key search-map (kbd "l")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(call-interactively 'consult-line)))
|
||||
|
||||
(define-key search-map (kbd "L")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(call-interactively 'consult-line-multi)))
|
||||
|
||||
(define-key search-map (kbd "o")
|
||||
(lambda () (interactive)
|
||||
(require 'consult)
|
||||
(call-interactively 'consult-outline)))
|
||||
|
||||
;;; Alternative: Helm-ag for live search (if you prefer helm)
|
||||
;; Uncomment if you want to use helm-ag instead
|
||||
;; (use-package helm-ag
|
||||
;; :ensure t
|
||||
;; :defer t
|
||||
;; :config
|
||||
;; (setq helm-ag-base-command "rg --vimgrep --no-heading --smart-case"))
|
||||
|
||||
;;; Ag (The Silver Searcher) - alternative to ripgrep
|
||||
(use-package ag
|
||||
:ensure t
|
||||
:defer t
|
||||
:commands (ag ag-project ag-regexp)
|
||||
:config
|
||||
(setq ag-highlight-search t)
|
||||
(setq ag-reuse-buffers t))
|
||||
|
||||
;;; Rg.el - Another ripgrep interface
|
||||
(use-package rg
|
||||
:ensure t
|
||||
:defer t
|
||||
:commands (rg rg-project rg-dwim)
|
||||
:config
|
||||
(rg-enable-default-bindings))
|
||||
|
||||
;;; Search and replace across project
|
||||
(defun project-search-and-replace (search-string replace-string)
|
||||
"Search for SEARCH-STRING and replace with REPLACE-STRING in project."
|
||||
(interactive
|
||||
(list (read-string "Search: " (thing-at-point 'symbol))
|
||||
(read-string "Replace: ")))
|
||||
(let ((project-root (projectile-project-root)))
|
||||
(rg search-string "*" project-root)
|
||||
(with-current-buffer "*rg*"
|
||||
(wgrep-change-to-wgrep-mode)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward search-string nil t)
|
||||
(replace-match replace-string))
|
||||
(wgrep-finish-edit))))
|
||||
|
||||
(define-key search-map (kbd "R") 'project-search-and-replace)
|
||||
|
||||
;;; Help function to show all search keybindings
|
||||
(defun show-search-help ()
|
||||
"Show available search commands and keybindings."
|
||||
(interactive)
|
||||
(with-output-to-temp-buffer "*Search Commands Help*"
|
||||
(princ "=== SEARCH COMMANDS ===\n\n")
|
||||
(princ "LIVE SEARCH (with preview):\n")
|
||||
(princ " C-c s p : Search in project files (live)\n")
|
||||
(princ " C-c s s : Search project for symbol at point\n")
|
||||
(princ " C-c s d : Search in current directory\n")
|
||||
(princ " C-c s r : Consult ripgrep (specify directory)\n")
|
||||
(princ " C-c s l : Search lines in current buffer\n")
|
||||
(princ " C-c s L : Search lines in multiple buffers\n")
|
||||
(princ " C-c s o : Search outline/headings in buffer\n\n")
|
||||
(princ "OTHER SEARCH TOOLS:\n")
|
||||
(princ " C-c s g : Consult grep\n")
|
||||
(princ " C-c s G : Consult git-grep\n")
|
||||
(princ " C-c d g : Deadgrep (ripgrep with nice UI)\n")
|
||||
(princ " C-c s R : Project search and replace\n")
|
||||
(princ " M-s . : isearch symbol at point\n\n")
|
||||
(princ "TIPS:\n")
|
||||
(princ " - In consult-ripgrep, use '#pattern' to filter results\n")
|
||||
(princ " - Use C-SPC to preview different results\n")
|
||||
(princ " - In deadgrep results, press 'r' to enable wgrep mode for editing\n")))
|
||||
|
||||
(define-key search-map (kbd "h") 'show-search-help)
|
||||
(define-key search-map (kbd "?") 'show-search-help)
|
||||
|
||||
(provide 'init-search)
|
||||
;;; init-search.el ends here
|
||||
74
lisp/init-seq-fix.el
Normal file
74
lisp/init-seq-fix.el
Normal file
@@ -0,0 +1,74 @@
|
||||
;;; init-seq-fix.el --- Fix seq library issues -*- lexical-binding: t -*-
|
||||
;;; Commentary:
|
||||
;;; Fix for cl-no-applicable-method errors with seq-empty-p
|
||||
;;; This particularly affects Emacs 31 development versions
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Ensure seq library is properly loaded
|
||||
(require 'seq)
|
||||
(require 'cl-lib)
|
||||
(require 'cl-generic) ; Needed for method dispatch in Emacs 31
|
||||
|
||||
;; Fix potential issues with seq-empty-p being called on strings
|
||||
;; This can happen with input methods or certain packages
|
||||
(defadvice seq-empty-p (before fix-string-input activate)
|
||||
"Handle string inputs that should be converted to sequences."
|
||||
(when (and (ad-get-arg 0)
|
||||
(stringp (ad-get-arg 0))
|
||||
(not (sequencep (ad-get-arg 0))))
|
||||
(ad-set-arg 0 (append (ad-get-arg 0) nil))))
|
||||
|
||||
;; Alternative fix: Override seq-empty-p to handle strings properly
|
||||
(with-eval-after-load 'seq
|
||||
(defun seq-empty-p-fixed (orig-fun sequence)
|
||||
"Fixed version of seq-empty-p that handles strings."
|
||||
(cond
|
||||
((stringp sequence) (string-empty-p sequence))
|
||||
((sequencep sequence) (funcall orig-fun sequence))
|
||||
(t (funcall orig-fun sequence))))
|
||||
|
||||
;; Only apply if we're having issues
|
||||
(when (condition-case nil
|
||||
(progn (seq-empty-p "test") nil)
|
||||
(error t))
|
||||
(advice-add 'seq-empty-p :around #'seq-empty-p-fixed)))
|
||||
|
||||
;; Ensure proper loading order for seq-related packages
|
||||
(with-eval-after-load 'company
|
||||
(require 'seq))
|
||||
|
||||
(with-eval-after-load 'lsp-mode
|
||||
(require 'seq))
|
||||
|
||||
;; Fix for input method issues that might trigger this error
|
||||
(setq default-input-method nil)
|
||||
|
||||
;; Specific fix for the "latin" string issue
|
||||
;; This often comes from font or input method configurations
|
||||
(with-eval-after-load 'mule
|
||||
(when (and (boundp 'current-input-method)
|
||||
(stringp current-input-method)
|
||||
(string= current-input-method "latin"))
|
||||
(setq current-input-method nil)))
|
||||
|
||||
;; Prevent problematic input method activation
|
||||
(defadvice activate-input-method (before check-input-method activate)
|
||||
"Prevent activation of problematic input methods."
|
||||
(when (and (ad-get-arg 0)
|
||||
(stringp (ad-get-arg 0))
|
||||
(string= (ad-get-arg 0) "latin"))
|
||||
(ad-set-arg 0 nil)))
|
||||
|
||||
;; Ensure strings are properly handled in various contexts
|
||||
(defun ensure-seq-compatibility ()
|
||||
"Ensure seq library compatibility across Emacs."
|
||||
(unless (fboundp 'string-empty-p)
|
||||
(defun string-empty-p (string)
|
||||
"Check whether STRING is empty."
|
||||
(string= string ""))))
|
||||
|
||||
(ensure-seq-compatibility)
|
||||
|
||||
(provide 'init-seq-fix)
|
||||
;;; init-seq-fix.el ends here
|
||||
141
lisp/init-ui.el
141
lisp/init-ui.el
@@ -15,10 +15,135 @@
|
||||
(show-paren-mode 1)
|
||||
(setq show-paren-delay 0)
|
||||
|
||||
;; CUA mode for rectangles - use selection mode only to avoid conflicts
|
||||
(cua-selection-mode t) ; Only rectangle selection, not full CUA bindings
|
||||
;; Enable syntax highlighting globally
|
||||
(global-font-lock-mode t)
|
||||
(setq font-lock-maximum-decoration t)
|
||||
(setq font-lock-support-mode 'jit-lock-mode)
|
||||
(setq jit-lock-contextually t)
|
||||
(setq jit-lock-stealth-time 5)
|
||||
|
||||
;; Enable full CUA mode for standard copy/paste/cut
|
||||
;; This provides C-c (copy), C-v (paste), C-x (cut), C-z (undo)
|
||||
(setq cua-enable-cua-keys t)
|
||||
(setq cua-auto-tabify-rectangles nil)
|
||||
(setq cua-keep-region-after-copy t)
|
||||
;; Make CUA mode work properly with other keybindings
|
||||
(setq cua-prefix-override-inhibit-delay 0.001)
|
||||
(cua-mode t)
|
||||
|
||||
;; Function to ensure CUA bindings work properly
|
||||
(defun ensure-cua-bindings ()
|
||||
"Ensure CUA mode bindings are properly set."
|
||||
(interactive)
|
||||
;; Force CUA mode to be on
|
||||
(cua-mode 1)
|
||||
;; Ensure the keybindings are active
|
||||
(setq cua-enable-cua-keys t)
|
||||
(message "CUA bindings reinforced: C-c (copy), C-v (paste), C-x (cut), C-z (undo)"))
|
||||
|
||||
;; Run this after all init files are loaded
|
||||
(add-hook 'after-init-hook 'ensure-cua-bindings)
|
||||
|
||||
;; Ensure CUA works in programming modes
|
||||
(add-hook 'prog-mode-hook
|
||||
(lambda ()
|
||||
(when (not cua-mode)
|
||||
(cua-mode 1))
|
||||
(local-set-key (kbd "C-c") nil) ; Clear any local C-c binding
|
||||
(local-set-key (kbd "C-v") nil) ; Clear any local C-v binding
|
||||
))
|
||||
|
||||
;; Function to fix syntax highlighting in current buffer
|
||||
(defun fix-syntax-highlighting ()
|
||||
"Fix syntax highlighting in the current buffer."
|
||||
(interactive)
|
||||
(font-lock-mode -1)
|
||||
(font-lock-mode 1)
|
||||
(font-lock-fontify-buffer)
|
||||
(message "Syntax highlighting refreshed"))
|
||||
|
||||
;; Function to switch between tree-sitter and regular modes
|
||||
(defun toggle-tree-sitter-mode ()
|
||||
"Toggle between tree-sitter and regular mode for current buffer."
|
||||
(interactive)
|
||||
(cond
|
||||
((eq major-mode 'c-ts-mode)
|
||||
(c-mode)
|
||||
(message "Switched to regular c-mode"))
|
||||
((eq major-mode 'c++-ts-mode)
|
||||
(c++-mode)
|
||||
(message "Switched to regular c++-mode"))
|
||||
((eq major-mode 'c-mode)
|
||||
(if (fboundp 'c-ts-mode)
|
||||
(progn (c-ts-mode)
|
||||
(message "Switched to c-ts-mode"))
|
||||
(message "Tree-sitter mode not available")))
|
||||
((eq major-mode 'c++-mode)
|
||||
(if (fboundp 'c++-ts-mode)
|
||||
(progn (c++-ts-mode)
|
||||
(message "Switched to c++-ts-mode"))
|
||||
(message "Tree-sitter mode not available")))
|
||||
(t (message "Not in a C/C++ buffer"))))
|
||||
|
||||
;; Function to diagnose syntax highlighting issues
|
||||
(defun diagnose-syntax-highlighting ()
|
||||
"Diagnose syntax highlighting issues in current buffer."
|
||||
(interactive)
|
||||
(with-output-to-temp-buffer "*Syntax Highlighting Diagnostics*"
|
||||
(princ "=== SYNTAX HIGHLIGHTING DIAGNOSTICS ===\n\n")
|
||||
(princ (format "Buffer: %s\n" (buffer-name)))
|
||||
(princ (format "Major mode: %s\n" major-mode))
|
||||
(princ (format "Font-lock mode: %s\n" (if font-lock-mode "ENABLED" "DISABLED")))
|
||||
(princ (format "Global font-lock mode: %s\n" (if global-font-lock-mode "ENABLED" "DISABLED")))
|
||||
(princ (format "Font-lock keywords defined: %s\n"
|
||||
(if font-lock-keywords "YES" "NO")))
|
||||
(princ (format "Buffer size: %d bytes\n" (buffer-size)))
|
||||
(princ (format "File size threshold check: %s\n"
|
||||
(if (> (buffer-size) (* 1024 1024))
|
||||
"LARGE FILE (>1MB) - highlighting may be disabled"
|
||||
"Normal size")))
|
||||
(princ "\nTo fix issues, try:\n")
|
||||
(princ " M-x fix-syntax-highlighting\n")
|
||||
(princ " M-x font-lock-mode (toggle off and on)\n")
|
||||
(princ " M-x font-lock-fontify-buffer\n")))
|
||||
|
||||
;; Ensure font-lock works in C/C++ modes (both regular and tree-sitter)
|
||||
(defun ensure-c-syntax-highlighting ()
|
||||
"Ensure syntax highlighting works in C/C++ modes."
|
||||
(font-lock-mode 1)
|
||||
(setq font-lock-keywords-case-fold-search nil)
|
||||
;; Force fontification if needed
|
||||
(when (and (boundp 'font-lock-mode) (not font-lock-mode))
|
||||
(font-lock-mode 1))
|
||||
;; For tree-sitter modes, ensure proper setup
|
||||
(when (or (eq major-mode 'c-ts-mode)
|
||||
(eq major-mode 'c++-ts-mode))
|
||||
(when (fboundp 'treesit-font-lock-recompute-features)
|
||||
(treesit-font-lock-recompute-features))))
|
||||
|
||||
;; Apply to all C/C++ mode variants
|
||||
(add-hook 'c-mode-hook 'ensure-c-syntax-highlighting)
|
||||
(add-hook 'c++-mode-hook 'ensure-c-syntax-highlighting)
|
||||
(add-hook 'c-ts-mode-hook 'ensure-c-syntax-highlighting)
|
||||
(add-hook 'c++-ts-mode-hook 'ensure-c-syntax-highlighting)
|
||||
|
||||
;; Diagnostic function for CUA mode
|
||||
(defun diagnose-cua-mode ()
|
||||
"Diagnose CUA mode settings and keybindings."
|
||||
(interactive)
|
||||
(with-output-to-temp-buffer "*CUA Mode Diagnostics*"
|
||||
(princ "=== CUA MODE DIAGNOSTICS ===\n\n")
|
||||
(princ (format "CUA mode enabled: %s\n" (if cua-mode "YES" "NO")))
|
||||
(princ (format "CUA keys enabled: %s\n" (if cua-enable-cua-keys "YES" "NO")))
|
||||
(princ (format "CUA prefix override delay: %s\n" cua-prefix-override-inhibit-delay))
|
||||
(princ "\nKey bindings:\n")
|
||||
(princ (format "C-c binding: %s\n" (key-binding (kbd "C-c"))))
|
||||
(princ (format "C-v binding: %s\n" (key-binding (kbd "C-v"))))
|
||||
(princ (format "C-x binding: %s\n" (key-binding (kbd "C-x"))))
|
||||
(princ (format "C-z binding: %s\n" (key-binding (kbd "C-z"))))
|
||||
(princ "\nTo fix issues, try:\n")
|
||||
(princ " M-x ensure-cua-bindings\n")
|
||||
(princ " M-x cua-mode (toggle off and on)\n")))
|
||||
|
||||
;; Trailing whitespace
|
||||
(setq show-trailing-whitespace t)
|
||||
@@ -56,11 +181,13 @@
|
||||
:foundry "nil"
|
||||
:slant 'normal
|
||||
:weight 'regular
|
||||
:height 140
|
||||
:height 180
|
||||
:width 'normal)
|
||||
|
||||
;; Ensure font settings apply to new frames
|
||||
(add-to-list 'default-frame-alist '(font . "0xProto Nerd Font Mono-14"))
|
||||
;; Use the proper font spec format
|
||||
(add-to-list 'default-frame-alist
|
||||
(cons 'font (font-spec :family "0xProto Nerd Font Mono" :size 18)))
|
||||
|
||||
;;; Diff-hl face customizations
|
||||
(with-eval-after-load 'diff-hl
|
||||
@@ -69,6 +196,10 @@
|
||||
(set-face-attribute 'diff-hl-insert nil :background "green3" :foreground "green3"))
|
||||
|
||||
;;; Theme Management
|
||||
;; Add lisp directory to theme load path
|
||||
(add-to-list 'custom-theme-load-path
|
||||
(expand-file-name "lisp" user-emacs-directory))
|
||||
|
||||
(defvar jens-themes
|
||||
'(developer-dark
|
||||
modus-vivendi
|
||||
@@ -113,4 +244,4 @@
|
||||
(setq which-key-popup-type 'side-window))
|
||||
|
||||
(provide 'init-ui)
|
||||
;;; init-ui.el ends here
|
||||
;;; init-ui.el ends here
|
||||
|
||||
571
lisp/mu4e-config.el
Normal file
571
lisp/mu4e-config.el
Normal file
@@ -0,0 +1,571 @@
|
||||
;;; mu4e-config.el --- mu4e email configuration -*- lexical-binding: t; -*-
|
||||
|
||||
;; mu4e should already be loaded from .emacs before this file is loaded
|
||||
;; If not loaded, try to load it
|
||||
(add-to-list 'load-path "/opt/homebrew/Cellar/mu/1.12.12/share/emacs/site-lisp/mu/mu4e")
|
||||
(require 'mu4e)
|
||||
|
||||
;; HTML rendering configuration for mu4e 1.12
|
||||
|
||||
;; Use shr as the default renderer
|
||||
(setq mm-text-html-renderer 'shr)
|
||||
|
||||
;; Configure shr for better plain text display
|
||||
;; (setq shr-use-colors nil) ; No colors
|
||||
;; (setq shr-use-fonts nil) ; No variable fonts
|
||||
;; (setq shr-width 80) ; 80 column width
|
||||
(setq shr-bullet "• ") ; Nice bullet
|
||||
|
||||
;; Increase font size in SHR (HTML rendering)
|
||||
(defun my-shr-rescale-font ()
|
||||
"Increase font size in SHR rendered content."
|
||||
(text-scale-set 1)) ; Increase by 1 step, adjust as needed (2, 3, etc.)
|
||||
|
||||
;; Apply font scaling to mu4e HTML viewing
|
||||
(add-hook 'mu4e-view-mode-hook 'my-shr-rescale-font)
|
||||
|
||||
;; Create a custom command to view HTML with pandoc
|
||||
(defun mu4e-view-html-with-pandoc ()
|
||||
"View the HTML part of the current message using pandoc."
|
||||
(interactive)
|
||||
(let ((msg (mu4e-message-at-point)))
|
||||
(unless msg
|
||||
(mu4e-error "No message at point"))
|
||||
(let* ((path (mu4e-message-field msg :path))
|
||||
(pandoc-buffer "*mu4e-pandoc*")
|
||||
(temp-dir (make-temp-file "mu4e-extract" t)))
|
||||
(if path
|
||||
(progn
|
||||
(with-current-buffer (get-buffer-create pandoc-buffer)
|
||||
(erase-buffer)
|
||||
(insert "=== HTML Email Rendered with Pandoc ===\n\n")
|
||||
;; Extract all parts
|
||||
(call-process "mu" nil nil nil
|
||||
"extract" "--save-all"
|
||||
(format "--target-dir=%s" temp-dir) path)
|
||||
;; Find HTML files and convert them
|
||||
(let ((html-files (directory-files temp-dir t "\\.html?$")))
|
||||
(if html-files
|
||||
(dolist (html-file html-files)
|
||||
(insert (shell-command-to-string
|
||||
(format "pandoc -f html -t markdown --wrap=auto --columns=80 '%s' 2>/dev/null"
|
||||
html-file))))
|
||||
;; No HTML files found, try extracting from message directly
|
||||
(let ((raw-msg (shell-command-to-string (format "cat '%s'" path))))
|
||||
;; Look for HTML content between boundaries
|
||||
(if (string-match "Content-Type: text/html" raw-msg)
|
||||
(progn
|
||||
(insert "Converting HTML content...\n\n")
|
||||
(let ((temp-html (make-temp-file "mu4e-msg" nil ".html")))
|
||||
(with-temp-file temp-html
|
||||
(insert raw-msg))
|
||||
(insert (shell-command-to-string
|
||||
(format "cat '%s' | sed -n '/Content-Type: text\\/html/,/^--/p' | sed '1,/^$/d' | sed '/^--/,$d' | pandoc -f html -t markdown --wrap=auto --columns=80 2>/dev/null"
|
||||
temp-html)))
|
||||
(delete-file temp-html)))
|
||||
(insert "No HTML content found in this message.\n")))))
|
||||
;; Clean up temp directory
|
||||
(delete-directory temp-dir t)
|
||||
(goto-char (point-min))
|
||||
(view-mode)
|
||||
(display-buffer (current-buffer))))
|
||||
(mu4e-warn "Cannot access message file")))))
|
||||
|
||||
;; Simpler approach: Add action to view HTML with pandoc
|
||||
(add-to-list 'mu4e-view-actions
|
||||
'("pandoc" . (lambda (msg)
|
||||
(let* ((path (mu4e-message-field msg :path))
|
||||
(pandoc-buffer "*mu4e-pandoc*"))
|
||||
(when path
|
||||
(with-current-buffer (get-buffer-create pandoc-buffer)
|
||||
(erase-buffer)
|
||||
(insert "=== HTML Email Rendered with Pandoc ===\n\n")
|
||||
(let ((html-content
|
||||
(shell-command-to-string
|
||||
(format "mu view '%s' 2>/dev/null | awk '/text\\/html/{flag=1; next} flag && /^--/{exit} flag' | pandoc -f html -t markdown --wrap=auto --columns=80 2>/dev/null" path))))
|
||||
(if (and html-content (> (length html-content) 0))
|
||||
(insert html-content)
|
||||
;; If no HTML part found, show message
|
||||
(insert "No HTML content found in this message.\n\n")
|
||||
(insert (shell-command-to-string
|
||||
(format "mu view '%s'" path)))))
|
||||
(goto-char (point-min))
|
||||
(view-mode)
|
||||
(display-buffer (current-buffer)))))))
|
||||
t)
|
||||
|
||||
;; Add keybinding for pandoc view
|
||||
(with-eval-after-load 'mu4e-view
|
||||
(define-key mu4e-view-mode-map (kbd "H") 'mu4e-view-html-with-pandoc))
|
||||
|
||||
(message "mu4e: HTML rendering configured. Press 'H' in message view to render with pandoc.")
|
||||
|
||||
;; Basic mu4e settings
|
||||
(setq mu4e-maildir "~/Maildir"
|
||||
;; Use our processing script instead of plain mbsync
|
||||
;; This will sync mail and fix List-Id headers
|
||||
mu4e-get-mail-command (expand-file-name "process-mail.sh" user-emacs-directory)
|
||||
mu4e-update-interval 300 ; Update every 5 minutes
|
||||
mu4e-compose-signature-auto-include nil
|
||||
mu4e-view-show-images t
|
||||
mu4e-view-show-addresses t
|
||||
mu4e-change-filenames-when-moving t ; Needed for mbsync
|
||||
mu4e-index-cleanup t ; Clean up after moving
|
||||
mu4e-index-lazy-check nil ; Don't be lazy about indexing
|
||||
mu4e-hide-index-messages t) ; Hide indexing messages to avoid errors
|
||||
|
||||
;; Function to get current context's maildir prefix
|
||||
(defun mu4e-current-context-maildir-prefix ()
|
||||
"Get the maildir prefix for the current context."
|
||||
(if mu4e-context-current
|
||||
(let ((context-name (mu4e-context-name mu4e-context-current)))
|
||||
(format "maildir:/%s/*" context-name))
|
||||
""))
|
||||
|
||||
;; Bookmarks (shortcuts to common searches)
|
||||
;; Use setq to define the complete list at once
|
||||
(setq mu4e-bookmarks
|
||||
'(;; Basic views - work in current context
|
||||
(:name "Unread messages"
|
||||
:query "flag:unread AND NOT flag:trashed"
|
||||
:key ?u)
|
||||
(:name "Today's messages"
|
||||
:query "date:today..now"
|
||||
:key ?t)
|
||||
(:name "Last 7 days"
|
||||
:query "date:7d..now"
|
||||
:key ?w)
|
||||
|
||||
;; Smart mailboxes - search across all contexts
|
||||
(:name "📌 Important"
|
||||
:query "(flag:flagged OR prio:high OR from:/boss|manager|ceo|director|important/ OR subject:/urgent|important|critical|asap/) AND NOT flag:trashed"
|
||||
:key ?i)
|
||||
|
||||
(:name "📰 Newsletters"
|
||||
:query "(from:/nytimes|newyorktimes|atlantic|politico/ OR from:nytimes.com OR from:theatlantic.com OR from:politico.com OR from:politico.eu) AND NOT flag:trashed AND NOT flag:flagged"
|
||||
:key ?n)
|
||||
|
||||
(:name "🛍️ Purchases & Orders"
|
||||
:query "(from:/amazon|ebay|paypal|stripe|shopify|order|store|shop|invoice|receipt/ OR subject:/order|invoice|receipt|purchase|payment|confirmation|shipping|delivery|tracking/) AND NOT flag:trashed"
|
||||
:key ?p)
|
||||
|
||||
(:name "📎 Attachments"
|
||||
:query "flag:attach AND NOT flag:trashed"
|
||||
:key ?a)
|
||||
|
||||
(:name "🎫 Travel & Tickets"
|
||||
:query "(from:/airline|hotel|booking|expedia|airbnb|uber|lyft|train|eventbrite|ticketmaster/ OR subject:/booking|reservation|ticket|flight|itinerary|confirmation/) AND NOT flag:trashed"
|
||||
:key ?v)
|
||||
|
||||
(:name "💰 Finance & Banking"
|
||||
:query "(from:/bank|credit|visa|mastercard|amex|insurance|tax|accountant/ OR subject:/statement|balance|transaction|payment/) AND NOT flag:trashed"
|
||||
:key ?f)
|
||||
|
||||
(:name "👥 Social & Forums"
|
||||
:query "(from:/facebook|twitter|linkedin|instagram|reddit|github|gitlab|discourse|forum/ OR subject:/commented|replied|mentioned|tagged|followed/) AND NOT flag:trashed"
|
||||
:key ?s)
|
||||
|
||||
;; Mailing Lists (Personal context)
|
||||
(:name "📋 All Mailing Lists"
|
||||
:query "maildir:/Personal/Lists AND NOT flag:trashed"
|
||||
:key ?L)
|
||||
|
||||
(:name "📋 C++ std-discussion"
|
||||
:query "list:std-discussion.lists.isocpp.org AND NOT flag:trashed"
|
||||
:key ?C)
|
||||
|
||||
(:name "📋 Qt Interest"
|
||||
:query "list:interest.qt-project.org AND NOT flag:trashed"
|
||||
:key ?q)
|
||||
|
||||
(:name "📋 Boost"
|
||||
:query "list:boost.lists.boost.org AND NOT flag:trashed"
|
||||
:key ?b)
|
||||
|
||||
(:name "📋 GCC"
|
||||
:query "list:gcc.gcc.gnu.org AND NOT flag:trashed"
|
||||
:key ?G)
|
||||
|
||||
(:name "📋 LKML"
|
||||
:query "list:linux-kernel.vger.kernel.org AND NOT flag:trashed"
|
||||
:key ?K)))
|
||||
|
||||
(setq mu4e-maildir-shortcuts
|
||||
'(("/Personal/INBOX" . ?i)
|
||||
("/Personal/Sent" . ?s)
|
||||
("/Personal/Trash" . ?t)
|
||||
("/Personal/Drafts" . ?d)
|
||||
("/Personal/Spam " . ?S)
|
||||
("/Personal/Archive" . ?a)
|
||||
("/Personal/Lists" . ?l)
|
||||
("/IONOS/INBOX" . ?I)
|
||||
("/IONOS/Sent" . ?S)
|
||||
("/IONOS/Trash" . ?T)
|
||||
("/IONOS/Drafts" . ?D)
|
||||
("/IONOS/Archive" . ?A)))
|
||||
|
||||
;; Custom function for fuzzy relative timestamps
|
||||
(defun my-mu4e-format-date (date)
|
||||
"Format DATE as a fuzzy relative time string."
|
||||
(let* ((now (float-time))
|
||||
(time (float-time date))
|
||||
(diff (- now time))
|
||||
(sec diff)
|
||||
(min (/ diff 60))
|
||||
(hour (/ diff 3600))
|
||||
(day (/ diff 86400))
|
||||
(week (/ diff 604800))
|
||||
(month (/ diff 2592000))
|
||||
(year (/ diff 31536000)))
|
||||
(cond
|
||||
((< sec 60) "just now")
|
||||
((< min 2) "1 min ago")
|
||||
((< min 60) (format "%d mins ago" (truncate min)))
|
||||
((< hour 2) "1 hour ago")
|
||||
((< hour 24) (format "%d hours ago" (truncate hour)))
|
||||
((< day 2) "yesterday")
|
||||
((< day 7) (format "%d days ago" (truncate day)))
|
||||
((< week 2) "1 week ago")
|
||||
((< week 4) (format "%d weeks ago" (truncate week)))
|
||||
((< month 2) "1 month ago")
|
||||
((< month 12) (format "%d months ago" (truncate month)))
|
||||
((< year 2) "1 year ago")
|
||||
(t (format "%d years ago" (truncate year))))))
|
||||
|
||||
;; Custom header field for fuzzy date
|
||||
(add-to-list 'mu4e-header-info-custom
|
||||
'(:fuzzy-date . (:name "Date"
|
||||
:shortname "Date"
|
||||
:function (lambda (msg)
|
||||
(my-mu4e-format-date (mu4e-message-field msg :date))))))
|
||||
|
||||
;; UI Configuration - use fuzzy dates in headers
|
||||
(setq mu4e-headers-fields
|
||||
'((:fuzzy-date . 15) ; Fuzzy date with 15 char width
|
||||
(:flags . 6)
|
||||
(:from-or-to . 22)
|
||||
(:subject)))
|
||||
|
||||
;; Make mu4e respect the current color theme
|
||||
(setq mu4e-view-use-gnus t) ; Use Gnus article mode for better theme support
|
||||
(setq shr-use-colors nil) ; Let the theme handle colors in HTML emails
|
||||
|
||||
;; Use full window for reading emails
|
||||
(setq mu4e-split-view nil) ; Don't split, use full window for message view
|
||||
|
||||
;; Make headers/search view respect theme
|
||||
(setq mu4e-headers-unread-face 'bold) ; Use theme's bold face instead of custom color
|
||||
(setq mu4e-headers-highlight-face 'highlight) ; Use theme's highlight face
|
||||
(setq mu4e-headers-flagged-face 'font-lock-warning-face) ; Use theme's warning face
|
||||
|
||||
;; Enable inline images
|
||||
(setq mu4e-view-show-images t)
|
||||
(when (fboundp 'imagemagick-register-types)
|
||||
(imagemagick-register-types))
|
||||
|
||||
;; Colorize inline patches in emails
|
||||
(require 'diff-mode)
|
||||
|
||||
(defun mu4e-colorize-patch ()
|
||||
"Colorize patches in mu4e view buffers."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
;; Look for patch sections (starting with diff, ---, or @@)
|
||||
(while (re-search-forward "^\\(diff \\|--- \\|\\+\\+\\+ \\|@@ \\)" nil t)
|
||||
(let ((patch-start (match-beginning 0)))
|
||||
;; Find the end of the patch
|
||||
(if (re-search-forward "^[^-+@ \t]" nil t)
|
||||
(backward-char)
|
||||
(goto-char (point-max)))
|
||||
(let ((patch-end (point))
|
||||
(inhibit-read-only t))
|
||||
;; Apply diff-mode font-lock to the patch region
|
||||
(add-text-properties patch-start patch-end
|
||||
'(face nil)) ; Reset face first
|
||||
(save-restriction
|
||||
(narrow-to-region patch-start patch-end)
|
||||
(diff-mode)
|
||||
(font-lock-fontify-region patch-start patch-end)
|
||||
(widen))))))
|
||||
;; Also colorize simple diff lines
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\(-.*\\)$" nil t)
|
||||
(let ((inhibit-read-only t))
|
||||
(add-face-text-property (match-beginning 1) (match-end 1)
|
||||
'diff-removed)))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\(\\+.*\\)$" nil t)
|
||||
(let ((inhibit-read-only t))
|
||||
(add-face-text-property (match-beginning 1) (match-end 1)
|
||||
'diff-added)))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\(@@.*@@\\).*$" nil t)
|
||||
(let ((inhibit-read-only t))
|
||||
(add-face-text-property (match-beginning 1) (match-end 1)
|
||||
'diff-hunk-header)))))
|
||||
|
||||
;; Hook to colorize patches when viewing messages
|
||||
(add-hook 'mu4e-view-mode-hook 'mu4e-colorize-patch)
|
||||
|
||||
;; For Gnus article mode (when mu4e-view-use-gnus is t)
|
||||
(with-eval-after-load 'gnus-art
|
||||
(add-hook 'gnus-article-mode-hook 'mu4e-colorize-patch))
|
||||
|
||||
;; Use mu4e for composing email
|
||||
(setq mail-user-agent 'mu4e-user-agent)
|
||||
|
||||
;; Keybindings
|
||||
(global-set-key (kbd "C-x m") 'mu4e)
|
||||
(global-set-key (kbd "C-x M") 'mu4e-compose-new)
|
||||
|
||||
;; Double-check pandoc is available and being used
|
||||
(if (executable-find "pandoc")
|
||||
(message "Pandoc found at: %s" (executable-find "pandoc"))
|
||||
(message "WARNING: Pandoc not found!"))
|
||||
|
||||
;; Prefer plain text when available, but show HTML when it's the only option
|
||||
(setq mu4e-view-prefer-html nil)
|
||||
|
||||
;; Actions
|
||||
(add-to-list 'mu4e-view-actions
|
||||
'("ViewInBrowser" . mu4e-action-view-in-browser) t)
|
||||
|
||||
;; Don't keep message buffers around
|
||||
(setq message-kill-buffer-on-exit t)
|
||||
|
||||
;; For Proton Bridge: Comment out custom marks for now to avoid errors
|
||||
;; The default behavior will be:
|
||||
;; d - move to trash
|
||||
;; D - delete permanently
|
||||
;; r - refile/archive
|
||||
|
||||
;; If you want to customize deletion behavior, uncomment and adjust:
|
||||
;; (with-eval-after-load 'mu4e
|
||||
;; ;; Make 'd' archive instead of trash
|
||||
;; (setf (alist-get 'trash mu4e-marks)
|
||||
;; (list :char '("d" . "▼")
|
||||
;; :prompt "archive"
|
||||
;; :dyn-target (lambda (target msg)
|
||||
;; (mu4e-get-refile-folder msg))
|
||||
;; :action (lambda (docid msg target)
|
||||
;; (mu4e--server-move docid
|
||||
;; (mu4e--mark-check-target target)
|
||||
;; "-N")))))
|
||||
|
||||
;; Test function to verify pandoc is working
|
||||
(defun mu4e-test-pandoc ()
|
||||
"Test if pandoc is being used for HTML rendering."
|
||||
(interactive)
|
||||
(let ((test-html "<html><body><h1>Test Header</h1><p>This is a <strong>test</strong> paragraph with <em>emphasis</em>.</p><ul><li>Item 1</li><li>Item 2</li></ul></body></html>")
|
||||
(temp-file (make-temp-file "mu4e-pandoc-test" nil ".html")))
|
||||
(with-temp-file temp-file
|
||||
(insert test-html))
|
||||
(message "Testing pandoc with command: %s" mu4e-html2text-command)
|
||||
(message "Input HTML:\n%s" test-html)
|
||||
(message "Pandoc output:\n%s"
|
||||
(shell-command-to-string
|
||||
(format "%s < %s" mu4e-html2text-command temp-file)))
|
||||
(delete-file temp-file)))
|
||||
|
||||
;; Mailing lists configuration
|
||||
(setq mu4e-mailing-lists
|
||||
'((:list-id "linux-kernel.vger.linux.org" :name "linux-kernel")
|
||||
(:list-id "std-discussion.lists.isocpp.org" :name "std-discussion")
|
||||
(:list-id "gcc.gnu.gcc.org" :name "gnu-gcc")
|
||||
(:list-id "interest.qt-project.org" :name "Qt-Interest")
|
||||
(:list-id "boost.lists.boost.org" :name "Boost")
|
||||
(:list-id "boost-announce.lists.boost.org" :name "Boost-Announce")
|
||||
(:list-id "boost-interest.lists.boost.org" :name "Boost-Interest")))
|
||||
|
||||
;; Signature
|
||||
(setq mu4e-compose-signature
|
||||
"Jens")
|
||||
|
||||
;; SMTP Configuration for sending mail
|
||||
(require 'smtpmail)
|
||||
(setq message-send-mail-function 'smtpmail-send-it)
|
||||
|
||||
;; Default SMTP settings (will be overridden by context)
|
||||
(setq smtpmail-stream-type 'starttls
|
||||
smtpmail-smtp-service 587
|
||||
smtpmail-debug-info t
|
||||
smtpmail-debug-verb t
|
||||
smtpmail-auth-credentials "~/.authinfo")
|
||||
|
||||
;; Configure context policy
|
||||
(setq mu4e-context-policy 'pick-first
|
||||
mu4e-compose-context-policy 'ask-if-none)
|
||||
|
||||
;; Function to set SMTP parameters based on From address
|
||||
(defun my-mu4e-set-smtp-params ()
|
||||
"Set SMTP parameters based on the From address."
|
||||
(let ((from (message-field-value "From")))
|
||||
(cond
|
||||
;; Personal account - also handles alias addresses @luedicke.me and @luedicke.xyz
|
||||
((or (string-match "@luedicke\\.me" from)
|
||||
(string-match "@luedicke\\.xyz" from))
|
||||
(message "Setting SMTP for Personal account (Proton Bridge)...")
|
||||
(setq smtpmail-smtp-server "127.0.0.1"
|
||||
smtpmail-smtp-service 1025
|
||||
smtpmail-stream-type 'starttls
|
||||
smtpmail-smtp-user "jens@luedicke.me" ; Always use main account for auth
|
||||
smtpmail-auth-credentials "~/.authinfo"
|
||||
smtpmail-smtp-timeout 30)) ; 30 second timeout
|
||||
((string-match "jens@luedicke.cloud" from)
|
||||
(message "Setting SMTP for IONOS account...")
|
||||
(setq smtpmail-smtp-server "smtp.ionos.de"
|
||||
smtpmail-smtp-service 587
|
||||
smtpmail-stream-type 'starttls
|
||||
smtpmail-smtp-user "jens@luedicke.cloud"
|
||||
smtpmail-auth-credentials "~/.authinfo"
|
||||
smtpmail-smtp-timeout 30)) ; 30 second timeout
|
||||
(t
|
||||
(error "Unknown sender address: %s" from)))))
|
||||
|
||||
;; Hook to set SMTP params before sending
|
||||
(add-hook 'message-send-hook 'my-mu4e-set-smtp-params)
|
||||
|
||||
;; Alias email addresses configuration
|
||||
;; Define your email aliases here
|
||||
(setq my-email-aliases
|
||||
'(("std-discussion@luedicke.xyz" . "std-discussion.lists.isocpp.org")
|
||||
("gnu-gcc@luedicke.xyz" . "gcc.gcc.gnu.org")
|
||||
("qt-interest@luedicke.xyz" . "interest.qt-project.org")
|
||||
("boost@luedicke.xyz" . "boost.lists.boost.org")
|
||||
("jens@luedicke.me" . "linux-kernel.vger.kernel.org")))
|
||||
|
||||
;; Variable to store the desired From address
|
||||
(defvar my-mu4e-reply-address nil
|
||||
"Stores the email address to use for replies to mailing lists.")
|
||||
|
||||
;; Function to determine the reply address based on recipient
|
||||
(defun my-mu4e-set-from-address ()
|
||||
"Set the From address based on the original recipient.
|
||||
If the message was sent to one of our aliases (via mailing list),
|
||||
use that alias as the From address."
|
||||
(setq my-mu4e-reply-address nil) ; Reset first
|
||||
(let ((msg mu4e-compose-parent-message))
|
||||
(when msg
|
||||
(let ((list-id (mu4e-message-field msg :list))
|
||||
(to (mu4e-message-field msg :to))
|
||||
(cc (mu4e-message-field msg :cc)))
|
||||
;; Check if this message came from a mailing list we have an alias for
|
||||
(dolist (alias-pair my-email-aliases)
|
||||
(when (and list-id
|
||||
(string-match-p (regexp-quote (cdr alias-pair)) list-id)
|
||||
(not my-mu4e-reply-address))
|
||||
;; Store the alias to use
|
||||
(setq my-mu4e-reply-address (car alias-pair))
|
||||
(message "Will use alias address: %s" my-mu4e-reply-address)))))))
|
||||
|
||||
;; Function to actually set the From header
|
||||
(defun my-mu4e-compose-set-from ()
|
||||
"Set the From address in the compose buffer."
|
||||
(when my-mu4e-reply-address
|
||||
;; For aliases, we need to keep the authenticated address in From
|
||||
;; but add a Reply-To with the alias address
|
||||
(save-excursion
|
||||
;; Keep the main address in From (for SMTP authentication)
|
||||
(message-remove-header "From")
|
||||
(message-add-header (format "From: %s <%s>" user-full-name "jens@luedicke.me"))
|
||||
|
||||
;; Add Reply-To with the alias address so replies come back to the right address
|
||||
(message-remove-header "Reply-To")
|
||||
(message-add-header (format "Reply-To: %s <%s>" user-full-name my-mu4e-reply-address))
|
||||
|
||||
;; Optionally add a comment in the From field to show which list this is for
|
||||
;; This helps you see which alias you're using
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^From: \\(.*\\) <\\(.*\\)>$" nil t)
|
||||
(replace-match (format "From: %s (via %s) <%s>"
|
||||
user-full-name
|
||||
my-mu4e-reply-address
|
||||
"jens@luedicke.me"))))
|
||||
(message "Using Reply-To address: %s" my-mu4e-reply-address)))
|
||||
|
||||
;; Hook to set the From address when composing replies
|
||||
(add-hook 'mu4e-compose-pre-hook 'my-mu4e-set-from-address)
|
||||
;; Run after a short delay to ensure context switching is complete
|
||||
(add-hook 'mu4e-compose-mode-hook
|
||||
(lambda ()
|
||||
(run-at-time 0.1 nil 'my-mu4e-compose-set-from)))
|
||||
|
||||
;; Update contexts to include SMTP settings
|
||||
(setq mu4e-contexts
|
||||
`(,(make-mu4e-context
|
||||
:name "Personal"
|
||||
:match-func (lambda (msg)
|
||||
(when msg
|
||||
(string-prefix-p "/Personal" (mu4e-message-field msg :maildir))))
|
||||
:vars '((user-mail-address . "jens@luedicke.me")
|
||||
(user-full-name . "Jens Luedicke")
|
||||
(mu4e-drafts-folder . "/Personal/Drafts")
|
||||
(mu4e-sent-folder . "/Personal/Sent")
|
||||
(mu4e-trash-folder . "/Personal/Trash")
|
||||
(mu4e-refile-folder . "/Personal/Archive")))
|
||||
,(make-mu4e-context
|
||||
:name "IONOS"
|
||||
:match-func (lambda (msg)
|
||||
(when msg
|
||||
(string-prefix-p "/IONOS" (mu4e-message-field msg :maildir))))
|
||||
:vars '((user-mail-address . "jens@luedicke.cloud")
|
||||
(user-full-name . "Jens Luedicke")
|
||||
(mu4e-drafts-folder . "/IONOS/Drafts")
|
||||
(mu4e-sent-folder . "/IONOS/Sent")
|
||||
(mu4e-trash-folder . "/IONOS/Trash")
|
||||
(mu4e-refile-folder . "/IONOS/Archive")))))
|
||||
|
||||
;; Optional: Auto-filing rules for incoming mail
|
||||
;; Uncomment and customize these to automatically move messages to folders
|
||||
;; (setq mu4e-headers-auto-update t) ; Auto-update headers buffer
|
||||
|
||||
;; Example auto-filing with mu4e-marks
|
||||
;; This runs when indexing new mail
|
||||
(defun my-mu4e-auto-file ()
|
||||
"Auto-file certain messages to specific folders."
|
||||
(when (mu4e-message-field mu4e-compose-parent-message :subject)
|
||||
(let ((subject (mu4e-message-field mu4e-compose-parent-message :subject))
|
||||
(from (mu4e-message-field mu4e-compose-parent-message :from)))
|
||||
|
||||
;; Auto-file newsletters
|
||||
(when (or (string-match-p "newsletter\\|digest\\|weekly" subject)
|
||||
(string-match-p "noreply\\|no-reply\\|newsletter" (car from)))
|
||||
(mu4e-message-field mu4e-compose-parent-message :maildir "/Newsletters"))
|
||||
|
||||
;; Auto-file purchase receipts
|
||||
(when (string-match-p "order\\|receipt\\|invoice\\|purchase" subject)
|
||||
(mu4e-message-field mu4e-compose-parent-message :maildir "/Purchases")))))
|
||||
|
||||
;; Hook to run auto-filing after updating
|
||||
;; (add-hook 'mu4e-index-updated-hook 'my-mu4e-auto-file)
|
||||
|
||||
;; Custom search query functions for advanced users
|
||||
(defun mu4e-search-important ()
|
||||
"Search for important messages."
|
||||
(interactive)
|
||||
(mu4e-search "(flag:flagged OR prio:high OR from:/boss|manager|ceo|director/) AND NOT flag:trashed"))
|
||||
|
||||
(defun mu4e-search-newsletters ()
|
||||
"Search for newsletters."
|
||||
(interactive)
|
||||
(mu4e-search "(list:/.+/ OR from:/newsletter|news|digest/ OR body:/unsubscribe/) AND NOT flag:trashed"))
|
||||
|
||||
(defun mu4e-search-purchases ()
|
||||
"Search for purchase-related emails."
|
||||
(interactive)
|
||||
(mu4e-search "(from:/amazon|ebay|paypal|order|shop/ OR subject:/order|invoice|receipt|purchase/) AND NOT flag:trashed"))
|
||||
|
||||
;; Customization tips:
|
||||
;; 1. To add more bookmarks, add entries to mu4e-bookmarks above
|
||||
;; 2. To customize search patterns, modify the :query strings
|
||||
;; 3. To change keyboard shortcuts, modify the :key values
|
||||
;; 4. To add sender-specific rules, add from:/sender@domain/ patterns
|
||||
;; 5. To exclude certain messages, add AND NOT conditions
|
||||
|
||||
;; VIP sender list example (uncomment and customize):
|
||||
;; (setq my-vip-senders '("boss@company.com" "important@client.com"))
|
||||
;; Then use in queries: (member-if (lambda (vip) (string-match-p vip from)) my-vip-senders)
|
||||
|
||||
(provide 'mu4e-config)
|
||||
;;; mu4e-config.el ends here
|
||||
129
lisp/qml-config.el
Normal file
129
lisp/qml-config.el
Normal file
@@ -0,0 +1,129 @@
|
||||
;;; -*- lexical-binding: t -*-
|
||||
;; QML Mode Configuration (Qt5 - No LSP)
|
||||
;; This file provides QML mode setup without LSP (Qt5 has no language server)
|
||||
|
||||
;; Basic QML mode configuration
|
||||
(use-package qml-mode
|
||||
:ensure t
|
||||
:mode ("\\.qml\\'" . qml-mode)
|
||||
:config
|
||||
;; Set up proper indentation
|
||||
(setq qml-indent-offset 4)
|
||||
|
||||
;; Add QML-specific keywords for better syntax highlighting
|
||||
(font-lock-add-keywords 'qml-mode
|
||||
'(("\\<\\(readonly\\|default\\|signal\\|alias\\|property\\|required\\)\\>" . font-lock-keyword-face)
|
||||
("\\<\\(Qt\\|QtQuick\\|QtQuick\\.Controls\\|QtQuick\\.Layouts\\)\\>" . font-lock-constant-face)
|
||||
("\\<\\(Item\\|Rectangle\\|Text\\|Image\\|MouseArea\\|Column\\|Row\\|Grid\\|ListView\\|GridView\\)\\>" . font-lock-type-face))))
|
||||
|
||||
;; Company backends for QML (without LSP)
|
||||
(with-eval-after-load 'company
|
||||
(defun setup-qml-company-backends ()
|
||||
"Set up company backends for QML mode without LSP."
|
||||
(setq-local company-backends
|
||||
'((company-dabbrev-code ; Code word completions
|
||||
company-keywords ; Language keywords
|
||||
company-files ; File completions
|
||||
company-yasnippet) ; Snippet completions
|
||||
company-dabbrev))) ; General word completions
|
||||
|
||||
;; Apply to QML mode
|
||||
(add-hook 'qml-mode-hook 'setup-qml-company-backends))
|
||||
|
||||
;; QML snippets configuration
|
||||
(with-eval-after-load 'yasnippet
|
||||
;; Create QML snippets directory if it doesn't exist
|
||||
(let ((qml-snippets-dir (expand-file-name "snippets/qml-mode" user-emacs-directory)))
|
||||
(unless (file-exists-p qml-snippets-dir)
|
||||
(make-directory qml-snippets-dir t))))
|
||||
|
||||
;; Prevent LSP from being registered for QML
|
||||
(with-eval-after-load 'lsp-mode
|
||||
;; Remove QML from LSP language configurations
|
||||
(setq lsp-language-id-configuration
|
||||
(assq-delete-all 'qml-mode lsp-language-id-configuration))
|
||||
;; Unregister any QML LSP clients
|
||||
(when (boundp 'lsp-clients)
|
||||
(setq lsp-clients (delq 'qmlls lsp-clients))))
|
||||
|
||||
;; Prevent Eglot from activating in QML mode
|
||||
(with-eval-after-load 'eglot
|
||||
;; Remove QML from eglot server programs if present
|
||||
(setq eglot-server-programs
|
||||
(assq-delete-all 'qml-mode eglot-server-programs)))
|
||||
|
||||
;; QML development settings
|
||||
(add-hook 'qml-mode-hook
|
||||
(lambda ()
|
||||
;; Forcefully disable LSP for QML files (Qt5 has no language server)
|
||||
(when (bound-and-true-p lsp-mode)
|
||||
(lsp-disconnect)
|
||||
(lsp-mode -1))
|
||||
(when (bound-and-true-p lsp-managed-mode)
|
||||
(lsp-managed-mode -1))
|
||||
;; Also disable Eglot if it tries to start
|
||||
(when (bound-and-true-p eglot--managed-mode)
|
||||
(eglot-shutdown)
|
||||
(eglot--managed-mode -1))
|
||||
;; Electric pair mode for automatic bracket/quote pairing
|
||||
(electric-pair-local-mode 1)
|
||||
;; Enable automatic indentation
|
||||
(electric-indent-local-mode 1)
|
||||
;; Set tab width
|
||||
(setq tab-width 4)
|
||||
;; Use spaces instead of tabs
|
||||
(setq indent-tabs-mode nil)
|
||||
;; Enable line numbers
|
||||
(display-line-numbers-mode 1)
|
||||
;; Disable flycheck (no QML checker for Qt5)
|
||||
(when (bound-and-true-p flycheck-mode)
|
||||
(flycheck-mode -1))))
|
||||
|
||||
;; Simple navigation functions for QML
|
||||
(defun qml-find-definition ()
|
||||
"Simple definition finder using grep."
|
||||
(interactive)
|
||||
(let ((thing (thing-at-point 'symbol)))
|
||||
(when thing
|
||||
(grep (format "grep -n \"\\b%s\\b\" *.qml" thing)))))
|
||||
|
||||
(defun qml-find-references ()
|
||||
"Simple reference finder using grep."
|
||||
(interactive)
|
||||
(let ((thing (thing-at-point 'symbol)))
|
||||
(when thing
|
||||
(grep (format "grep -n \"\\b%s\\b\" *.qml" thing)))))
|
||||
|
||||
;; Key bindings for QML development (without LSP)
|
||||
(with-eval-after-load 'qml-mode
|
||||
(define-key qml-mode-map (kbd "C-c C-d") 'qml-find-definition)
|
||||
(define-key qml-mode-map (kbd "C-c C-f") 'qml-find-references)
|
||||
(define-key qml-mode-map (kbd "C-c C-c") 'comment-region)
|
||||
(define-key qml-mode-map (kbd "C-c C-u") 'uncomment-region))
|
||||
|
||||
;; Helper function to insert common QML snippets
|
||||
(defun qml-insert-property ()
|
||||
"Insert a QML property declaration."
|
||||
(interactive)
|
||||
(insert "property ")
|
||||
(save-excursion (insert ": ")))
|
||||
|
||||
(defun qml-insert-signal ()
|
||||
"Insert a QML signal declaration."
|
||||
(interactive)
|
||||
(insert "signal ")
|
||||
(save-excursion (insert "()")))
|
||||
|
||||
(defun qml-insert-function ()
|
||||
"Insert a QML function declaration."
|
||||
(interactive)
|
||||
(insert "function ")
|
||||
(save-excursion (insert "() {\n \n}")))
|
||||
|
||||
;; Add snippet key bindings
|
||||
(with-eval-after-load 'qml-mode
|
||||
(define-key qml-mode-map (kbd "C-c i p") 'qml-insert-property)
|
||||
(define-key qml-mode-map (kbd "C-c i s") 'qml-insert-signal)
|
||||
(define-key qml-mode-map (kbd "C-c i f") 'qml-insert-function))
|
||||
|
||||
(provide 'qml-config)
|
||||
133
lisp/shr-config.el
Normal file
133
lisp/shr-config.el
Normal file
@@ -0,0 +1,133 @@
|
||||
;;; shr-config.el --- SHR (Simple HTML Renderer) configuration -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Global configuration for SHR which is used by mu4e, elfeed, eww, etc.
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Configure SHR for better readability
|
||||
(with-eval-after-load 'shr
|
||||
;; Basic SHR settings
|
||||
(setq shr-use-fonts t) ; Use variable fonts
|
||||
(setq shr-use-colors t) ; Use colors from HTML
|
||||
(setq shr-max-image-proportion 0.7) ; Limit image size
|
||||
(setq shr-width nil) ; Use full window width
|
||||
(setq shr-bullet "• ") ; Nice bullet character
|
||||
(setq shr-inhibit-images nil) ; Enable image display
|
||||
(setq shr-blocked-images nil) ; Don't block any images
|
||||
|
||||
;; Increase indentation for better structure
|
||||
(setq shr-indentation 2)
|
||||
|
||||
;; Cookie policy
|
||||
(setq shr-cookie-policy 'same-origin))
|
||||
|
||||
;; Define a serif face for SHR content
|
||||
(defface shr-text
|
||||
'((t :family "Georgia" :height 1.1))
|
||||
"Face for SHR body text with serif font."
|
||||
:group 'shr)
|
||||
|
||||
;; Override SHR faces to use serif fonts
|
||||
(with-eval-after-load 'shr
|
||||
;; Set default SHR text to use serif font
|
||||
(set-face-attribute 'shr-text nil
|
||||
:family "Georgia" ; You can change to "Times New Roman", "Palatino", "Baskerville", etc.
|
||||
:height 1.1)
|
||||
|
||||
;; Apply serif font to the main text
|
||||
(defun my-shr-tag-p (dom)
|
||||
"Custom paragraph handler to apply serif font."
|
||||
(shr-ensure-paragraph)
|
||||
(let ((shr-current-font 'shr-text))
|
||||
(shr-generic dom))
|
||||
(shr-ensure-paragraph))
|
||||
|
||||
;; Hook to apply serif font after rendering
|
||||
(defun my-shr-apply-serif-font ()
|
||||
"Apply serif font to SHR rendered content."
|
||||
(when (derived-mode-p 'eww-mode 'mu4e-view-mode 'elfeed-show-mode)
|
||||
(buffer-face-set 'shr-text)))
|
||||
|
||||
(add-hook 'shr-after-render-hook 'my-shr-apply-serif-font))
|
||||
|
||||
;; Disable the fill-column indicator (red margin line) in SHR-related modes
|
||||
(defun disable-fill-column-indicator ()
|
||||
"Disable the fill column indicator in the current buffer."
|
||||
(display-fill-column-indicator-mode -1))
|
||||
|
||||
;; Add hooks to disable fill-column indicator in SHR-using modes
|
||||
(add-hook 'eww-mode-hook 'disable-fill-column-indicator)
|
||||
(add-hook 'elfeed-show-mode-hook 'disable-fill-column-indicator)
|
||||
(add-hook 'mu4e-view-mode-hook 'disable-fill-column-indicator)
|
||||
|
||||
;; Also disable it after SHR renders content
|
||||
(add-hook 'shr-after-render-hook 'disable-fill-column-indicator)
|
||||
|
||||
;; Disable line numbers in all modes that use SHR
|
||||
(defun my-shr-disable-line-numbers ()
|
||||
"Disable line numbers in SHR-rendered buffers."
|
||||
(display-line-numbers-mode -1)
|
||||
(setq-local display-line-numbers nil))
|
||||
|
||||
;; Apply to mu4e view mode
|
||||
(with-eval-after-load 'mu4e
|
||||
(add-hook 'mu4e-view-mode-hook 'my-shr-disable-line-numbers))
|
||||
|
||||
;; Apply to elfeed show mode (already done in elfeed-config, but adding here for completeness)
|
||||
(with-eval-after-load 'elfeed
|
||||
(add-hook 'elfeed-show-mode-hook 'my-shr-disable-line-numbers))
|
||||
|
||||
;; Apply to EWW mode
|
||||
(with-eval-after-load 'eww
|
||||
(add-hook 'eww-mode-hook 'my-shr-disable-line-numbers))
|
||||
|
||||
;; Apply to any buffer after SHR renders content
|
||||
(with-eval-after-load 'shr
|
||||
(add-hook 'shr-after-render-hook 'my-shr-disable-line-numbers))
|
||||
|
||||
;; Function to increase font size in any SHR-rendered buffer
|
||||
(defun shr-increase-font-size ()
|
||||
"Increase font size in SHR-rendered content."
|
||||
(interactive)
|
||||
(text-scale-increase 1))
|
||||
|
||||
;; Function to decrease font size in any SHR-rendered buffer
|
||||
(defun shr-decrease-font-size ()
|
||||
"Decrease font size in SHR-rendered content."
|
||||
(interactive)
|
||||
(text-scale-decrease 1))
|
||||
|
||||
;; Function to reset font size
|
||||
(defun shr-reset-font-size ()
|
||||
"Reset font size to default."
|
||||
(interactive)
|
||||
(text-scale-set 0))
|
||||
|
||||
;; Auto-increase font size in specific modes that use SHR
|
||||
(defun auto-increase-shr-font-size ()
|
||||
"Automatically increase font size in SHR content."
|
||||
(text-scale-set 1)) ; Increase by 1 step. Change to 2 or 3 for larger increase
|
||||
|
||||
;; Apply to mu4e
|
||||
(with-eval-after-load 'mu4e
|
||||
(add-hook 'mu4e-view-mode-hook 'auto-increase-shr-font-size))
|
||||
|
||||
;; Apply to elfeed
|
||||
(with-eval-after-load 'elfeed
|
||||
(add-hook 'elfeed-show-mode-hook 'auto-increase-shr-font-size))
|
||||
|
||||
;; Apply to EWW
|
||||
(with-eval-after-load 'eww
|
||||
(add-hook 'eww-mode-hook 'auto-increase-shr-font-size))
|
||||
|
||||
;; Keybindings for manual font size adjustment
|
||||
(with-eval-after-load 'shr
|
||||
;; These will work in any buffer with SHR content
|
||||
(define-key shr-map (kbd "+") 'shr-increase-font-size)
|
||||
(define-key shr-map (kbd "=") 'shr-increase-font-size)
|
||||
(define-key shr-map (kbd "-") 'shr-decrease-font-size)
|
||||
(define-key shr-map (kbd "0") 'shr-reset-font-size))
|
||||
|
||||
(provide 'shr-config)
|
||||
;;; shr-config.el ends here
|
||||
343
lisp/symbol-finder.el
Normal file
343
lisp/symbol-finder.el
Normal file
@@ -0,0 +1,343 @@
|
||||
;;; symbol-finder.el --- Jump to symbol definitions using Python symbol finder -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; This package provides integration with the Python symbol_finder.py tool
|
||||
;; for quickly jumping to symbol definitions in C++ and QML files.
|
||||
;;
|
||||
;; Usage:
|
||||
;; 1. Add this file to your Emacs load path
|
||||
;; 2. Add (require 'symbol-finder) to your init.el
|
||||
;; 3. Optionally customize symbol-finder-python-script path
|
||||
;; 4. Use M-. to jump to definition (or customize keybinding)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defgroup symbol-finder nil
|
||||
"Jump to symbol definitions using Python symbol finder."
|
||||
:group 'tools)
|
||||
|
||||
(defcustom symbol-finder-python-script
|
||||
(expand-file-name "symbol_finder.py"
|
||||
(file-name-directory (or load-file-name buffer-file-name)))
|
||||
"Path to the symbol_finder.py script."
|
||||
:type 'file
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-root-directory nil
|
||||
"Root directory for symbol indexing. If nil, use project root or default-directory."
|
||||
:type '(choice (const :tag "Auto-detect" nil)
|
||||
(directory :tag "Directory"))
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-cache-directory ".symbol_cache"
|
||||
"Directory name for symbol cache (relative to root or absolute path)."
|
||||
:type 'string
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-use-absolute-cache nil
|
||||
"If non-nil, treat cache-directory as an absolute path."
|
||||
:type 'boolean
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-auto-index t
|
||||
"Whether to automatically index files when cache is missing."
|
||||
:type 'boolean
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defvar symbol-finder--history nil
|
||||
"History of searched symbols.")
|
||||
|
||||
(defvar symbol-finder--marker-ring (make-ring 20)
|
||||
"Ring of markers for jumping back.")
|
||||
|
||||
(defun symbol-finder--get-root ()
|
||||
"Get the root directory for symbol operations."
|
||||
(or symbol-finder-root-directory
|
||||
(when (fboundp 'projectile-project-root)
|
||||
(projectile-project-root))
|
||||
(when (fboundp 'project-root)
|
||||
(car (project-roots (project-current))))
|
||||
default-directory))
|
||||
|
||||
(defun symbol-finder--get-cache-dir ()
|
||||
"Get the cache directory path."
|
||||
(if (or symbol-finder-use-absolute-cache
|
||||
(file-name-absolute-p symbol-finder-cache-directory))
|
||||
symbol-finder-cache-directory
|
||||
(expand-file-name symbol-finder-cache-directory (symbol-finder--get-root))))
|
||||
|
||||
(defun symbol-finder--run-command (args)
|
||||
"Run symbol_finder.py with ARGS and return output."
|
||||
(let* ((root-dir (symbol-finder--get-root))
|
||||
(cache-dir (symbol-finder--get-cache-dir))
|
||||
(default-directory root-dir)
|
||||
(cmd (format "python3 %s --root %s --cache-dir %s %s"
|
||||
(shell-quote-argument symbol-finder-python-script)
|
||||
(shell-quote-argument root-dir)
|
||||
(shell-quote-argument cache-dir)
|
||||
args)))
|
||||
(shell-command-to-string cmd)))
|
||||
|
||||
(defun symbol-finder-index (&optional force)
|
||||
"Index all source files. With prefix arg FORCE, force reindex."
|
||||
(interactive "P")
|
||||
(message "Indexing files...")
|
||||
(let* ((args (if force "--index --force" "--index"))
|
||||
(output (symbol-finder--run-command args)))
|
||||
(message "%s" (string-trim output))))
|
||||
|
||||
(defun symbol-finder--parse-emacs-output (output)
|
||||
"Parse Emacs-formatted output from symbol finder."
|
||||
(let ((lines (split-string output "\n" t))
|
||||
results)
|
||||
(dolist (line lines)
|
||||
(when (string-match "\\(.+?\\):\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)" line)
|
||||
(push `(:file ,(match-string 1 line)
|
||||
:line ,(string-to-number (match-string 2 line))
|
||||
:column ,(string-to-number (match-string 3 line))
|
||||
:context ,(match-string 4 line))
|
||||
results)))
|
||||
(nreverse results)))
|
||||
|
||||
(defun symbol-finder--push-mark ()
|
||||
"Push current position to marker ring."
|
||||
(ring-insert symbol-finder--marker-ring (point-marker)))
|
||||
|
||||
(defun symbol-finder-jump-to-definition (&optional symbol)
|
||||
"Jump to definition of SYMBOL at point or prompt for symbol."
|
||||
(interactive)
|
||||
(message "symbol-finder-jump-to-definition called") ;; Debug
|
||||
(let* ((symbol (or symbol
|
||||
(thing-at-point 'symbol t)
|
||||
(read-string "Symbol: " nil 'symbol-finder--history)))
|
||||
(cmd (format "--definition %s --emacs" (shell-quote-argument symbol)))
|
||||
(output (symbol-finder--run-command cmd)))
|
||||
(message "Command: python3 %s %s" symbol-finder-python-script cmd) ;; Debug
|
||||
(message "Output: %s" output) ;; Debug
|
||||
(let ((results (symbol-finder--parse-emacs-output output)))
|
||||
(cond
|
||||
((null results)
|
||||
(message "No definition found for '%s'" symbol))
|
||||
((= 1 (length results))
|
||||
(let ((result (car results)))
|
||||
(symbol-finder--push-mark)
|
||||
(find-file (plist-get result :file))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (plist-get result :line)))
|
||||
(move-to-column (1- (plist-get result :column)))
|
||||
(pulse-momentary-highlight-one-line (point))))
|
||||
(t
|
||||
(symbol-finder--select-and-jump results symbol))))))
|
||||
|
||||
(defun symbol-finder-find-references (&optional symbol)
|
||||
"Find all references to SYMBOL at point or prompt for symbol."
|
||||
(interactive)
|
||||
(let* ((symbol (or symbol
|
||||
(thing-at-point 'symbol t)
|
||||
(read-string "Find references to: " nil 'symbol-finder--history)))
|
||||
(output (symbol-finder--run-command
|
||||
(format "--references %s --emacs" (shell-quote-argument symbol)))))
|
||||
(with-current-buffer (get-buffer-create "*Symbol References*")
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert output)
|
||||
(grep-mode)
|
||||
(goto-char (point-min)))
|
||||
(display-buffer (current-buffer)))))
|
||||
|
||||
(defun symbol-finder-find-symbol (&optional exact)
|
||||
"Find symbol by name. With prefix arg EXACT, use exact match."
|
||||
(interactive "P")
|
||||
(let* ((initial (thing-at-point 'symbol t))
|
||||
(symbol (read-string (format "Find symbol%s: " (if exact " (exact)" ""))
|
||||
initial 'symbol-finder--history))
|
||||
(args (format "--find %s --emacs %s"
|
||||
(shell-quote-argument symbol)
|
||||
(if exact "--exact" "")))
|
||||
(output (symbol-finder--run-command args))
|
||||
(results (symbol-finder--parse-emacs-output output)))
|
||||
(cond
|
||||
((null results)
|
||||
(message "No symbols found matching '%s'" symbol))
|
||||
((= 1 (length results))
|
||||
(let ((result (car results)))
|
||||
(symbol-finder--push-mark)
|
||||
(find-file (plist-get result :file))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (plist-get result :line)))
|
||||
(pulse-momentary-highlight-one-line (point))))
|
||||
(t
|
||||
(symbol-finder--select-and-jump results symbol)))))
|
||||
|
||||
(defun symbol-finder--select-and-jump (results symbol)
|
||||
"Let user select from RESULTS and jump to selected SYMBOL."
|
||||
(let* ((choices (mapcar (lambda (r)
|
||||
(format "%s:%d: %s"
|
||||
(file-name-nondirectory (plist-get r :file))
|
||||
(plist-get r :line)
|
||||
(plist-get r :context)))
|
||||
results))
|
||||
(choice (completing-read (format "Select %s: " symbol) choices nil t))
|
||||
(index (cl-position choice choices :test 'equal)))
|
||||
(when index
|
||||
(let ((result (nth index results)))
|
||||
(symbol-finder--push-mark)
|
||||
(find-file (plist-get result :file))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (plist-get result :line)))
|
||||
(pulse-momentary-highlight-one-line (point))))))
|
||||
|
||||
(defun symbol-finder-cache-status ()
|
||||
"Show cache status and statistics."
|
||||
(interactive)
|
||||
(let* ((root-dir (symbol-finder--get-root))
|
||||
(output (symbol-finder--run-command "--stats")))
|
||||
(with-current-buffer (get-buffer-create "*Symbol Cache Status*")
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert "Symbol Finder Cache Status\n")
|
||||
(insert "==========================\n\n")
|
||||
(insert (format "Root directory: %s\n" root-dir))
|
||||
(insert (format "Python script: %s\n\n" symbol-finder-python-script))
|
||||
(insert output)
|
||||
(goto-char (point-min)))
|
||||
(display-buffer (current-buffer)))))
|
||||
|
||||
(defun symbol-finder-diagnose ()
|
||||
"Diagnose symbol-finder setup and keybindings."
|
||||
(interactive)
|
||||
(let ((global-binding (global-key-binding (kbd "M-.")))
|
||||
(local-binding (local-key-binding (kbd "M-.")))
|
||||
(mode-binding (and (boundp 'symbol-finder-mode-map)
|
||||
(lookup-key symbol-finder-mode-map (kbd "M-.")))))
|
||||
(message "=== Symbol-Finder Diagnostic ===")
|
||||
(message "Symbol-finder-mode: %s" (if symbol-finder-mode "ON" "OFF"))
|
||||
(message "Python script: %s" (if (file-exists-p symbol-finder-python-script)
|
||||
"FOUND" "NOT FOUND"))
|
||||
(message "M-. global binding: %s" global-binding)
|
||||
(message "M-. local binding: %s" local-binding)
|
||||
(message "M-. mode binding: %s" mode-binding)
|
||||
(message "Active minor modes: %s" (mapcar 'car minor-mode-alist))
|
||||
(when (eq global-binding 'xref-find-definitions)
|
||||
(message "NOTE: M-. is bound to xref. You may want to use symbol-finder-setup-override-xref"))))
|
||||
|
||||
(defun symbol-finder-setup-override-xref ()
|
||||
"Override xref M-. binding with symbol-finder in current buffer."
|
||||
(interactive)
|
||||
(local-set-key (kbd "M-.") 'symbol-finder-jump-to-definition)
|
||||
(local-set-key (kbd "M-,") 'symbol-finder-pop-mark)
|
||||
(local-set-key (kbd "M-?") 'symbol-finder-find-references)
|
||||
(message "M-. now bound to symbol-finder-jump-to-definition in this buffer"))
|
||||
|
||||
(defun symbol-finder-pop-mark ()
|
||||
"Pop back to previous position in marker ring."
|
||||
(interactive)
|
||||
(if (ring-empty-p symbol-finder--marker-ring)
|
||||
(message "No previous position")
|
||||
(let ((marker (ring-remove symbol-finder--marker-ring 0)))
|
||||
(switch-to-buffer (marker-buffer marker))
|
||||
(goto-char marker))))
|
||||
|
||||
(defun symbol-finder-update-file ()
|
||||
"Update index for current file."
|
||||
(interactive)
|
||||
(when buffer-file-name
|
||||
(message "Updating index for %s..." buffer-file-name)
|
||||
(let* ((args (format "--index --force --root %s"
|
||||
(shell-quote-argument (file-name-directory buffer-file-name))))
|
||||
(output (symbol-finder--run-command args)))
|
||||
(message "Index updated"))))
|
||||
|
||||
;; Auto-update on save
|
||||
(defun symbol-finder--after-save-hook ()
|
||||
"Hook to update index after saving."
|
||||
(when (and buffer-file-name
|
||||
(string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\|qml\\|js\\)$"
|
||||
buffer-file-name))
|
||||
(symbol-finder-update-file)))
|
||||
|
||||
(defcustom symbol-finder-auto-update-on-save nil
|
||||
"Whether to automatically update index on file save."
|
||||
:type 'boolean
|
||||
:group 'symbol-finder
|
||||
:set (lambda (symbol value)
|
||||
(set-default symbol value)
|
||||
(if value
|
||||
(add-hook 'after-save-hook 'symbol-finder--after-save-hook)
|
||||
(remove-hook 'after-save-hook 'symbol-finder--after-save-hook))))
|
||||
|
||||
;; Minor mode for keybindings
|
||||
(defvar symbol-finder-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "M-.") 'symbol-finder-jump-to-definition)
|
||||
(define-key map (kbd "M-?") 'symbol-finder-find-references)
|
||||
(define-key map (kbd "M-,") 'symbol-finder-pop-mark)
|
||||
(define-key map (kbd "C-c s f") 'symbol-finder-find-symbol)
|
||||
(define-key map (kbd "C-c s i") 'symbol-finder-index)
|
||||
(define-key map (kbd "C-c s u") 'symbol-finder-update-file)
|
||||
map)
|
||||
"Keymap for symbol-finder-mode.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode symbol-finder-mode
|
||||
"Minor mode for symbol navigation using Python symbol finder."
|
||||
:lighter " SymF"
|
||||
:keymap symbol-finder-mode-map
|
||||
:group 'symbol-finder
|
||||
(when symbol-finder-mode
|
||||
;; Debug message
|
||||
(message "Symbol-finder-mode activated. M-. bound to: %s"
|
||||
(lookup-key symbol-finder-mode-map (kbd "M-.")))
|
||||
(when (and symbol-finder-auto-index
|
||||
(not (file-exists-p
|
||||
(expand-file-name symbol-finder-cache-directory
|
||||
(symbol-finder--get-root)))))
|
||||
(when (y-or-n-p "No symbol cache found. Index files now?")
|
||||
(symbol-finder-index)))))
|
||||
|
||||
;;;###autoload
|
||||
(define-globalized-minor-mode global-symbol-finder-mode
|
||||
symbol-finder-mode
|
||||
(lambda ()
|
||||
(when (and (not (minibufferp))
|
||||
(string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\|qml\\|js\\)$"
|
||||
(or buffer-file-name "")))
|
||||
(symbol-finder-mode 1))))
|
||||
|
||||
;; Compatibility with xref (optional)
|
||||
(when (fboundp 'xref-make)
|
||||
(defun symbol-finder-xref-backend ()
|
||||
"Symbol finder backend for xref."
|
||||
'symbol-finder)
|
||||
|
||||
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql symbol-finder)))
|
||||
(thing-at-point 'symbol t))
|
||||
|
||||
(cl-defmethod xref-backend-definitions ((_backend (eql symbol-finder)) identifier)
|
||||
(let* ((output (symbol-finder--run-command
|
||||
(format "--definition %s --emacs" (shell-quote-argument identifier))))
|
||||
(results (symbol-finder--parse-emacs-output output)))
|
||||
(mapcar (lambda (r)
|
||||
(xref-make (plist-get r :context)
|
||||
(xref-make-file-location (plist-get r :file)
|
||||
(plist-get r :line)
|
||||
(1- (plist-get r :column)))))
|
||||
results)))
|
||||
|
||||
(cl-defmethod xref-backend-references ((_backend (eql symbol-finder)) identifier)
|
||||
(let* ((output (symbol-finder--run-command
|
||||
(format "--references %s --emacs" (shell-quote-argument identifier))))
|
||||
(results (symbol-finder--parse-emacs-output output)))
|
||||
(mapcar (lambda (r)
|
||||
(xref-make (plist-get r :context)
|
||||
(xref-make-file-location (plist-get r :file)
|
||||
(plist-get r :line)
|
||||
(1- (plist-get r :column)))))
|
||||
results)))
|
||||
|
||||
(add-hook 'symbol-finder-mode-hook
|
||||
(lambda ()
|
||||
(add-hook 'xref-backend-functions 'symbol-finder-xref-backend nil t))))
|
||||
|
||||
(provide 'symbol-finder)
|
||||
;;; symbol-finder.el ends here
|
||||
Reference in New Issue
Block a user