Compare commits
6 Commits
3a22e1e294
...
41b8b20f76
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41b8b20f76 | ||
|
|
b02a15f7e5 | ||
|
|
ca74d93e60 | ||
|
|
3fd6384b3b | ||
|
|
c6d72d79ed | ||
|
|
ba8b24b1d9 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -106,10 +106,5 @@ Thumbs.db
|
|||||||
|
|
||||||
# Keep these files
|
# Keep these files
|
||||||
!init.el
|
!init.el
|
||||||
!emacs-dev-config.el
|
|
||||||
!init-bungee.el
|
|
||||||
!bungee.el
|
|
||||||
!qml-config.el
|
!qml-config.el
|
||||||
!symbol-finder.el
|
!symbol-finder.el
|
||||||
!symbol_finder.py
|
|
||||||
!keybinding-reference.mdelfeed/
|
|
||||||
|
|||||||
@@ -68,10 +68,9 @@ M-x disable-eslint-in-buffer ; Disable ESLint in current buffer
|
|||||||
- `init-editor.el` - Selection keybindings, shift-selection fixes
|
- `init-editor.el` - Selection keybindings, shift-selection fixes
|
||||||
|
|
||||||
**Development:**
|
**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+
|
- `init-treesitter.el` - Tree-sitter support for Emacs 29+
|
||||||
- `emacs-dev-config-modern.el` - Modern development setup with Eglot
|
- `emacs-dev-config-modern.el` - Additional dev tools (yasnippet, origami, etc.) via `M-x enable-dev-mode-modern`
|
||||||
- `emacs-dev-config.el` - Legacy LSP-mode configuration
|
|
||||||
|
|
||||||
**Fix Modules:**
|
**Fix Modules:**
|
||||||
- `init-emergency-fix.el` - Emergency editing restoration
|
- `init-emergency-fix.el` - Emergency editing restoration
|
||||||
|
|||||||
30
init.el
30
init.el
@@ -40,20 +40,11 @@
|
|||||||
|
|
||||||
;;; Load optional configurations
|
;;; 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)))
|
(let ((dev-config-modern (expand-file-name "lisp/emacs-dev-config-modern.el" user-emacs-directory)))
|
||||||
(when (file-exists-p dev-config-modern)
|
(when (file-exists-p dev-config-modern)
|
||||||
(load-file 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)))
|
|
||||||
|
|
||||||
;; SHR Configuration (for HTML rendering in mu4e, elfeed, eww)
|
;; SHR Configuration (for HTML rendering in mu4e, elfeed, eww)
|
||||||
(let ((shr-config (expand-file-name "lisp/shr-config.el" user-emacs-directory)))
|
(let ((shr-config (expand-file-name "lisp/shr-config.el" user-emacs-directory)))
|
||||||
@@ -77,19 +68,6 @@
|
|||||||
(error
|
(error
|
||||||
(message "mu4e configuration available but mu4e not installed. Install mu4e package to enable email.")))))
|
(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:
|
;; Keybinding fixes are now integrated into the respective configuration files:
|
||||||
;; - Elfeed fixes in lisp/elfeed-config.el
|
;; - Elfeed fixes in lisp/elfeed-config.el
|
||||||
;; - Portfolio tracker fixes in portfolio-tracker-v2.el
|
;; - Portfolio tracker fixes in portfolio-tracker-v2.el
|
||||||
@@ -130,8 +108,6 @@
|
|||||||
(insert " '(diff-hl-global-modes t)\n")
|
(insert " '(diff-hl-global-modes t)\n")
|
||||||
(insert " '(neo-show-hidden-files t)\n")
|
(insert " '(neo-show-hidden-files t)\n")
|
||||||
(insert " '(neo-window-width 40)\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 " '(safe-local-variable-values\n")
|
||||||
(insert " '((company-backends\n")
|
(insert " '((company-backends\n")
|
||||||
(insert " (company-qml company-capf company-files company-yasnippet))\n")
|
(insert " (company-qml company-capf company-files company-yasnippet))\n")
|
||||||
|
|||||||
587
lisp/bungee.el
587
lisp/bungee.el
@@ -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
|
|
||||||
@@ -51,15 +51,16 @@
|
|||||||
(lambda ()
|
(lambda ()
|
||||||
(message "Feed update complete!"))))
|
(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
|
(defvar elfeed-update-timer-30min nil
|
||||||
"Timer for 30-minute elfeed updates.")
|
"Timer for 30-minute elfeed updates.")
|
||||||
|
|
||||||
;; Auto-update feeds every 30 minutes in the background
|
(defvar elfeed-update-timer-hourly nil
|
||||||
;; Delayed start to avoid impacting startup performance
|
"Timer for hourly elfeed updates.")
|
||||||
(setq elfeed-update-timer-30min
|
|
||||||
(run-with-timer (* 5 60) (* 30 60) #'elfeed-update-async))
|
(defvar elfeed-timers-initialized nil
|
||||||
|
"Whether elfeed auto-update timers have been started.")
|
||||||
|
|
||||||
;; Custom function for fuzzy relative timestamps
|
;; Custom function for fuzzy relative timestamps
|
||||||
(defun my-elfeed-search-format-date (date)
|
(defun my-elfeed-search-format-date (date)
|
||||||
"Format DATE as a fuzzy relative time string."
|
"Format DATE as a fuzzy relative time string."
|
||||||
@@ -181,6 +182,9 @@
|
|||||||
|
|
||||||
;; Keybindings for elfeed
|
;; Keybindings for elfeed
|
||||||
(with-eval-after-load '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
|
;; Disable CUA mode in elfeed buffers to allow single-key commands
|
||||||
(add-hook 'elfeed-search-mode-hook
|
(add-hook 'elfeed-search-mode-hook
|
||||||
(lambda ()
|
(lambda ()
|
||||||
@@ -213,14 +217,6 @@
|
|||||||
(rmh-elfeed-org-process rmh-elfeed-org-files rmh-elfeed-org-tree-id)
|
(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))))
|
(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
|
;; Functions to control auto-updates
|
||||||
(defun elfeed-stop-auto-updates ()
|
(defun elfeed-stop-auto-updates ()
|
||||||
"Stop all automatic elfeed feed updates."
|
"Stop all automatic elfeed feed updates."
|
||||||
@@ -231,6 +227,7 @@
|
|||||||
(when (timerp elfeed-update-timer-hourly)
|
(when (timerp elfeed-update-timer-hourly)
|
||||||
(cancel-timer elfeed-update-timer-hourly)
|
(cancel-timer elfeed-update-timer-hourly)
|
||||||
(setq elfeed-update-timer-hourly nil))
|
(setq elfeed-update-timer-hourly nil))
|
||||||
|
(setq elfeed-timers-initialized nil)
|
||||||
(message "Elfeed auto-updates stopped."))
|
(message "Elfeed auto-updates stopped."))
|
||||||
|
|
||||||
(defun elfeed-start-auto-updates ()
|
(defun elfeed-start-auto-updates ()
|
||||||
@@ -240,9 +237,16 @@
|
|||||||
(setq elfeed-update-timer-30min
|
(setq elfeed-update-timer-30min
|
||||||
(run-with-timer (* 5 60) (* 30 60) #'elfeed-update-async))
|
(run-with-timer (* 5 60) (* 30 60) #'elfeed-update-async))
|
||||||
(setq elfeed-update-timer-hourly
|
(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."))
|
(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
|
;; Sorting functions
|
||||||
(defun elfeed-sort-by-date-ascending ()
|
(defun elfeed-sort-by-date-ascending ()
|
||||||
"Sort elfeed entries by date ascending (oldest first)."
|
"Sort elfeed entries by date ascending (oldest first)."
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
"Flag indicating whether modern development mode is enabled.")
|
"Flag indicating whether modern development mode is enabled.")
|
||||||
|
|
||||||
(defvar dev-mode-modern-packages
|
(defvar dev-mode-modern-packages
|
||||||
'(;; Core development tools
|
'(;; Core development tools (Eglot built-in for Emacs 29+)
|
||||||
eglot ; Only needed for Emacs < 29
|
|
||||||
corfu corfu-terminal cape ; Modern completion
|
|
||||||
consult-eglot ; Consult integration with Eglot
|
consult-eglot ; Consult integration with Eglot
|
||||||
flycheck ; Can still use alongside Flymake
|
flycheck ; Can still use alongside Flymake
|
||||||
yasnippet
|
yasnippet
|
||||||
@@ -19,26 +17,27 @@
|
|||||||
multiple-cursors expand-region
|
multiple-cursors expand-region
|
||||||
hl-todo rainbow-delimiters
|
hl-todo rainbow-delimiters
|
||||||
origami ; Code folding
|
origami ; Code folding
|
||||||
|
|
||||||
;; Version control
|
;; Version control
|
||||||
magit
|
magit
|
||||||
forge ; GitHub/GitLab integration
|
forge ; GitHub/GitLab integration
|
||||||
magit-delta ; Better diffs if delta is installed
|
magit-delta ; Better diffs if delta is installed
|
||||||
treemacs-magit
|
treemacs-magit
|
||||||
|
|
||||||
;; Languages
|
;; Languages
|
||||||
clang-format
|
clang-format
|
||||||
qml-mode
|
qml-mode
|
||||||
|
|
||||||
;; Debugging
|
;; Debugging
|
||||||
dap-mode)
|
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 ()
|
(defun dev-mode-modern-ensure-packages ()
|
||||||
"Ensure all modern development packages are installed."
|
"Ensure all modern development packages are installed."
|
||||||
(dolist (package dev-mode-modern-packages)
|
(dolist (package dev-mode-modern-packages)
|
||||||
(unless (or (package-installed-p package)
|
(unless (package-installed-p package)
|
||||||
(and (eq package 'eglot) (fboundp 'eglot))) ; Eglot is built-in for Emacs 29+
|
|
||||||
(package-refresh-contents)
|
(package-refresh-contents)
|
||||||
(package-install package))))
|
(package-install package))))
|
||||||
|
|
||||||
@@ -58,15 +57,14 @@
|
|||||||
(define-key eglot-mode-map (kbd "C-c l s") 'consult-eglot-symbols))))
|
(define-key eglot-mode-map (kbd "C-c l s") 'consult-eglot-symbols))))
|
||||||
|
|
||||||
(defun dev-mode-modern-setup-completion ()
|
(defun dev-mode-modern-setup-completion ()
|
||||||
"Setup modern completion with Corfu."
|
"Setup completion for development.
|
||||||
;; Corfu is already configured in init-completion.el
|
Company is configured globally in init-completion.el.
|
||||||
;; Add development-specific configurations here
|
This function adds development-specific tweaks."
|
||||||
(with-eval-after-load 'corfu
|
;; More aggressive completion in programming modes
|
||||||
;; More aggressive completion in programming modes
|
(add-hook 'prog-mode-hook
|
||||||
(add-hook 'prog-mode-hook
|
(lambda ()
|
||||||
(lambda ()
|
(setq-local company-idle-delay 0.1)
|
||||||
(setq-local corfu-auto-delay 0.1)
|
(setq-local company-minimum-prefix-length 1))))
|
||||||
(setq-local corfu-auto-prefix 1)))))
|
|
||||||
|
|
||||||
(defun dev-mode-modern-setup-yasnippet ()
|
(defun dev-mode-modern-setup-yasnippet ()
|
||||||
"Configure yasnippet for code snippets."
|
"Configure yasnippet for code snippets."
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -31,8 +31,7 @@
|
|||||||
marginalia ; Rich annotations in minibuffer
|
marginalia ; Rich annotations in minibuffer
|
||||||
embark ; Contextual actions
|
embark ; Contextual actions
|
||||||
embark-consult ; Embark integration with Consult
|
embark-consult ; Embark integration with Consult
|
||||||
corfu ; In-buffer completion popup
|
company ; In-buffer completion (configured in init-completion.el)
|
||||||
cape ; Completion extensions for Corfu
|
|
||||||
|
|
||||||
;; Markdown & Notes
|
;; Markdown & Notes
|
||||||
markdown-mode markdown-toc grip-mode
|
markdown-mode markdown-toc grip-mode
|
||||||
@@ -55,14 +54,14 @@
|
|||||||
|
|
||||||
;; God mode for modal editing
|
;; God mode for modal editing
|
||||||
god-mode
|
god-mode
|
||||||
|
|
||||||
;; Quality of life improvements
|
;; Quality of life improvements
|
||||||
helpful ; Better help buffers
|
helpful ; Better help buffers
|
||||||
undo-tree ; Visual undo history
|
undo-tree ; Visual undo history
|
||||||
smartparens ; Better than electric-pair-mode
|
smartparens ; Better than electric-pair-mode
|
||||||
crux ; Collection of useful functions
|
crux ; Collection of useful functions
|
||||||
ace-window ; Quick window switching
|
ace-window ; Quick window switching
|
||||||
|
|
||||||
;; Terminal emulator
|
;; Terminal emulator
|
||||||
eat ; Emacs terminal emulator
|
eat ; Emacs terminal emulator
|
||||||
))
|
))
|
||||||
@@ -96,6 +95,75 @@
|
|||||||
;;; General Settings
|
;;; General Settings
|
||||||
(setq-default indent-tabs-mode nil)
|
(setq-default indent-tabs-mode nil)
|
||||||
(setq tab-width 4)
|
(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 inhibit-startup-screen t)
|
||||||
(setq ring-bell-function 'ignore)
|
(setq ring-bell-function 'ignore)
|
||||||
|
|
||||||
@@ -149,4 +217,4 @@
|
|||||||
(message "Desktop restored"))))))
|
(message "Desktop restored"))))))
|
||||||
|
|
||||||
(provide 'init-core)
|
(provide 'init-core)
|
||||||
;;; init-core.el ends here
|
;;; init-core.el ends here
|
||||||
|
|||||||
@@ -28,8 +28,13 @@
|
|||||||
(define-key dired-mode-map (kbd "* /") 'dired-mark-directories))
|
(define-key dired-mode-map (kbd "* /") 'dired-mark-directories))
|
||||||
|
|
||||||
;; Custom sorting: directories first (dotted first), then files (dotted first)
|
;; 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 ()
|
(defun dired-sort-dotfiles-first ()
|
||||||
"Sort dired: dirs first (dots first within), then files (dots first within)."
|
"Sort dired: dirs first (dots first within), then files (dots first within)."
|
||||||
|
(interactive)
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(let (buffer-read-only)
|
(let (buffer-read-only)
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
@@ -84,8 +89,26 @@
|
|||||||
(insert line "\n")))))
|
(insert line "\n")))))
|
||||||
(set-buffer-modified-p nil)))
|
(set-buffer-modified-p nil)))
|
||||||
|
|
||||||
;; Apply custom sorting after dired reads directory
|
(defun dired-maybe-sort-dotfiles-first ()
|
||||||
(add-hook 'dired-after-readin-hook 'dired-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)
|
(provide 'init-dired)
|
||||||
;;; init-dired.el ends here
|
;;; init-dired.el ends here
|
||||||
@@ -61,10 +61,10 @@
|
|||||||
(add-to-list 'eglot-server-programs
|
(add-to-list 'eglot-server-programs
|
||||||
'(qml-mode . ("qmlls"))))
|
'(qml-mode . ("qmlls"))))
|
||||||
|
|
||||||
;; Format on save
|
;; Format on save - DISABLED to prevent reindenting
|
||||||
(defun eglot-format-buffer-on-save ()
|
;; (defun eglot-format-buffer-on-save ()
|
||||||
"Format buffer with eglot before saving."
|
;; "Format buffer with eglot before saving."
|
||||||
(add-hook 'before-save-hook #'eglot-format-buffer -10 t))
|
;; (add-hook 'before-save-hook #'eglot-format-buffer -10 t))
|
||||||
|
|
||||||
;; Keybindings
|
;; Keybindings
|
||||||
(define-key eglot-mode-map (kbd "C-c l r") 'eglot-rename)
|
(define-key eglot-mode-map (kbd "C-c l r") 'eglot-rename)
|
||||||
@@ -102,15 +102,8 @@
|
|||||||
tsx-ts-mode-hook))
|
tsx-ts-mode-hook))
|
||||||
(add-hook mode #'eglot-ensure))
|
(add-hook mode #'eglot-ensure))
|
||||||
|
|
||||||
;; Integration with Corfu for completion
|
;; Integration with Company for completion (Company configured in init-completion.el)
|
||||||
(with-eval-after-load 'corfu
|
;; Eglot automatically uses completion-at-point-functions which Company respects
|
||||||
(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))))))
|
|
||||||
|
|
||||||
;; Eldoc configuration for better documentation display
|
;; Eldoc configuration for better documentation display
|
||||||
(with-eval-after-load 'eldoc
|
(with-eval-after-load 'eldoc
|
||||||
|
|||||||
@@ -98,7 +98,10 @@
|
|||||||
|
|
||||||
;; Integration with consult if available
|
;; Integration with consult if available
|
||||||
(with-eval-after-load 'consult
|
(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)
|
;; Keybindings - Main project map on C-x p (built-in)
|
||||||
;; Additional compatibility bindings for muscle memory
|
;; Additional compatibility bindings for muscle memory
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
("C-c e" . crux-eval-and-replace)
|
("C-c e" . crux-eval-and-replace)
|
||||||
("C-c w" . crux-swap-windows)
|
("C-c w" . crux-swap-windows)
|
||||||
("C-c D" . crux-delete-file-and-buffer)
|
("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 t" . crux-visit-term-buffer)
|
||||||
("C-c k" . crux-kill-other-buffers)
|
("C-c k" . crux-kill-other-buffers)
|
||||||
("C-c TAB" . crux-indent-rigidly-and-copy-to-clipboard)
|
("C-c TAB" . crux-indent-rigidly-and-copy-to-clipboard)
|
||||||
@@ -110,8 +110,9 @@
|
|||||||
([remap kill-whole-line] . crux-kill-whole-line)
|
([remap kill-whole-line] . crux-kill-whole-line)
|
||||||
([remap kill-line] . crux-smart-kill-line))
|
([remap kill-line] . crux-smart-kill-line))
|
||||||
:config
|
:config
|
||||||
;; Use crux cleanup on save for programming modes
|
;; Use crux cleanup on save for programming modes - DISABLED to prevent reindenting
|
||||||
(add-hook 'before-save-hook 'crux-cleanup-buffer-or-region))
|
;; (add-hook 'before-save-hook 'crux-cleanup-buffer-or-region)
|
||||||
|
)
|
||||||
|
|
||||||
;;; Ace-window - Quick window switching
|
;;; Ace-window - Quick window switching
|
||||||
(use-package ace-window
|
(use-package ace-window
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
treemacs-directory-name-transformer #'identity
|
treemacs-directory-name-transformer #'identity
|
||||||
treemacs-display-in-side-window t
|
treemacs-display-in-side-window t
|
||||||
treemacs-eldoc-display 'simple
|
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-file-follow-delay 0.2
|
||||||
treemacs-follow-after-init t
|
treemacs-follow-after-init t
|
||||||
treemacs-expand-after-init t
|
treemacs-expand-after-init t
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
treemacs-indentation 2
|
treemacs-indentation 2
|
||||||
treemacs-indentation-string " "
|
treemacs-indentation-string " "
|
||||||
treemacs-is-never-other-window nil
|
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-missing-project-action 'ask
|
||||||
treemacs-move-forward-on-expand nil
|
treemacs-move-forward-on-expand nil
|
||||||
treemacs-no-delete-other-windows t
|
treemacs-no-delete-other-windows t
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
treemacs-workspace-switch-cleanup nil)
|
treemacs-workspace-switch-cleanup nil)
|
||||||
|
|
||||||
(treemacs-follow-mode t)
|
(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)
|
(treemacs-fringe-indicator-mode 'always)
|
||||||
(when treemacs-python-executable
|
(when treemacs-python-executable
|
||||||
(treemacs-git-commit-diff-mode t))
|
(treemacs-git-commit-diff-mode t))
|
||||||
|
|||||||
@@ -85,6 +85,45 @@
|
|||||||
;; Enhanced font-lock for tree-sitter modes
|
;; Enhanced font-lock for tree-sitter modes
|
||||||
(setq treesit-font-lock-level 3) ; Balanced highlighting (was 4, reduced for performance)
|
(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
|
;; Tree-sitter debugging helpers
|
||||||
(defun treesit-inspect-node-at-point ()
|
(defun treesit-inspect-node-at-point ()
|
||||||
"Show tree-sitter node information at point."
|
"Show tree-sitter node information at point."
|
||||||
|
|||||||
@@ -106,11 +106,11 @@
|
|||||||
(princ (format "Major mode: %s\n" major-mode))
|
(princ (format "Major mode: %s\n" major-mode))
|
||||||
(princ (format "Font-lock mode: %s\n" (if font-lock-mode "ENABLED" "DISABLED")))
|
(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 "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")))
|
(if font-lock-keywords "YES" "NO")))
|
||||||
(princ (format "Buffer size: %d bytes\n" (buffer-size)))
|
(princ (format "Buffer size: %d bytes\n" (buffer-size)))
|
||||||
(princ (format "File size threshold check: %s\n"
|
(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"
|
"LARGE FILE (>1MB) - highlighting may be disabled"
|
||||||
"Normal size")))
|
"Normal size")))
|
||||||
(princ "\nTo fix issues, try:\n")
|
(princ "\nTo fix issues, try:\n")
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
(when (and (boundp 'font-lock-mode) (not font-lock-mode))
|
(when (and (boundp 'font-lock-mode) (not font-lock-mode))
|
||||||
(font-lock-mode 1))
|
(font-lock-mode 1))
|
||||||
;; For tree-sitter modes, ensure proper setup
|
;; 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))
|
(eq major-mode 'c++-ts-mode))
|
||||||
(when (fboundp 'treesit-font-lock-recompute-features)
|
(when (fboundp 'treesit-font-lock-recompute-features)
|
||||||
(treesit-font-lock-recompute-features))))
|
(treesit-font-lock-recompute-features))))
|
||||||
@@ -156,9 +156,8 @@
|
|||||||
(princ " M-x ensure-cua-bindings\n")
|
(princ " M-x ensure-cua-bindings\n")
|
||||||
(princ " M-x cua-mode (toggle off and on)\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)
|
(setq show-trailing-whitespace t)
|
||||||
(add-hook 'before-save-hook 'delete-trailing-whitespace)
|
|
||||||
|
|
||||||
;; Fill column indicator
|
;; Fill column indicator
|
||||||
(setq-default display-fill-column-indicator-column 80)
|
(setq-default display-fill-column-indicator-column 80)
|
||||||
@@ -192,13 +191,12 @@
|
|||||||
:foundry "nil"
|
:foundry "nil"
|
||||||
:slant 'normal
|
:slant 'normal
|
||||||
:weight 'regular
|
:weight 'regular
|
||||||
:height 180
|
:height 120
|
||||||
:width 'normal)
|
:width 'normal)
|
||||||
|
|
||||||
;; Ensure font settings apply to new frames
|
;; Ensure font settings apply to new frames
|
||||||
;; Use the proper font spec format
|
;; Use string format for fonts starting with numbers (font-spec has parsing issues)
|
||||||
(add-to-list 'default-frame-alist
|
(add-to-list 'default-frame-alist '(font . "0xProto Nerd Font Mono-12"))
|
||||||
(cons 'font (font-spec :family "0xProto Nerd Font Mono" :size 18)))
|
|
||||||
|
|
||||||
;;; Diff-hl face customizations
|
;;; Diff-hl face customizations
|
||||||
(with-eval-after-load 'diff-hl
|
(with-eval-after-load 'diff-hl
|
||||||
|
|||||||
@@ -17,11 +17,6 @@
|
|||||||
;; First reload the main init.el
|
;; First reload the main init.el
|
||||||
(load-file (expand-file-name "init.el" user-emacs-directory))
|
(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
|
;; Reload SHR config if it exists
|
||||||
(let ((shr-config (expand-file-name "lisp/shr-config.el" user-emacs-directory)))
|
(let ((shr-config (expand-file-name "lisp/shr-config.el" user-emacs-directory)))
|
||||||
(when (file-exists-p shr-config)
|
(when (file-exists-p shr-config)
|
||||||
@@ -98,7 +93,6 @@ to prevent UI freezing. With prefix ARG, use blocking reload."
|
|||||||
(cl-remove-if-not
|
(cl-remove-if-not
|
||||||
#'file-exists-p
|
#'file-exists-p
|
||||||
(list (expand-file-name "init.el" user-emacs-directory)
|
(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/shr-config.el" user-emacs-directory)
|
||||||
(expand-file-name "lisp/elfeed-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))))
|
(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
|
(cl-remove-if-not
|
||||||
#'file-exists-p
|
#'file-exists-p
|
||||||
(list
|
(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/shr-config.el" user-emacs-directory)
|
||||||
(expand-file-name "lisp/elfeed-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)))))
|
(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"
|
(message "Error reloading %s: %s"
|
||||||
(file-name-nondirectory file) err))))
|
(file-name-nondirectory file) err))))
|
||||||
(message "Smart reload complete!"))
|
(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 ()
|
(defun reload-current-file ()
|
||||||
"Reload only the current file if it's an Emacs Lisp 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 ()
|
(defun show-dev-mode-info ()
|
||||||
"Show information about development mode."
|
"Show information about development mode."
|
||||||
(interactive)
|
(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
|
;;; God-mode helpers
|
||||||
(defun enable-god-mode-config ()
|
(defun enable-god-mode-config ()
|
||||||
@@ -284,7 +277,7 @@ This is the fastest reload method but requires byte-compilation."
|
|||||||
(load-file god-config)
|
(load-file god-config)
|
||||||
(message "God-mode configuration loaded. Press ESC to toggle god-mode."))
|
(message "God-mode configuration loaded. Press ESC to toggle god-mode."))
|
||||||
(error (message "Failed to load god-mode config: %s" err)))
|
(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)
|
(provide 'init-utils)
|
||||||
;;; init-utils.el ends here
|
;;; init-utils.el ends here
|
||||||
@@ -48,9 +48,9 @@
|
|||||||
(setq diff-hl-flydiff-delay 0.3)
|
(setq diff-hl-flydiff-delay 0.3)
|
||||||
|
|
||||||
;; Make sure diff-hl updates on various events
|
;; 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-save-hook 'diff-hl-update)
|
||||||
(add-hook 'after-revert-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)
|
(add-hook 'vc-checkin-hook 'diff-hl-update)
|
||||||
|
|
||||||
;; Enable globally with a slight delay to speed up initial startup
|
;; Enable globally with a slight delay to speed up initial startup
|
||||||
|
|||||||
@@ -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
|
|
||||||
508
symbol_finder.py
508
symbol_finder.py
@@ -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()
|
|
||||||
Reference in New Issue
Block a user