Compare commits

...

7 Commits

Author SHA1 Message Date
Jens Luedicke
c82777c1e5 Change completion settings 2026-01-27 16:05:29 +01:00
Jens Luedicke
41b8b20f76 Fix font naming issue
* modified: lisp/init-ui.el
2026-01-21 17:11:46 +01:00
Jens Luedicke
b02a15f7e5 Remove unused files 2026-01-21 17:08:24 +01:00
Jens Luedicke
ca74d93e60 Remove unused bungee symbol finder
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:36:19 +01:00
Jens Luedicke
3fd6384b3b Improve startup performance and reduce resource usage
- Defer elfeed auto-update timers until first use (was running at startup)
- Fix consult-project-function to handle nil project gracefully
- Remove duplicate delete-trailing-whitespace hook
- Remove redundant diff-hl find-file-hook (global-diff-hl-mode handles it)
- Reduce treemacs resource usage (lower git entries, disable filewatch)
- Make dired dotfiles-first sorting opt-in (C-c s to sort, C-c S to toggle)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:34:33 +01:00
Jens Luedicke
c6d72d79ed Remove legacy dev config and consolidate to Eglot-based setup
- Delete emacs-dev-config.el (650 lines of legacy lsp-mode config)
- Update emacs-dev-config-modern.el to use Company instead of Corfu
- Remove legacy config loading from init.el and init-utils.el
- Update CLAUDE.md documentation to reflect new architecture

New structure:
- init-eglot.el: LSP support (auto-enables for programming modes)
- emacs-dev-config-modern.el: Optional extras via M-x enable-dev-mode-modern
- init-completion.el: Company completion (single source of truth)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 12:00:27 +01:00
Jens Luedicke
ba8b24b1d9 Fix keybinding conflict and remove dead Corfu code
- Change C-c r to C-c R for crux-rename-buffer-and-file to avoid
  conflict with consult-ripgrep-project-root
- Remove unused corfu and cape from package list, add company instead
- Remove dead Corfu/Cape integration code from init-eglot.el
- Include related indentation and formatting settings cleanup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 12:00:27 +01:00
22 changed files with 384 additions and 2344 deletions

5
.gitignore vendored
View File

@@ -106,10 +106,5 @@ Thumbs.db
# Keep these files
!init.el
!emacs-dev-config.el
!init-bungee.el
!bungee.el
!qml-config.el
!symbol-finder.el
!symbol_finder.py
!keybinding-reference.mdelfeed/

View File

@@ -68,10 +68,9 @@ M-x disable-eslint-in-buffer ; Disable ESLint in current buffer
- `init-editor.el` - Selection keybindings, shift-selection fixes
**Development:**
- `init-eglot.el` - Built-in LSP client configuration
- `init-eglot.el` - Built-in LSP client configuration (auto-enables for programming modes)
- `init-treesitter.el` - Tree-sitter support for Emacs 29+
- `emacs-dev-config-modern.el` - Modern development setup with Eglot
- `emacs-dev-config.el` - Legacy LSP-mode configuration
- `emacs-dev-config-modern.el` - Additional dev tools (yasnippet, origami, etc.) via `M-x enable-dev-mode-modern`
**Fix Modules:**
- `init-emergency-fix.el` - Emergency editing restoration

31
init.el
View File

@@ -5,6 +5,7 @@
;;; and maintainability.
;;; Code:
(server-mode 1)
;; Add lisp subdirectory to load path (avoids load-path warning)
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
@@ -40,20 +41,11 @@
;;; Load optional configurations
;; Development configuration - Modern version with Eglot
;; Development configuration - Eglot-based (init-eglot.el provides base LSP support)
;; Use M-x enable-dev-mode-modern for additional dev tools (yasnippet, origami, etc.)
(let ((dev-config-modern (expand-file-name "lisp/emacs-dev-config-modern.el" user-emacs-directory)))
(when (file-exists-p dev-config-modern)
(load-file dev-config-modern)
(add-hook 'emacs-startup-hook
(lambda ()
(run-with-timer 1 nil
(lambda ()
(message "Modern development mode available. Use M-x enable-dev-mode-modern to activate.")))))))
;; Legacy development configuration (lsp-mode) - kept for compatibility
(let ((dev-config (expand-file-name "lisp/emacs-dev-config.el" user-emacs-directory)))
(when (file-exists-p dev-config)
(load-file dev-config)))
(load-file dev-config-modern)))
;; SHR Configuration (for HTML rendering in mu4e, elfeed, eww)
(let ((shr-config (expand-file-name "lisp/shr-config.el" user-emacs-directory)))
@@ -77,19 +69,6 @@
(error
(message "mu4e configuration available but mu4e not installed. Install mu4e package to enable email.")))))
;; Beancount Configuration
(let ((beancount-config (expand-file-name "lisp/beancount-config.el" user-emacs-directory)))
(when (file-exists-p beancount-config)
(load-file beancount-config)
(message "Beancount portfolio tracking configuration loaded.")))
;; Portfolio Tracker Configuration
(with-eval-after-load 'tabulated-list
(let ((portfolio-tracker (expand-file-name "lisp/portfolio-tracker-v2.el" user-emacs-directory)))
(when (file-exists-p portfolio-tracker)
(load-file portfolio-tracker)
(message "Portfolio tracker with live prices loaded."))))
;; Keybinding fixes are now integrated into the respective configuration files:
;; - Elfeed fixes in lisp/elfeed-config.el
;; - Portfolio tracker fixes in portfolio-tracker-v2.el
@@ -130,8 +109,6 @@
(insert " '(diff-hl-global-modes t)\n")
(insert " '(neo-show-hidden-files t)\n")
(insert " '(neo-window-width 40)\n")
(insert " '(url-proxy-services\n")
(insert " '((\"https\" . \"eudewerepo001:3128\") (\"http\" . \"eudewerepo001:3128\")))\n")
(insert " '(safe-local-variable-values\n")
(insert " '((company-backends\n")
(insert " (company-qml company-capf company-files company-yasnippet))\n")

View File

@@ -1,587 +0,0 @@
;;; 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

@@ -51,15 +51,16 @@
(lambda ()
(message "Feed update complete!"))))
;; Store timer references so we can cancel them
;; Timer references for auto-updates (started lazily on first elfeed use)
(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))
(defvar elfeed-update-timer-hourly nil
"Timer for hourly elfeed updates.")
(defvar elfeed-timers-initialized nil
"Whether elfeed auto-update timers have been started.")
;; Custom function for fuzzy relative timestamps
(defun my-elfeed-search-format-date (date)
"Format DATE as a fuzzy relative time string."
@@ -181,6 +182,9 @@
;; Keybindings for elfeed
(with-eval-after-load 'elfeed
;; Start auto-update timers when elfeed is first opened (lazy initialization)
(add-hook 'elfeed-search-mode-hook #'elfeed-maybe-start-auto-updates)
;; Disable CUA mode in elfeed buffers to allow single-key commands
(add-hook 'elfeed-search-mode-hook
(lambda ()
@@ -213,14 +217,6 @@
(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."
@@ -231,6 +227,7 @@
(when (timerp elfeed-update-timer-hourly)
(cancel-timer elfeed-update-timer-hourly)
(setq elfeed-update-timer-hourly nil))
(setq elfeed-timers-initialized nil)
(message "Elfeed auto-updates stopped."))
(defun elfeed-start-auto-updates ()
@@ -240,9 +237,16 @@
(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))
(run-at-time (* 5 60) (* 60 60) 'elfeed-update))
(setq elfeed-timers-initialized t)
(message "Elfeed auto-updates started."))
(defun elfeed-maybe-start-auto-updates ()
"Start auto-updates if not already initialized.
This is called when elfeed is first opened."
(unless elfeed-timers-initialized
(elfeed-start-auto-updates)))
;; Sorting functions
(defun elfeed-sort-by-date-ascending ()
"Sort elfeed entries by date ascending (oldest first)."

View File

@@ -9,9 +9,7 @@
"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
'(;; Core development tools (Eglot built-in for Emacs 29+)
consult-eglot ; Consult integration with Eglot
flycheck ; Can still use alongside Flymake
yasnippet
@@ -19,26 +17,27 @@
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.")
"List of packages for modern development mode.
Company completion is configured globally in init-completion.el.
Eglot LSP is configured in init-eglot.el.")
(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+
(unless (package-installed-p package)
(package-refresh-contents)
(package-install package))))
@@ -58,15 +57,14 @@
(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)))))
"Setup completion for development.
Company is configured globally in init-completion.el.
This function adds development-specific tweaks."
;; More aggressive completion in programming modes
(add-hook 'prog-mode-hook
(lambda ()
(setq-local company-idle-delay 0.1)
(setq-local company-minimum-prefix-length 1))))
(defun dev-mode-modern-setup-yasnippet ()
"Configure yasnippet for code snippets."

View File

@@ -1,619 +0,0 @@
;;; -*- 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
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
;; 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)))
;; Project management is now handled by project.el in init-project.el
(defun dev-mode-setup-project ()
"Configure project.el for project management."
;; Project configuration is in init-project.el
;; Add any dev-specific project configs here
(with-eval-after-load 'project
(define-key project-prefix-map (kbd "c") 'project-compile)
(define-key project-prefix-map (kbd "t") 'recompile)))
(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)
(require 'project)
(let ((project-root (or (when-let ((proj (project-current)))
(project-root proj))
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)
(require 'project)
(let ((project-root (or (when-let ((proj (project-current)))
(project-root proj))
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)
(require 'project)
(let ((project-root (or (when-let ((proj (project-current)))
(project-root proj))
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-x p : Project commands prefix\n")
(princ " C-c p : Additional project bindings (compatibility)\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-project)
(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)
;; Project.el is built-in, no need to disable
(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

View File

@@ -1,63 +0,0 @@
;;; 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

@@ -20,6 +20,26 @@
;; Enable cycling
(setq vertico-cycle t))
;; Load vertico extensions
(add-to-list 'load-path
(expand-file-name "extensions"
(file-name-directory (locate-library "vertico"))))
;; Vertico directory navigation - arrow keys for file browsing
(use-package vertico-directory
:ensure nil ; Bundled with vertico
:after vertico
:bind (:map vertico-map
;; Navigate up with left arrow
("<left>" . vertico-directory-up)
;; Complete/enter with right arrow
("<right>" . vertico-directory-enter)
;; Backspace goes up if at beginning
("DEL" . vertico-directory-delete-char)
("M-DEL" . vertico-directory-delete-word))
;; Tidy shadowed file names
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
;;; Savehist - Persist history over Emacs restarts
(use-package savehist
:ensure nil

View File

@@ -31,8 +31,7 @@
marginalia ; Rich annotations in minibuffer
embark ; Contextual actions
embark-consult ; Embark integration with Consult
corfu ; In-buffer completion popup
cape ; Completion extensions for Corfu
company ; In-buffer completion (configured in init-completion.el)
;; Markdown & Notes
markdown-mode markdown-toc grip-mode
@@ -55,14 +54,14 @@
;; God mode for modal editing
god-mode
;; Quality of life improvements
helpful ; Better help buffers
undo-tree ; Visual undo history
smartparens ; Better than electric-pair-mode
crux ; Collection of useful functions
ace-window ; Quick window switching
;; Terminal emulator
eat ; Emacs terminal emulator
))
@@ -96,6 +95,75 @@
;;; General Settings
(setq-default indent-tabs-mode nil)
(setq tab-width 4)
;; Set standard indentation offset for all modes
(setq-default standard-indent 4)
;; Mode-specific indentation settings (4 spaces)
(setq-default
;; JavaScript
js-indent-level 4
js2-basic-offset 4
js3-indent-level 4
;; TypeScript
typescript-indent-level 4
;; CSS/SCSS
css-indent-offset 4
scss-indent-offset 4
;; HTML/XML
sgml-basic-offset 4
nxml-child-indent 4
;; Shell scripts
sh-basic-offset 4
sh-indentation 4
;; Lisp
lisp-indent-offset 4
;; Other languages
perl-indent-level 4
cperl-indent-level 4
ruby-indent-level 4
rust-indent-offset 4
scala-indent:step 4
coffee-tab-width 4
lua-indent-level 4
go-ts-mode-indent-offset 4
;; C/C++ Allman style
c-basic-offset 4)
;; C/C++ style configuration - Allman/BSD style with braces on new lines
(setq c-default-style '((c-mode . "bsd")
(c++-mode . "bsd")
(java-mode . "java")
(awk-mode . "awk")
(other . "bsd")))
;; Ensure C/C++ modes use Allman style
(with-eval-after-load 'cc-mode
(defun apply-c-allman-style ()
"Ensure Allman style is applied to C/C++ buffers."
(c-set-style "bsd")
(setq c-basic-offset 4
indent-tabs-mode nil))
(add-hook 'c-mode-hook #'apply-c-allman-style)
(add-hook 'c++-mode-hook #'apply-c-allman-style))
;; Web-mode indentation (for HTML/JSX/TSX files)
(with-eval-after-load 'web-mode
(setq web-mode-markup-indent-offset 4
web-mode-css-indent-offset 4
web-mode-code-indent-offset 4
web-mode-attr-indent-offset 4
web-mode-script-padding 4
web-mode-style-padding 4))
(setq inhibit-startup-screen t)
(setq ring-bell-function 'ignore)
@@ -149,4 +217,4 @@
(message "Desktop restored"))))))
(provide 'init-core)
;;; init-core.el ends here
;;; init-core.el ends here

View File

@@ -28,8 +28,13 @@
(define-key dired-mode-map (kbd "* /") 'dired-mark-directories))
;; Custom sorting: directories first (dotted first), then files (dotted first)
;; This is opt-in to avoid performance overhead on every directory open
(defvar dired-sort-dotfiles-first-enabled nil
"When non-nil, automatically sort dired buffers with dotfiles first.")
(defun dired-sort-dotfiles-first ()
"Sort dired: dirs first (dots first within), then files (dots first within)."
(interactive)
(save-excursion
(let (buffer-read-only)
(goto-char (point-min))
@@ -84,8 +89,26 @@
(insert line "\n")))))
(set-buffer-modified-p nil)))
;; Apply custom sorting after dired reads directory
(add-hook 'dired-after-readin-hook 'dired-sort-dotfiles-first)
(defun dired-maybe-sort-dotfiles-first ()
"Sort dired buffer if `dired-sort-dotfiles-first-enabled' is non-nil."
(when dired-sort-dotfiles-first-enabled
(dired-sort-dotfiles-first)))
(defun dired-toggle-dotfiles-first-sorting ()
"Toggle automatic dotfiles-first sorting in dired."
(interactive)
(setq dired-sort-dotfiles-first-enabled (not dired-sort-dotfiles-first-enabled))
(if dired-sort-dotfiles-first-enabled
(progn
(add-hook 'dired-after-readin-hook 'dired-maybe-sort-dotfiles-first)
(message "Dired dotfiles-first sorting enabled"))
(remove-hook 'dired-after-readin-hook 'dired-maybe-sort-dotfiles-first)
(message "Dired dotfiles-first sorting disabled")))
;; Keybinding to manually sort current buffer or toggle auto-sorting
(with-eval-after-load 'dired
(define-key dired-mode-map (kbd "C-c s") 'dired-sort-dotfiles-first)
(define-key dired-mode-map (kbd "C-c S") 'dired-toggle-dotfiles-first-sorting))
(provide 'init-dired)
;;; init-dired.el ends here

View File

@@ -61,10 +61,10 @@
(add-to-list 'eglot-server-programs
'(qml-mode . ("qmlls"))))
;; Format on save
(defun eglot-format-buffer-on-save ()
"Format buffer with eglot before saving."
(add-hook 'before-save-hook #'eglot-format-buffer -10 t))
;; Format on save - DISABLED to prevent reindenting
;; (defun eglot-format-buffer-on-save ()
;; "Format buffer with eglot before saving."
;; (add-hook 'before-save-hook #'eglot-format-buffer -10 t))
;; Keybindings
(define-key eglot-mode-map (kbd "C-c l r") 'eglot-rename)
@@ -102,15 +102,8 @@
tsx-ts-mode-hook))
(add-hook mode #'eglot-ensure))
;; Integration with Corfu for completion
(with-eval-after-load 'corfu
(setq completion-category-overrides '((eglot (styles orderless))))
(add-hook 'eglot-managed-mode-hook
(lambda ()
(setq-local completion-at-point-functions
(list (cape-super-capf
#'eglot-completion-at-point
#'cape-file))))))
;; Integration with Company for completion (Company configured in init-completion.el)
;; Eglot automatically uses completion-at-point-functions which Company respects
;; Eldoc configuration for better documentation display
(with-eval-after-load 'eldoc

157
lisp/init-perspective.el Normal file
View File

@@ -0,0 +1,157 @@
;;; init-perspective.el --- Perspective and Projectile configuration -*- lexical-binding: t -*-
;;; Commentary:
;;; Workspace management with perspective.el integrated with Projectile
;;; Code:
;; Projectile - Project management
(use-package projectile
:ensure t
:diminish projectile-mode
:init
(setq projectile-keymap-prefix (kbd "C-c p"))
:config
(projectile-mode +1)
;; Use hybrid indexing for best performance
(setq projectile-indexing-method 'hybrid)
;; Enable caching
(setq projectile-enable-caching t)
;; Project root markers
(setq projectile-project-root-files-top-down-recurring
'(".projectile" ".git" ".hg" ".svn"))
(setq projectile-project-root-files
'("Makefile" "package.json" "Cargo.toml" "go.mod"
"pom.xml" "build.gradle" "requirements.txt"
"setup.py" "pyproject.toml" "Gemfile" "composer.json"))
;; Sort files by recently opened
(setq projectile-sort-order 'recentf)
;; Use completion system (works with vertico/consult)
(setq projectile-completion-system 'default)
;; Where to store projectile cache
(setq projectile-cache-file
(expand-file-name "projectile.cache" user-emacs-directory))
(setq projectile-known-projects-file
(expand-file-name "projectile-bookmarks.eld" user-emacs-directory))
;; Integration with consult if available
(with-eval-after-load 'consult
(setq consult-project-function (lambda (_) (projectile-project-root)))))
;; Perspective - Workspace management
(use-package perspective
:ensure t
:bind (("C-x x s" . persp-switch)
("C-x x k" . persp-kill)
("C-x x r" . persp-rename)
("C-x x a" . persp-add-buffer)
("C-x x A" . persp-set-buffer)
("C-x x b" . persp-switch-to-buffer)
("C-x x n" . persp-next)
("C-x x p" . persp-prev)
("C-x x i" . persp-import)
("C-x x m" . persp-merge)
("C-x x u" . persp-unmerge)
("C-x x g" . persp-add-buffer-to-frame-global)
("C-x x x" . persp-switch-last))
:custom
;; Show perspective name in mode line
(persp-mode-prefix-key (kbd "C-x x"))
(persp-initial-frame-name "main")
;; Don't auto-kill buffers when perspective is killed
(persp-kill-foreign-buffer-behaviour nil)
;; Save state on exit
(persp-state-default-file (expand-file-name "persp-state" user-emacs-directory))
:init
(persp-mode +1)
:config
;; Show perspective in mode line
(setq persp-show-modestring t)
;; Remove/bury buffers not associated with the current perspective when switching
(defun my/persp-remove-foreign-buffers ()
"Bury buffers not belonging to the current perspective."
(when (and (boundp 'perspectives-hash) (hash-table-p perspectives-hash))
(let ((persp-buffers (persp-current-buffers)))
(dolist (buf (buffer-list))
(unless (or (memq buf persp-buffers)
(string-prefix-p " " (buffer-name buf)) ; internal buffers
(string-prefix-p "*Messages*" (buffer-name buf))
(string-prefix-p "*scratch*" (buffer-name buf)))
(bury-buffer buf))))))
(add-hook 'persp-activated-hook #'my/persp-remove-foreign-buffers)
;; Save and restore perspectives on Emacs quit/start
(add-hook 'kill-emacs-hook #'persp-state-save)
;; Restore perspectives on startup (after init)
(defun my/persp-restore-state ()
"Restore perspective state if file exists."
(when (file-exists-p persp-state-default-file)
(condition-case err
(persp-state-load persp-state-default-file)
(error
(message "Failed to restore perspective state: %s. Resetting perspectives." (error-message-string err))
(my/persp-reset)))))
(add-hook 'emacs-startup-hook #'my/persp-restore-state))
;; Persp-Projectile - Integration between perspective and projectile
(use-package persp-projectile
:ensure t
:after (perspective projectile)
:bind (:map projectile-mode-map
("C-c p p" . projectile-persp-switch-project))
:config
;; When switching projects, create/switch to a perspective named after the project
(setq projectile-switch-project-action #'projectile-find-file))
;; Helper functions
(defun my/persp-project-switch ()
"Switch to a project and its perspective."
(interactive)
(call-interactively #'projectile-persp-switch-project))
(defun my/persp-kill-project-buffers ()
"Kill all buffers in current perspective/project."
(interactive)
(if (and (boundp 'perspectives-hash) (hash-table-p perspectives-hash))
(when (y-or-n-p (format "Kill all buffers in perspective '%s'? " (persp-current-name)))
(persp-kill-buffer*))
(message "Perspectives not initialized or corrupted. Use M-x my/persp-reset to reset.")))
(defun my/persp-reset ()
"Reset perspectives if they become corrupted."
(interactive)
(when (boundp 'perspectives-hash)
(setq perspectives-hash (make-hash-table :test 'equal)))
(persp-mode +1)
(message "Perspectives reset."))
(defun my/persp-list ()
"List all perspectives."
(interactive)
(if (and (boundp 'perspectives-hash) (hash-table-p perspectives-hash))
(let ((perspectives (persp-names)))
(message "Perspectives: %s" (string-join perspectives ", ")))
(message "Perspectives not initialized or corrupted. Use M-x my/persp-reset to reset.")))
;; Additional keybindings for convenience
(with-eval-after-load 'projectile
(define-key projectile-mode-map (kbd "C-c p P") #'my/persp-project-switch)
(define-key projectile-mode-map (kbd "C-c p K") #'my/persp-kill-project-buffers)
(define-key projectile-mode-map (kbd "C-c p L") #'my/persp-list))
;; Integration with CUA mode - ensure perspective commands work in special modes
(with-eval-after-load 'perspective
(dolist (cmd '(persp-switch persp-kill persp-rename persp-next persp-prev))
(put cmd 'CUA 'move)))
(provide 'init-perspective)
;;; init-perspective.el ends here

View File

@@ -98,7 +98,10 @@
;; Integration with consult if available
(with-eval-after-load 'consult
(setq consult-project-function #'project-root))
(setq consult-project-function
(lambda (_may-prompt)
(when-let ((proj (project-current)))
(project-root proj)))))
;; Keybindings - Main project map on C-x p (built-in)
;; Additional compatibility bindings for muscle memory

View File

@@ -88,7 +88,7 @@
("C-c e" . crux-eval-and-replace)
("C-c w" . crux-swap-windows)
("C-c D" . crux-delete-file-and-buffer)
("C-c r" . crux-rename-buffer-and-file)
("C-c R" . crux-rename-buffer-and-file)
("C-c t" . crux-visit-term-buffer)
("C-c k" . crux-kill-other-buffers)
("C-c TAB" . crux-indent-rigidly-and-copy-to-clipboard)
@@ -110,8 +110,9 @@
([remap kill-whole-line] . crux-kill-whole-line)
([remap kill-line] . crux-smart-kill-line))
:config
;; Use crux cleanup on save for programming modes
(add-hook 'before-save-hook 'crux-cleanup-buffer-or-region))
;; Use crux cleanup on save for programming modes - DISABLED to prevent reindenting
;; (add-hook 'before-save-hook 'crux-cleanup-buffer-or-region)
)
;;; Ace-window - Quick window switching
(use-package ace-window

View File

@@ -26,7 +26,7 @@
treemacs-directory-name-transformer #'identity
treemacs-display-in-side-window t
treemacs-eldoc-display 'simple
treemacs-file-event-delay 2000
treemacs-file-event-delay 5000 ; Increased from 2000 for less CPU usage
treemacs-file-follow-delay 0.2
treemacs-follow-after-init t
treemacs-expand-after-init t
@@ -35,7 +35,7 @@
treemacs-indentation 2
treemacs-indentation-string " "
treemacs-is-never-other-window nil
treemacs-max-git-entries 5000
treemacs-max-git-entries 500 ; Reduced from 5000 for faster git status
treemacs-missing-project-action 'ask
treemacs-move-forward-on-expand nil
treemacs-no-delete-other-windows t
@@ -59,7 +59,7 @@
treemacs-workspace-switch-cleanup nil)
(treemacs-follow-mode t)
(treemacs-filewatch-mode t)
(treemacs-filewatch-mode nil) ; Disabled by default - use M-x treemacs-filewatch-mode to enable
(treemacs-fringe-indicator-mode 'always)
(when treemacs-python-executable
(treemacs-git-commit-diff-mode t))

View File

@@ -85,6 +85,45 @@
;; Enhanced font-lock for tree-sitter modes
(setq treesit-font-lock-level 3) ; Balanced highlighting (was 4, reduced for performance)
;; C/C++ Tree-sitter indentation - Allman style with 4 spaces
(with-eval-after-load 'c-ts-mode
;; Set indentation offset to 4 spaces
(setq c-ts-mode-indent-offset 4)
;; Configure Allman style for C/C++ tree-sitter modes
(defun my-c-ts-mode-setup ()
"Configure C tree-sitter mode for Allman style."
(setq-local indent-tabs-mode nil
tab-width 4
c-ts-mode-indent-offset 4))
(defun my-c++-ts-mode-setup ()
"Configure C++ tree-sitter mode for Allman style."
(setq-local indent-tabs-mode nil
tab-width 4
c-ts-mode-indent-offset 4))
(add-hook 'c-ts-mode-hook #'my-c-ts-mode-setup)
(add-hook 'c++-ts-mode-hook #'my-c++-ts-mode-setup)
;; Custom indentation rules for Allman style
;; These rules ensure braces go on new lines
(setq c-ts-mode-indent-style 'linux) ; Base style, we'll override specific rules
;; Add clang-format support for tree-sitter modes
(with-eval-after-load 'clang-format
;; Bind clang-format to C-c C-f in tree-sitter C/C++ modes
(defun setup-clang-format-for-c-ts-modes ()
"Set up clang-format keybinding for C/C++ tree-sitter modes."
(local-set-key (kbd "C-c C-f") 'clang-format-buffer))
(add-hook 'c-ts-mode-hook #'setup-clang-format-for-c-ts-modes)
(add-hook 'c++-ts-mode-hook #'setup-clang-format-for-c-ts-modes))
;; Note: Tree-sitter C mode indentation is less configurable than cc-mode
;; For full Allman style control, use clang-format (C-c C-f)
(message "C/C++ tree-sitter mode configured for Allman style with 4-space indentation"))
;; Tree-sitter debugging helpers
(defun treesit-inspect-node-at-point ()
"Show tree-sitter node information at point."

View File

@@ -106,11 +106,11 @@
(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"
(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))
(if (> (buffer-size) (* 1024 1024))
"LARGE FILE (>1MB) - highlighting may be disabled"
"Normal size")))
(princ "\nTo fix issues, try:\n")
@@ -127,7 +127,7 @@
(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)
(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))))
@@ -156,9 +156,8 @@
(princ " M-x ensure-cua-bindings\n")
(princ " M-x cua-mode (toggle off and on)\n")))
;; Trailing whitespace
;; Trailing whitespace visualization (deletion handled in init-editor.el)
(setq show-trailing-whitespace t)
(add-hook 'before-save-hook 'delete-trailing-whitespace)
;; Fill column indicator
(setq-default display-fill-column-indicator-column 80)
@@ -192,13 +191,12 @@
:foundry "nil"
:slant 'normal
:weight 'regular
:height 180
:height 120
:width 'normal)
;; Ensure font settings apply to new frames
;; Use the proper font spec format
(add-to-list 'default-frame-alist
(cons 'font (font-spec :family "0xProto Nerd Font Mono" :size 18)))
;; Use string format for fonts starting with numbers (font-spec has parsing issues)
(add-to-list 'default-frame-alist '(font . "0xProto Nerd Font Mono-12"))
;;; Diff-hl face customizations
(with-eval-after-load 'diff-hl

View File

@@ -17,11 +17,6 @@
;; First reload the main init.el
(load-file (expand-file-name "init.el" user-emacs-directory))
;; Reload development config if it exists
(let ((dev-config (expand-file-name "lisp/emacs-dev-config.el" user-emacs-directory)))
(when (file-exists-p dev-config)
(load-file dev-config)))
;; Reload SHR config if it exists
(let ((shr-config (expand-file-name "lisp/shr-config.el" user-emacs-directory)))
(when (file-exists-p shr-config)
@@ -98,7 +93,6 @@ to prevent UI freezing. With prefix ARG, use blocking reload."
(cl-remove-if-not
#'file-exists-p
(list (expand-file-name "init.el" user-emacs-directory)
(expand-file-name "lisp/emacs-dev-config.el" user-emacs-directory)
(expand-file-name "lisp/shr-config.el" user-emacs-directory)
(expand-file-name "lisp/elfeed-config.el" user-emacs-directory)
(expand-file-name "lisp/mu4e-config.el" user-emacs-directory))))
@@ -129,7 +123,6 @@ to prevent UI freezing. With prefix ARG, use blocking reload."
(cl-remove-if-not
#'file-exists-p
(list
(expand-file-name "lisp/emacs-dev-config.el" user-emacs-directory)
(expand-file-name "lisp/shr-config.el" user-emacs-directory)
(expand-file-name "lisp/elfeed-config.el" user-emacs-directory)
(expand-file-name "lisp/mu4e-config.el" user-emacs-directory)))))
@@ -188,7 +181,7 @@ to prevent UI freezing. With prefix ARG, use blocking reload."
(message "Error reloading %s: %s"
(file-name-nondirectory file) err))))
(message "Smart reload complete!"))
(message "No files changed recently. Use C-u C-c C-r for full reload.")))))
(message "No files changed recently. Use C-u C-c C-r for full reload."))))))
(defun reload-current-file ()
"Reload only the current file if it's an Emacs Lisp file."
@@ -271,7 +264,7 @@ This is the fastest reload method but requires byte-compilation."
(defun show-dev-mode-info ()
"Show information about development mode."
(interactive)
(message "Development mode is available. Use M-x enable-dev-mode to activate LSP, company-mode, flycheck, and other development tools."))
(message "Development mode is available. Use M-x enable-dev-mode-modern to activate yasnippet, origami, and other development tools. LSP (Eglot) is already active for programming modes."))
;;; God-mode helpers
(defun enable-god-mode-config ()
@@ -284,7 +277,7 @@ This is the fastest reload method but requires byte-compilation."
(load-file god-config)
(message "God-mode configuration loaded. Press ESC to toggle god-mode."))
(error (message "Failed to load god-mode config: %s" err)))
(message "God-mode config file not found at %s" god-config)))))
(message "God-mode config file not found at %s" god-config))))
(provide 'init-utils)
;;; init-utils.el ends here

View File

@@ -48,9 +48,9 @@
(setq diff-hl-flydiff-delay 0.3)
;; Make sure diff-hl updates on various events
;; Note: find-file-hook not needed as global-diff-hl-mode handles it
(add-hook 'after-save-hook 'diff-hl-update)
(add-hook 'after-revert-hook 'diff-hl-update)
(add-hook 'find-file-hook 'diff-hl-update)
(add-hook 'vc-checkin-hook 'diff-hl-update)
;; Enable globally with a slight delay to speed up initial startup

View File

@@ -1,451 +0,0 @@
;;; portfolio-tracker-v2.el --- Portfolio tracking with transaction history -*- lexical-binding: t -*-
;;; Commentary:
;; Enhanced portfolio tracker with full transaction history and cost basis tracking
;; Supports buy/sell transactions, dividends, and multiple lots (FIFO/LIFO)
;;; Code:
(require 'cl-lib)
(require 'tabulated-list)
(require 'url)
(require 'json)
(defgroup portfolio-tracker nil
"Portfolio tracking with live price updates."
:group 'applications)
(defcustom portfolio-tracker-update-interval 300
"Interval in seconds between automatic price updates."
:type 'integer
:group 'portfolio-tracker)
(defcustom portfolio-tracker-cost-basis-method 'fifo
"Method for calculating cost basis: fifo or lifo."
:type '(choice (const :tag "FIFO" fifo)
(const :tag "LIFO" lifo))
:group 'portfolio-tracker)
;; Data structures
(cl-defstruct portfolio-transaction
date
type ; buy, sell, dividend, split
symbol
shares
price
fees
notes)
(cl-defstruct portfolio-lot
symbol
shares ; remaining shares in this lot
purchase-date
purchase-price
fees)
(cl-defstruct portfolio-holding
symbol
name
total-shares
lots ; list of portfolio-lot structs
average-cost
current-price
previous-close
value
unrealized-gain
unrealized-gain-percent
realized-gain ; from sell transactions
dividends ; total dividends received
type) ; stock, etf, crypto
(defvar portfolio-tracker-transactions nil
"List of all transactions.")
(defvar portfolio-tracker-holdings nil
"Current holdings calculated from transactions.")
(defvar portfolio-tracker-prices-cache nil
"Cache of current prices.")
;; Transaction processing
(defun portfolio-tracker-process-transactions ()
"Process all transactions to calculate current holdings."
(let ((holdings-table (make-hash-table :test 'equal))
(realized-gains (make-hash-table :test 'equal))
(dividends-table (make-hash-table :test 'equal)))
;; Sort transactions by date
(setq portfolio-tracker-transactions
(sort portfolio-tracker-transactions
(lambda (a b)
(string< (portfolio-transaction-date a)
(portfolio-transaction-date b)))))
;; Process each transaction
(dolist (txn portfolio-tracker-transactions)
(let ((symbol (portfolio-transaction-symbol txn))
(type (portfolio-transaction-type txn)))
(cond
;; Buy transaction - add lot
((eq type 'buy)
(let* ((holding (gethash symbol holdings-table))
(new-lot (make-portfolio-lot
:symbol symbol
:shares (portfolio-transaction-shares txn)
:purchase-date (portfolio-transaction-date txn)
:purchase-price (portfolio-transaction-price txn)
:fees (or (portfolio-transaction-fees txn) 0))))
(if holding
(push new-lot (portfolio-holding-lots holding))
(puthash symbol
(make-portfolio-holding
:symbol symbol
:lots (list new-lot)
:realized-gain 0
:dividends 0)
holdings-table))))
;; Sell transaction - remove shares using FIFO/LIFO
((eq type 'sell)
(let ((holding (gethash symbol holdings-table))
(shares-to-sell (portfolio-transaction-shares txn))
(sell-price (portfolio-transaction-price txn))
(realized 0))
(when holding
(let ((lots (if (eq portfolio-tracker-cost-basis-method 'lifo)
(reverse (portfolio-holding-lots holding))
(portfolio-holding-lots holding))))
(dolist (lot lots)
(when (> shares-to-sell 0)
(let ((shares-from-lot (min shares-to-sell
(portfolio-lot-shares lot))))
(setq realized
(+ realized
(* shares-from-lot
(- sell-price (portfolio-lot-purchase-price lot)))))
(setf (portfolio-lot-shares lot)
(- (portfolio-lot-shares lot) shares-from-lot))
(setq shares-to-sell (- shares-to-sell shares-from-lot)))))
;; Remove empty lots
(setf (portfolio-holding-lots holding)
(cl-remove-if (lambda (lot) (<= (portfolio-lot-shares lot) 0))
(portfolio-holding-lots holding)))
;; Add to realized gains
(setf (portfolio-holding-realized-gain holding)
(+ (portfolio-holding-realized-gain holding) realized))))))
;; Dividend transaction
((eq type 'dividend)
(let ((holding (gethash symbol holdings-table)))
(when holding
(setf (portfolio-holding-dividends holding)
(+ (portfolio-holding-dividends holding)
(* (portfolio-transaction-shares txn)
(portfolio-transaction-price txn)))))))))
;; Calculate totals for each holding
(maphash (lambda (symbol holding)
(let ((total-shares 0)
(total-cost 0))
(dolist (lot (portfolio-holding-lots holding))
(setq total-shares (+ total-shares (portfolio-lot-shares lot)))
(setq total-cost (+ total-cost
(* (portfolio-lot-shares lot)
(portfolio-lot-purchase-price lot)))))
(setf (portfolio-holding-total-shares holding) total-shares)
(setf (portfolio-holding-average-cost holding)
(if (> total-shares 0)
(/ total-cost total-shares)
0))))
holdings-table)
;; Convert hash table to list
(setq portfolio-tracker-holdings nil)
(maphash (lambda (symbol holding)
(when (> (portfolio-holding-total-shares holding) 0)
(push holding portfolio-tracker-holdings)))
holdings-table)))
;; Yahoo Finance integration
(defun portfolio-tracker-fetch-price (symbol callback)
"Fetch current price for SYMBOL and call CALLBACK with the result."
(let ((url (format "https://query1.finance.yahoo.com/v8/finance/chart/%s" symbol)))
(url-retrieve url
(lambda (status)
(if (plist-get status :error)
(message "Error fetching price for %s" symbol)
(goto-char (point-min))
(re-search-forward "^$")
(let* ((json-object-type 'plist)
(json-array-type 'list)
(json (json-read))
(result (plist-get json :chart))
(data (car (plist-get result :result)))
(meta (plist-get data :meta))
(price (plist-get meta :regularMarketPrice))
(prev-close (plist-get meta :previousClose))
(name (plist-get meta :longName)))
(funcall callback symbol price prev-close name))))
nil t)))
(defun portfolio-tracker-update-prices ()
"Update prices for all holdings."
(interactive)
(message "Updating prices...")
(dolist (holding portfolio-tracker-holdings)
(portfolio-tracker-fetch-price
(portfolio-holding-symbol holding)
(lambda (symbol price prev-close name)
(let ((h (cl-find symbol portfolio-tracker-holdings
:key #'portfolio-holding-symbol
:test #'string=)))
(when h
(setf (portfolio-holding-current-price h) price)
(setf (portfolio-holding-previous-close h) prev-close)
(when name
(setf (portfolio-holding-name h) name))
;; Calculate unrealized gains
(let ((total-cost 0))
(dolist (lot (portfolio-holding-lots h))
(setq total-cost (+ total-cost
(* (portfolio-lot-shares lot)
(portfolio-lot-purchase-price lot)))))
(setf (portfolio-holding-value h)
(* (portfolio-holding-total-shares h) price))
(setf (portfolio-holding-unrealized-gain h)
(- (portfolio-holding-value h) total-cost))
(setf (portfolio-holding-unrealized-gain-percent h)
(if (> total-cost 0)
(* 100 (/ (portfolio-holding-unrealized-gain h) total-cost))
0))))
(portfolio-tracker-refresh-display))))))
;; Display functions
(eval-and-compile
(defvar portfolio-tracker-mode-map
(let ((map (make-sparse-keymap)))
;; Use string keys directly for single characters
(define-key map "g" 'portfolio-tracker-refresh)
(define-key map "a" 'portfolio-tracker-add-transaction)
(define-key map "t" 'portfolio-tracker-show-transactions)
(define-key map "h" 'portfolio-tracker-refresh-display)
(define-key map "s" 'portfolio-tracker-save)
(define-key map "l" 'portfolio-tracker-load)
(define-key map "r" 'portfolio-tracker-refresh)
(define-key map "q" 'quit-window)
map)
"Keymap for portfolio-tracker-mode.")
(define-derived-mode portfolio-tracker-mode tabulated-list-mode "Portfolio"
"Major mode for tracking investment portfolio.
\\{portfolio-tracker-mode-map}"
:keymap portfolio-tracker-mode-map
;; Disable CUA mode to allow single-key commands
(setq-local cua-mode nil)
(setq-local cua-enable-cua-keys nil)
(setq tabulated-list-format
[("Symbol" 8 t)
("Name" 20 t)
("Shares" 10 t)
("Avg Cost" 10 nil)
("Current" 10 nil)
("Value" 12 nil)
("Unrealized" 12 nil)
("Realized" 10 nil)
("Dividends" 10 nil)
("Total %" 8 nil)])
(setq tabulated-list-padding 2)
(setq tabulated-list-sort-key (cons "Symbol" nil))
(tabulated-list-init-header)))
(define-derived-mode portfolio-transactions-mode tabulated-list-mode "Transactions"
"Major mode for viewing portfolio transactions."
(setq tabulated-list-format
[("Date" 12 t)
("Type" 8 t)
("Symbol" 8 t)
("Shares" 10 nil)
("Price" 10 nil)
("Total" 12 nil)
("Fees" 8 nil)
("Notes" 30 nil)])
(setq tabulated-list-padding 2)
(setq tabulated-list-sort-key (cons "Date" t))
(tabulated-list-init-header))
(defun portfolio-tracker-refresh-display ()
"Refresh the holdings display."
(interactive)
(let ((buf (get-buffer "*Portfolio Holdings*")))
(when buf
(with-current-buffer buf
(let ((inhibit-read-only t))
(setq tabulated-list-entries
(mapcar (lambda (holding)
(list holding
(vector
(portfolio-holding-symbol holding)
(or (portfolio-holding-name holding) "")
(format "%.2f" (portfolio-holding-total-shares holding))
(format "$%.2f" (portfolio-holding-average-cost holding))
(if (portfolio-holding-current-price holding)
(format "$%.2f" (portfolio-holding-current-price holding))
"...")
(format "$%.2f" (or (portfolio-holding-value holding) 0))
(propertize (format "$%.2f" (or (portfolio-holding-unrealized-gain holding) 0))
'face (if (>= (or (portfolio-holding-unrealized-gain holding) 0) 0)
'success 'error))
(format "$%.2f" (portfolio-holding-realized-gain holding))
(format "$%.2f" (portfolio-holding-dividends holding))
(propertize (format "%.1f%%" (or (portfolio-holding-unrealized-gain-percent holding) 0))
'face (if (>= (or (portfolio-holding-unrealized-gain-percent holding) 0) 0)
'success 'error)))))
portfolio-tracker-holdings))
(tabulated-list-print t)
;; Add summary at the bottom
(portfolio-tracker-display-summary)))))))
(defun portfolio-tracker-display-summary ()
"Display portfolio summary with totals at bottom of buffer."
(let ((inhibit-read-only t)
(total-cost 0)
(total-value 0)
(total-unrealized 0)
(total-realized 0)
(total-dividends 0))
;; Calculate totals
(dolist (holding portfolio-tracker-holdings)
(let ((cost (* (portfolio-holding-total-shares holding)
(portfolio-holding-average-cost holding))))
(setq total-cost (+ total-cost cost))
(setq total-value (+ total-value (or (portfolio-holding-value holding) cost)))
(setq total-unrealized (+ total-unrealized (or (portfolio-holding-unrealized-gain holding) 0)))
(setq total-realized (+ total-realized (portfolio-holding-realized-gain holding)))
(setq total-dividends (+ total-dividends (portfolio-holding-dividends holding)))))
;; Display summary
(goto-char (point-max))
(insert "\n" (make-string 100 ?-) "\n")
(insert (propertize "PORTFOLIO TOTALS\n" 'face 'bold))
(insert (format " Total Buy-In (Cost Basis): %s%.2f\n"
(propertize "$" 'face 'default)
total-cost))
(insert (format " Current Market Value: %s%.2f\n"
(propertize "$" 'face 'default)
total-value))
(insert (format " Unrealized Gain/Loss: %s\n"
(propertize (format "$%.2f (%.1f%%)"
total-unrealized
(if (> total-cost 0)
(* 100 (/ total-unrealized total-cost))
0))
'face (if (>= total-unrealized 0) 'success 'error))))
(insert (format " Realized Gain/Loss: %s\n"
(propertize (format "$%.2f" total-realized)
'face (if (>= total-realized 0) 'success 'error))))
(insert (format " Total Dividends Received: %s%.2f\n"
(propertize "$" 'face 'default)
total-dividends))
(insert (make-string 100 ?-) "\n")
(let ((total-gain (+ total-unrealized total-realized total-dividends))
(total-return-pct (if (> total-cost 0)
(* 100 (/ (+ total-unrealized total-dividends) total-cost))
0)))
(insert (format " TOTAL RETURN: %s\n"
(propertize (format "$%.2f (%.1f%%)"
total-gain
total-return-pct)
'face (if (>= total-gain 0) 'success 'error))))
(insert (make-string 100 ?=) "\n"))
(insert "\nKeys: [g] Refresh | [a] Add Transaction | [t] Show Transactions | [s] Save | [l] Load | [q] Quit\n")))
(defun portfolio-tracker-refresh ()
"Refresh prices and redisplay."
(interactive)
(portfolio-tracker-process-transactions)
(portfolio-tracker-update-prices)
(portfolio-tracker-refresh-display))
(defun portfolio-tracker-add-transaction ()
"Add a new transaction interactively."
(interactive)
(let* ((date (read-string "Date (YYYY-MM-DD): " (format-time-string "%Y-%m-%d")))
(type (intern (completing-read "Type: " '("buy" "sell" "dividend") nil t)))
(symbol (upcase (read-string "Symbol: ")))
(shares (read-number "Shares: "))
(price (read-number "Price per share: "))
(fees (read-number "Fees (0 if none): " 0))
(notes (read-string "Notes (optional): ")))
(push (make-portfolio-transaction
:date date
:type type
:symbol symbol
:shares shares
:price price
:fees fees
:notes notes)
portfolio-tracker-transactions)
(portfolio-tracker-process-transactions)
(portfolio-tracker-update-prices)))
(defun portfolio-tracker-show-transactions ()
"Show all transactions in a separate buffer."
(interactive)
(let ((buf (get-buffer-create "*Portfolio Transactions*")))
(with-current-buffer buf
(portfolio-transactions-mode)
(setq tabulated-list-entries
(mapcar (lambda (txn)
(list txn
(vector
(portfolio-transaction-date txn)
(symbol-name (portfolio-transaction-type txn))
(portfolio-transaction-symbol txn)
(format "%.2f" (portfolio-transaction-shares txn))
(format "$%.2f" (portfolio-transaction-price txn))
(format "$%.2f" (* (portfolio-transaction-shares txn)
(portfolio-transaction-price txn)))
(format "$%.2f" (or (portfolio-transaction-fees txn) 0))
(or (portfolio-transaction-notes txn) ""))))
(reverse portfolio-tracker-transactions)))
(tabulated-list-print))
(switch-to-buffer buf)))
(defun portfolio-tracker-save (file)
"Save portfolio data to FILE."
(interactive "FSave portfolio to: ")
(with-temp-file file
(insert ";; Portfolio Tracker Data\n")
(insert ";; Transaction history and settings\n\n")
(insert "(setq portfolio-tracker-transactions\n")
(pp portfolio-tracker-transactions (current-buffer))
(insert ")\n"))
(message "Portfolio saved to %s" file))
(defun portfolio-tracker-load (file)
"Load portfolio data from FILE."
(interactive "fLoad portfolio from: ")
(load-file file)
(portfolio-tracker-process-transactions)
(portfolio-tracker-refresh-display)
(portfolio-tracker-update-prices)
(message "Portfolio loaded from %s" file))
;;;###autoload
(defun portfolio-tracker ()
"Start the portfolio tracker."
(interactive)
(require 'tabulated-list)
(require 'cl-lib)
(let ((buf (get-buffer-create "*Portfolio Holdings*")))
(switch-to-buffer buf)
(unless (eq major-mode 'portfolio-tracker-mode)
(portfolio-tracker-mode))
(when portfolio-tracker-transactions
(portfolio-tracker-process-transactions)
(portfolio-tracker-refresh-display))
(message "Portfolio Tracker: 'a' add transaction | 't' show transactions | 'g' refresh prices | 'l' load portfolio")))
(provide 'portfolio-tracker-v2)
;;; portfolio-tracker-v2.el ends here

View File

@@ -1,508 +0,0 @@
#!/usr/bin/env python3
"""
Symbol finder tool for C++ and QML source files with caching support.
"""
import os
import re
import json
import time
import hashlib
import argparse
import sys
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Set
from dataclasses import dataclass, asdict
from collections import defaultdict
@dataclass
class Symbol:
"""Represents a symbol found in source code."""
name: str
file_path: str
line_number: int
symbol_type: str # 'class', 'function', 'variable', 'property', 'signal', 'method', 'enum', 'namespace'
context: str # Line content for context
def to_dict(self):
return asdict(self)
@classmethod
def from_dict(cls, data):
return cls(**data)
class SymbolCache:
"""Manages caching of symbol information."""
def __init__(self, cache_dir: str = ".symbol_cache"):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
self.index_file = self.cache_dir / "index.json"
self.symbols_file = self.cache_dir / "symbols.json"
self.index = self._load_index()
self.symbols = self._load_symbols()
def _load_index(self) -> Dict[str, Dict]:
"""Load file index from cache."""
if self.index_file.exists():
with open(self.index_file, 'r') as f:
return json.load(f)
return {}
def _load_symbols(self) -> Dict[str, List[Dict]]:
"""Load symbols from cache."""
if self.symbols_file.exists():
with open(self.symbols_file, 'r') as f:
return json.load(f)
return {}
def save(self):
"""Save cache to disk."""
with open(self.index_file, 'w') as f:
json.dump(self.index, f, indent=2)
with open(self.symbols_file, 'w') as f:
json.dump(self.symbols, f, indent=2)
def is_file_cached(self, file_path: str) -> bool:
"""Check if file is cached and up to date."""
if file_path not in self.index:
return False
cached_mtime = self.index[file_path].get('mtime', 0)
current_mtime = os.path.getmtime(file_path)
return cached_mtime >= current_mtime
def update_file(self, file_path: str, symbols: List[Symbol]):
"""Update cache for a specific file."""
self.index[file_path] = {
'mtime': os.path.getmtime(file_path),
'symbol_count': len(symbols)
}
self.symbols[file_path] = [s.to_dict() for s in symbols]
def get_symbols(self, file_path: str) -> List[Symbol]:
"""Get cached symbols for a file."""
if file_path in self.symbols:
return [Symbol.from_dict(s) for s in self.symbols[file_path]]
return []
def get_all_symbols(self) -> List[Symbol]:
"""Get all cached symbols."""
all_symbols = []
for file_path, symbols in self.symbols.items():
all_symbols.extend([Symbol.from_dict(s) for s in symbols])
return all_symbols
class CppParser:
"""Parser for C++ source files."""
# Patterns for C++ symbols
CLASS_PATTERN = re.compile(r'^\s*(class|struct|union)\s+([A-Za-z_]\w*)', re.MULTILINE)
FUNCTION_PATTERN = re.compile(r'^\s*(?:(?:static|inline|virtual|const|explicit|friend|extern)\s+)*(?:[\w:]+\s+)?([A-Za-z_]\w*)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*[{;]', re.MULTILINE)
NAMESPACE_PATTERN = re.compile(r'^\s*namespace\s+([A-Za-z_]\w*)', re.MULTILINE)
ENUM_PATTERN = re.compile(r'^\s*enum\s+(?:class\s+)?([A-Za-z_]\w*)', re.MULTILINE)
TYPEDEF_PATTERN = re.compile(r'^\s*(?:typedef|using)\s+.*\s+([A-Za-z_]\w*)\s*[;=]', re.MULTILINE)
MEMBER_VAR_PATTERN = re.compile(r'^\s*(?:(?:static|const|mutable)\s+)*(?:[\w:]+\s+)+([A-Za-z_]\w*)\s*[;=]', re.MULTILINE)
def parse_file(self, file_path: str) -> List[Symbol]:
"""Parse a C++ file and extract symbols."""
symbols = []
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
lines = content.split('\n')
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return symbols
# Extract classes/structs
for match in self.CLASS_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(2),
file_path=file_path,
line_number=line_num,
symbol_type='class',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract namespaces
for match in self.NAMESPACE_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(1),
file_path=file_path,
line_number=line_num,
symbol_type='namespace',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract enums
for match in self.ENUM_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(1),
file_path=file_path,
line_number=line_num,
symbol_type='enum',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract functions (basic pattern, may need refinement)
for match in self.FUNCTION_PATTERN.finditer(content):
name = match.group(1)
# Filter out some common false positives
if name not in ['if', 'while', 'for', 'switch', 'return', 'delete', 'new']:
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=name,
file_path=file_path,
line_number=line_num,
symbol_type='function',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
return symbols
class QmlParser:
"""Parser for QML files."""
# Patterns for QML symbols - more comprehensive
QML_TYPE_PATTERN = re.compile(r'^\s*([A-Z]\w*)\s*\{', re.MULTILINE)
# Match both regular and readonly properties with better type capture
PROPERTY_PATTERN = re.compile(r'^\s*(?:readonly\s+)?property\s+(?:[\w.<>]+\s+)?([a-zA-Z_]\w*)', re.MULTILINE)
SIGNAL_PATTERN = re.compile(r'^\s*signal\s+([a-zA-Z_]\w*)', re.MULTILINE)
FUNCTION_PATTERN = re.compile(r'^\s*function\s+([a-zA-Z_]\w*)\s*\(', re.MULTILINE)
ID_PATTERN = re.compile(r'^\s*id:\s*([a-zA-Z_]\w*)', re.MULTILINE)
# Match simple property assignments - but be more selective
PROPERTY_BINDING_PATTERN = re.compile(r'^\s*([a-zA-Z_]\w*):\s*(?:["\'{#]|[0-9]|true|false)', re.MULTILINE)
def parse_file(self, file_path: str, debug: bool = False) -> List[Symbol]:
"""Parse a QML file and extract symbols."""
symbols = []
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
lines = content.split('\n')
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return symbols
if debug:
print(f"Debug: Parsing {file_path}, {len(lines)} lines", file=sys.stderr)
# Extract QML types
for match in self.QML_TYPE_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(1),
file_path=file_path,
line_number=line_num,
symbol_type='class',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract properties (including readonly)
property_matches = list(self.PROPERTY_PATTERN.finditer(content))
if debug:
print(f"Debug: Found {len(property_matches)} property declarations", file=sys.stderr)
for match in property_matches:
line_num = content[:match.start()].count('\n') + 1
name = match.group(1)
# Avoid duplicates by checking if we already have this symbol at same line
if not any(s.name == name and s.line_number == line_num for s in symbols):
symbols.append(Symbol(
name=name,
file_path=file_path,
line_number=line_num,
symbol_type='property',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
if debug and 'color' in name.lower():
print(f"Debug: Found color property: {name} at line {line_num}", file=sys.stderr)
# Extract property bindings (like colorMedTechNavy1: "#value")
for match in self.PROPERTY_BINDING_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
name = match.group(1)
# Skip common keywords that aren't properties
if name not in {'import', 'if', 'else', 'for', 'while', 'return', 'var', 'let', 'const'}:
# Check if this isn't already captured
if not any(s.name == name and abs(s.line_number - line_num) < 2 for s in symbols):
symbols.append(Symbol(
name=name,
file_path=file_path,
line_number=line_num,
symbol_type='property',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract signals
for match in self.SIGNAL_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(1),
file_path=file_path,
line_number=line_num,
symbol_type='signal',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract functions
for match in self.FUNCTION_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(1),
file_path=file_path,
line_number=line_num,
symbol_type='function',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
# Extract IDs
for match in self.ID_PATTERN.finditer(content):
line_num = content[:match.start()].count('\n') + 1
symbols.append(Symbol(
name=match.group(1),
file_path=file_path,
line_number=line_num,
symbol_type='variable',
context=lines[line_num - 1].strip() if line_num <= len(lines) else ""
))
return symbols
class SymbolFinder:
"""Main symbol finder class."""
def __init__(self, root_dir: str = ".", cache_dir: str = ".symbol_cache"):
self.root_dir = Path(root_dir).resolve()
# Always use absolute path for cache_dir
if not Path(cache_dir).is_absolute():
cache_dir = self.root_dir / cache_dir
self.cache = SymbolCache(str(cache_dir))
self.cpp_parser = CppParser()
self.qml_parser = QmlParser()
self.file_extensions = {
'.cpp', '.cc', '.cxx', '.c++', '.hpp', '.h', '.hh', '.hxx', '.h++',
'.qml', '.js'
}
def should_index_file(self, file_path: Path) -> bool:
"""Check if a file should be indexed."""
return file_path.suffix.lower() in self.file_extensions
def index_file(self, file_path: str, debug: bool = False, force: bool = False) -> List[Symbol]:
"""Index a single file."""
file_path_obj = Path(file_path)
if not file_path_obj.exists():
if debug:
print(f"Debug: File does not exist: {file_path}", file=sys.stderr)
return []
# Check cache first (unless force is True)
if not force and self.cache.is_file_cached(file_path):
if debug:
print(f"Debug: Using cached symbols for {file_path}", file=sys.stderr)
return self.cache.get_symbols(file_path)
# Parse file based on extension
symbols = []
if file_path_obj.suffix.lower() in {'.qml', '.js'}:
symbols = self.qml_parser.parse_file(file_path, debug=debug)
else:
symbols = self.cpp_parser.parse_file(file_path)
if debug:
print(f"Debug: Found {len(symbols)} symbols in {file_path}", file=sys.stderr)
# Update cache
self.cache.update_file(file_path, symbols)
return symbols
def index_directory(self, directory: str = None, force_reindex: bool = False, debug: bool = False):
"""Index all files in a directory tree."""
if directory is None:
directory = self.root_dir
directory = Path(directory).resolve()
if debug:
print(f"Debug: Indexing directory {directory}", file=sys.stderr)
indexed_count = 0
skipped_count = 0
for root, dirs, files in os.walk(directory):
# Skip hidden directories and common cache directories
# Note: 'build' removed to allow indexing build directories if needed
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in {'node_modules', '__pycache__', 'CMakeFiles', 'dist', 'target'}]
for file in files:
file_path = Path(root) / file
if self.should_index_file(file_path):
file_str = str(file_path)
if force_reindex or not self.cache.is_file_cached(file_str):
print(f"Indexing: {file_str}")
symbols = self.index_file(file_str, debug=debug, force=force_reindex)
if symbols:
indexed_count += 1
else:
if debug:
print(f"Debug: No symbols found in {file_str}", file=sys.stderr)
else:
skipped_count += 1
if debug:
print(f"Debug: Skipping cached file: {file_str}", file=sys.stderr)
self.cache.save()
print(f"Indexing complete. Indexed {indexed_count} files, skipped {skipped_count} cached files.")
print(f"Total files in cache: {len(self.cache.symbols)}")
def find_symbol(self, symbol_name: str, exact_match: bool = False) -> List[Symbol]:
"""Find a symbol by name."""
results = []
all_symbols = self.cache.get_all_symbols()
for symbol in all_symbols:
if exact_match:
if symbol.name == symbol_name:
results.append(symbol)
else:
if symbol_name.lower() in symbol.name.lower():
results.append(symbol)
# Sort by relevance (exact matches first, then by name length)
results.sort(key=lambda s: (s.name != symbol_name, len(s.name), s.name))
return results
def find_definition(self, symbol_name: str) -> Optional[Symbol]:
"""Find the most likely definition of a symbol."""
symbols = self.find_symbol(symbol_name, exact_match=True)
# Prioritize classes, then functions, then others
priority_order = ['class', 'namespace', 'function', 'enum', 'property', 'signal', 'variable']
for symbol_type in priority_order:
for symbol in symbols:
if symbol.symbol_type == symbol_type:
return symbol
return symbols[0] if symbols else None
def find_references(self, symbol_name: str) -> List[Tuple[str, int, str]]:
"""Find all references to a symbol (simple grep-based)."""
references = []
for file_path in self.cache.symbols.keys():
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
for i, line in enumerate(lines, 1):
if re.search(r'\b' + re.escape(symbol_name) + r'\b', line):
references.append((file_path, i, line.strip()))
except Exception as e:
print(f"Error searching {file_path}: {e}", file=sys.stderr)
return references
def main():
parser = argparse.ArgumentParser(description='Symbol finder for C++ and QML files')
parser.add_argument('--index', action='store_true', help='Index/reindex all files')
parser.add_argument('--force', action='store_true', help='Force reindex even if cached')
parser.add_argument('--find', metavar='SYMBOL', help='Find symbol by name')
parser.add_argument('--definition', metavar='SYMBOL', help='Find definition of symbol')
parser.add_argument('--references', metavar='SYMBOL', help='Find all references to symbol')
parser.add_argument('--exact', action='store_true', help='Exact match only')
parser.add_argument('--emacs', action='store_true', help='Output in Emacs format')
parser.add_argument('--root', metavar='DIR', default='.', help='Root directory to search')
parser.add_argument('--cache-dir', metavar='DIR', default='.symbol_cache', help='Cache directory')
parser.add_argument('--debug', action='store_true', help='Enable debug output')
parser.add_argument('--stats', action='store_true', help='Show cache statistics')
parser.add_argument('--list-files', action='store_true', help='List files that would be indexed')
args = parser.parse_args()
if args.debug:
print(f"Debug: Working directory: {os.getcwd()}", file=sys.stderr)
print(f"Debug: Root directory: {Path(args.root).resolve()}", file=sys.stderr)
print(f"Debug: Cache directory: {args.cache_dir}", file=sys.stderr)
finder = SymbolFinder(args.root, args.cache_dir)
if args.list_files:
# List all files that would be indexed
directory = Path(args.root).resolve()
print(f"Files that would be indexed from {directory}:")
count = 0
for root, dirs, files in os.walk(directory):
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in {'node_modules', '__pycache__', 'CMakeFiles', 'dist', 'target'}]
for file in files:
file_path = Path(root) / file
if finder.should_index_file(file_path):
print(f" {file_path}")
count += 1
print(f"\nTotal: {count} files")
sys.exit(0)
if args.stats:
print(f"Cache Statistics:")
print(f" Root: {finder.root_dir}")
print(f" Cache location: {finder.cache.cache_dir}")
print(f" Files indexed: {len(finder.cache.index)}")
total_symbols = sum(len(symbols) for symbols in finder.cache.symbols.values())
print(f" Total symbols: {total_symbols}")
if finder.cache.index:
print(f"\nIndexed files:")
for file_path, info in list(finder.cache.index.items())[:10]:
print(f" {file_path}: {info.get('symbol_count', 0)} symbols")
if len(finder.cache.index) > 10:
print(f" ... and {len(finder.cache.index) - 10} more files")
sys.exit(0)
if args.index:
finder.index_directory(force_reindex=args.force, debug=args.debug)
elif args.find:
symbols = finder.find_symbol(args.find, exact_match=args.exact)
if args.emacs:
# Emacs format: file:line:column:
for symbol in symbols:
print(f"{symbol.file_path}:{symbol.line_number}:1:{symbol.symbol_type} {symbol.name}")
else:
for symbol in symbols:
print(f"{symbol.name} ({symbol.symbol_type}) - {symbol.file_path}:{symbol.line_number}")
print(f" {symbol.context}")
elif args.definition:
symbol = finder.find_definition(args.definition)
if symbol:
if args.emacs:
print(f"{symbol.file_path}:{symbol.line_number}:1:")
else:
print(f"Definition: {symbol.name} ({symbol.symbol_type})")
print(f"Location: {symbol.file_path}:{symbol.line_number}")
print(f"Context: {symbol.context}")
else:
print(f"No definition found for '{args.definition}'")
sys.exit(1)
elif args.references:
refs = finder.find_references(args.references)
if args.emacs:
for file_path, line_num, context in refs:
print(f"{file_path}:{line_num}:1:{context}")
else:
for file_path, line_num, context in refs:
print(f"{file_path}:{line_num}: {context}")
else:
# Default: index if no cache exists
if not finder.cache.index:
print("No cache found. Indexing files...")
finder.index_directory()
else:
parser.print_help()
if __name__ == '__main__':
main()