Move all *.el files to ./lisp

This commit is contained in:
Jens Luedicke
2025-09-09 16:08:15 +02:00
parent 8567e48c4c
commit a4328ce3ba
19 changed files with 894 additions and 39 deletions

587
lisp/bungee.el Normal file
View 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

View 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
View 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

View 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
View 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
View 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
View 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

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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

View File

@@ -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
View 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
View 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
View 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
View 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