Initial Emacs configuration with modular dev mode
- Main configuration in init.el - Development tools in emacs-dev-config.el (M-x enable-dev-mode) - Fixed diff-hl to use VC backend - Added Origami code folding to dev mode - Fixed Magit-delta to check for delta executable - QML files always use qml-mode in dev mode
This commit is contained in:
115
.gitignore
vendored
Normal file
115
.gitignore
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
# Emacs temporary and backup files
|
||||
*~
|
||||
\#*\#
|
||||
.\#*
|
||||
*.elc
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
# Auto-save files
|
||||
auto-save-list/
|
||||
\#*
|
||||
.saves-*
|
||||
|
||||
# Backup directory
|
||||
backups/
|
||||
|
||||
# Session files
|
||||
.emacs.desktop
|
||||
.emacs.desktop.lock
|
||||
emacs-desktop
|
||||
session.*
|
||||
.session
|
||||
|
||||
# History and state files
|
||||
recentf
|
||||
savehist
|
||||
saveplace
|
||||
places
|
||||
.recentf
|
||||
|
||||
# Package management
|
||||
elpa/
|
||||
straight/
|
||||
quelpa/
|
||||
eln-cache/
|
||||
.cache/
|
||||
|
||||
# LSP and language server files
|
||||
.lsp-session-*
|
||||
.dap-breakpoints
|
||||
workspace/
|
||||
|
||||
# Projectile
|
||||
projectile-bookmarks.eld
|
||||
projectile.cache
|
||||
projects.eld
|
||||
|
||||
# Treemacs
|
||||
.treemacs-persist
|
||||
|
||||
# Company
|
||||
company-statistics-cache.el
|
||||
|
||||
# Transient
|
||||
transient/
|
||||
|
||||
# URL cache
|
||||
url/
|
||||
|
||||
# Network security
|
||||
network-security.data
|
||||
|
||||
# Custom configurations (machine-specific)
|
||||
custom.el
|
||||
.custom.el
|
||||
|
||||
# Tramp
|
||||
tramp
|
||||
|
||||
# Eshell
|
||||
eshell/
|
||||
|
||||
# Org mode
|
||||
org-clock-save.el
|
||||
|
||||
# Bookmarks
|
||||
bookmarks
|
||||
|
||||
# Abbrev
|
||||
abbrev_defs
|
||||
|
||||
# Games scores
|
||||
games/
|
||||
|
||||
# Calculator
|
||||
calc-trail
|
||||
|
||||
# Markdown preview
|
||||
.markdown-preview.html
|
||||
|
||||
# Symbol finder (from your config)
|
||||
# symbol_finder.py - Keep this, it's needed by symbol-finder.el
|
||||
|
||||
# Test files
|
||||
test-*.el
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Keep these files
|
||||
!init.el
|
||||
!emacs-dev-config.el
|
||||
!init-bungee.el
|
||||
!bungee.el
|
||||
!qml-config.el
|
||||
!symbol-finder.el
|
||||
!symbol_finder.py
|
||||
!keybinding-reference.md
|
||||
587
bungee.el
Normal file
587
bungee.el
Normal file
@@ -0,0 +1,587 @@
|
||||
;;; bungee.el --- Fast symbol navigation with Elisp-based caching -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Bungee provides fast symbol navigation for C++ and QML files using
|
||||
;; an Elisp-based cache system. It can work standalone or with the
|
||||
;; Python symbol_finder.py for indexing.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'json)
|
||||
(require 'cl-lib)
|
||||
(require 'xref nil t)
|
||||
(require 'pulse nil t)
|
||||
(require 'grep nil t)
|
||||
|
||||
(defgroup bungee nil
|
||||
"Fast symbol navigation with caching."
|
||||
:group 'tools)
|
||||
|
||||
(defcustom bungee-cache-directory ".symbol_cache"
|
||||
"Directory for symbol cache files."
|
||||
:type 'string
|
||||
:group 'bungee)
|
||||
|
||||
(defcustom bungee-python-indexer nil
|
||||
"Path to Python indexer script (optional). If nil, use Elisp indexer."
|
||||
:type '(choice (const :tag "Use Elisp indexer" nil)
|
||||
(file :tag "Python script path"))
|
||||
:group 'bungee)
|
||||
|
||||
(defcustom bungee-auto-update t
|
||||
"Automatically update cache when files change."
|
||||
:type 'boolean
|
||||
:group 'bungee)
|
||||
|
||||
(defcustom bungee-save-json-cache nil
|
||||
"Also save cache in JSON format for Python compatibility."
|
||||
:type 'boolean
|
||||
:group 'bungee)
|
||||
|
||||
;; Cache data structures
|
||||
(defvar bungee--symbol-cache nil
|
||||
"In-memory symbol cache. Hash table mapping file paths to symbol lists.")
|
||||
|
||||
(defvar bungee--index-cache nil
|
||||
"In-memory index cache. Hash table mapping file paths to modification times.")
|
||||
|
||||
(defvar bungee--cache-loaded nil
|
||||
"Whether the cache has been loaded from disk.")
|
||||
|
||||
(cl-defstruct bungee-symbol
|
||||
"A symbol in the codebase."
|
||||
name ; Symbol name
|
||||
file-path ; Absolute file path
|
||||
line-number ; Line number (1-based)
|
||||
symbol-type ; 'class, 'function, 'property, etc.
|
||||
context) ; Line content for context
|
||||
|
||||
;; Cache management functions
|
||||
(defun bungee--cache-dir ()
|
||||
"Get the cache directory path."
|
||||
(let ((root (or (and (fboundp 'project-current)
|
||||
(project-current)
|
||||
(fboundp 'project-root)
|
||||
(ignore-errors
|
||||
(car (project-roots (project-current)))))
|
||||
default-directory)))
|
||||
(expand-file-name bungee-cache-directory root)))
|
||||
|
||||
(defun bungee--cache-file-path (filename)
|
||||
"Get path for cache FILENAME."
|
||||
(expand-file-name filename (bungee--cache-dir)))
|
||||
|
||||
(defun bungee--load-cache ()
|
||||
"Load cache from disk into memory."
|
||||
(let ((cache-file (bungee--cache-file-path "bungee-cache.el"))
|
||||
(json-symbols-file (bungee--cache-file-path "symbols.json"))
|
||||
(json-index-file (bungee--cache-file-path "index.json")))
|
||||
|
||||
;; Initialize hash tables
|
||||
(setq bungee--symbol-cache (make-hash-table :test 'equal))
|
||||
(setq bungee--index-cache (make-hash-table :test 'equal))
|
||||
|
||||
(cond
|
||||
;; Prefer Elisp cache if it exists
|
||||
((file-exists-p cache-file)
|
||||
(load cache-file nil t)
|
||||
;; Convert loaded lists back to symbol structs
|
||||
(let ((new-cache (make-hash-table :test 'equal)))
|
||||
(maphash (lambda (file-path symbol-lists)
|
||||
(puthash file-path
|
||||
(mapcar (lambda (s)
|
||||
(make-bungee-symbol
|
||||
:name (nth 0 s)
|
||||
:file-path (nth 1 s)
|
||||
:line-number (nth 2 s)
|
||||
:symbol-type (nth 3 s)
|
||||
:context (nth 4 s)))
|
||||
symbol-lists)
|
||||
new-cache))
|
||||
bungee--symbol-cache)
|
||||
(setq bungee--symbol-cache new-cache))
|
||||
(message "Loaded Elisp cache: %d files" (hash-table-count bungee--index-cache)))
|
||||
|
||||
;; Fall back to JSON if available
|
||||
((and (file-exists-p json-symbols-file) (file-exists-p json-index-file))
|
||||
(bungee--load-json-cache json-symbols-file json-index-file)
|
||||
(message "Loaded JSON cache: %d files" (hash-table-count bungee--index-cache)))
|
||||
|
||||
(t
|
||||
(message "No cache found. Run `bungee-index-directory' to create one.")))
|
||||
|
||||
(setq bungee--cache-loaded t)))
|
||||
|
||||
(defun bungee--load-json-cache (symbols-file index-file)
|
||||
"Load JSON cache from SYMBOLS-FILE and INDEX-FILE."
|
||||
;; Load symbols
|
||||
(when (file-exists-p symbols-file)
|
||||
(let* ((json-object-type 'hash-table)
|
||||
(json-array-type 'list)
|
||||
(json-key-type 'string)
|
||||
(symbols-data (json-read-file symbols-file)))
|
||||
(maphash (lambda (file-path symbols-list)
|
||||
(let ((symbols (mapcar (lambda (s)
|
||||
(make-bungee-symbol
|
||||
:name (gethash "name" s)
|
||||
:file-path (gethash "file_path" s)
|
||||
:line-number (gethash "line_number" s)
|
||||
:symbol-type (intern (gethash "symbol_type" s))
|
||||
:context (gethash "context" s)))
|
||||
symbols-list)))
|
||||
(puthash file-path symbols bungee--symbol-cache)))
|
||||
symbols-data)))
|
||||
|
||||
;; Load index
|
||||
(when (file-exists-p index-file)
|
||||
(let* ((json-object-type 'hash-table)
|
||||
(json-key-type 'string)
|
||||
(index-data (json-read-file index-file)))
|
||||
(maphash (lambda (file-path info)
|
||||
(puthash file-path (gethash "mtime" info) bungee--index-cache))
|
||||
index-data))))
|
||||
|
||||
(defun bungee--ensure-cache ()
|
||||
"Ensure cache is loaded."
|
||||
(unless bungee--cache-loaded
|
||||
(bungee--load-cache)))
|
||||
|
||||
(defun bungee--save-cache ()
|
||||
"Save in-memory cache to disk as Elisp code."
|
||||
(let ((cache-dir (bungee--cache-dir)))
|
||||
(unless (file-exists-p cache-dir)
|
||||
(make-directory cache-dir t))
|
||||
|
||||
;; Save as Elisp file for fast loading
|
||||
(with-temp-file (bungee--cache-file-path "bungee-cache.el")
|
||||
(insert ";;; bungee-cache.el --- Bungee symbol cache -*- lexical-binding: t; -*-\n")
|
||||
(insert ";;; This file is auto-generated. Do not edit.\n\n")
|
||||
|
||||
;; Save symbol cache
|
||||
(insert "(setq bungee--symbol-cache (make-hash-table :test 'equal))\n\n")
|
||||
(maphash (lambda (file-path symbols)
|
||||
(insert (format "(puthash %S\n '(" (abbreviate-file-name file-path)))
|
||||
(dolist (symbol symbols)
|
||||
(insert (format "\n %S"
|
||||
(list (bungee-symbol-name symbol)
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
(bungee-symbol-symbol-type symbol)
|
||||
(bungee-symbol-context symbol)))))
|
||||
(insert ")\n bungee--symbol-cache)\n\n"))
|
||||
bungee--symbol-cache)
|
||||
|
||||
;; Save index cache
|
||||
(insert "(setq bungee--index-cache (make-hash-table :test 'equal))\n\n")
|
||||
(maphash (lambda (file-path mtime)
|
||||
(insert (format "(puthash %S %S bungee--index-cache)\n"
|
||||
(abbreviate-file-name file-path) mtime)))
|
||||
bungee--index-cache)
|
||||
|
||||
(insert "\n;;; bungee-cache.el ends here\n"))
|
||||
|
||||
;; Optionally save as JSON for compatibility with Python tool
|
||||
(when bungee-save-json-cache
|
||||
(bungee--save-json-cache))))
|
||||
|
||||
(defun bungee--save-json-cache ()
|
||||
"Save cache in JSON format for Python compatibility."
|
||||
(let ((cache-dir (bungee--cache-dir)))
|
||||
;; Save symbols
|
||||
(let ((symbols-file (expand-file-name "symbols.json" cache-dir))
|
||||
(symbols-data (make-hash-table :test 'equal)))
|
||||
(maphash (lambda (file-path symbols)
|
||||
(puthash file-path
|
||||
(mapcar (lambda (s)
|
||||
`((name . ,(bungee-symbol-name s))
|
||||
(file_path . ,(bungee-symbol-file-path s))
|
||||
(line_number . ,(bungee-symbol-line-number s))
|
||||
(symbol_type . ,(symbol-name (bungee-symbol-symbol-type s)))
|
||||
(context . ,(bungee-symbol-context s))))
|
||||
symbols)
|
||||
symbols-data))
|
||||
bungee--symbol-cache)
|
||||
(with-temp-file symbols-file
|
||||
(insert (json-encode symbols-data))))
|
||||
|
||||
;; Save index
|
||||
(let ((index-file (expand-file-name "index.json" cache-dir))
|
||||
(index-data (make-hash-table :test 'equal)))
|
||||
(maphash (lambda (file-path mtime)
|
||||
(puthash file-path
|
||||
`((mtime . ,mtime)
|
||||
(symbol_count . ,(length (gethash file-path bungee--symbol-cache))))
|
||||
index-data))
|
||||
bungee--index-cache)
|
||||
(with-temp-file index-file
|
||||
(insert (json-encode index-data))))))
|
||||
|
||||
(defun bungee--file-cached-p (file-path)
|
||||
"Check if FILE-PATH is cached and up to date."
|
||||
(bungee--ensure-cache)
|
||||
(let ((cached-mtime (gethash file-path bungee--index-cache))
|
||||
(current-mtime (and (file-exists-p file-path)
|
||||
(float-time (nth 5 (file-attributes file-path))))))
|
||||
(and cached-mtime current-mtime (>= cached-mtime current-mtime))))
|
||||
|
||||
;; Elisp-based parsing
|
||||
(defun bungee--parse-qml-file (file-path)
|
||||
"Parse a QML file and extract symbols."
|
||||
(let ((symbols '())
|
||||
(case-fold-search nil))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file-path)
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Find QML types
|
||||
(save-excursion
|
||||
(while (re-search-forward "^\\s-*\\([A-Z]\\w*\\)\\s-*{" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'class
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))
|
||||
|
||||
;; Find properties (including readonly)
|
||||
;; Format: [readonly] property <type> <name>
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*\\(?:readonly\\s-+\\)?property\\s-+[\\w.<>]+\\s-+\\([a-zA-Z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'property
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find signals
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*signal\\s-+\\([a-zA-Z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'signal
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find functions
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*function\\s-+\\([a-zA-Z_]\\w*\\)\\s-*(" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'function
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find ids
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*id:\\s-*\\([a-zA-Z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'variable
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))
|
||||
|
||||
(nreverse symbols)))
|
||||
|
||||
(defun bungee--parse-cpp-file (file-path)
|
||||
"Parse a C++ file and extract symbols."
|
||||
(let ((symbols '())
|
||||
(case-fold-search nil))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file-path)
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Find classes/structs
|
||||
(save-excursion
|
||||
(while (re-search-forward "^\\s-*\\(?:class\\|struct\\|union\\)\\s-+\\([A-Za-z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'class
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))
|
||||
|
||||
;; Find namespaces
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*namespace\\s-+\\([A-Za-z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'namespace
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Find enums
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*enum\\s-+\\(?:class\\s-+\\)?\\([A-Za-z_]\\w*\\)" nil t)
|
||||
(push (make-bungee-symbol
|
||||
:name (match-string 1)
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'enum
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols))
|
||||
|
||||
;; Basic function detection (simplified)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^\\s-*\\(?:\\w+\\s-+\\)*\\([A-Za-z_]\\w*\\)\\s-*([^)]*)[^;{]*{" nil t)
|
||||
(let ((name (match-string 1)))
|
||||
(unless (member name '("if" "while" "for" "switch" "catch"))
|
||||
(push (make-bungee-symbol
|
||||
:name name
|
||||
:file-path file-path
|
||||
:line-number (line-number-at-pos)
|
||||
:symbol-type 'function
|
||||
:context (buffer-substring-no-properties
|
||||
(line-beginning-position)
|
||||
(line-end-position)))
|
||||
symbols)))))
|
||||
|
||||
(nreverse symbols)))
|
||||
|
||||
(defun bungee--index-file (file-path &optional force)
|
||||
"Index FILE-PATH. If FORCE is non-nil, reindex even if cached."
|
||||
(bungee--ensure-cache)
|
||||
(when (or force (not (bungee--file-cached-p file-path)))
|
||||
(let ((symbols (cond
|
||||
((string-match-p "\\.qml\\'" file-path)
|
||||
(bungee--parse-qml-file file-path))
|
||||
((string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'" file-path)
|
||||
(bungee--parse-cpp-file file-path))
|
||||
(t nil))))
|
||||
(when symbols
|
||||
(puthash file-path symbols bungee--symbol-cache)
|
||||
(puthash file-path (float-time (nth 5 (file-attributes file-path))) bungee--index-cache)
|
||||
(message "Indexed %s: %d symbols" file-path (length symbols))))))
|
||||
|
||||
;; Symbol lookup functions
|
||||
(defun bungee-find-symbol (symbol-name &optional exact-match)
|
||||
"Find all symbols matching SYMBOL-NAME. If EXACT-MATCH is non-nil, use exact matching."
|
||||
(bungee--ensure-cache)
|
||||
(let ((results '()))
|
||||
(maphash (lambda (_ symbols)
|
||||
(dolist (symbol symbols)
|
||||
(when (if exact-match
|
||||
(string= (bungee-symbol-name symbol) symbol-name)
|
||||
(string-match-p (regexp-quote symbol-name)
|
||||
(bungee-symbol-name symbol)))
|
||||
(push symbol results))))
|
||||
bungee--symbol-cache)
|
||||
(sort results (lambda (a b)
|
||||
(or (string< (bungee-symbol-name a) (bungee-symbol-name b))
|
||||
(< (bungee-symbol-line-number a) (bungee-symbol-line-number b)))))))
|
||||
|
||||
(defun bungee-find-definition (symbol-name)
|
||||
"Find the most likely definition of SYMBOL-NAME."
|
||||
(let ((symbols (bungee-find-symbol symbol-name t)))
|
||||
;; Prioritize by symbol type
|
||||
(or (cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'class)) symbols)
|
||||
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'namespace)) symbols)
|
||||
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'function)) symbols)
|
||||
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'property)) symbols)
|
||||
(car symbols))))
|
||||
|
||||
;; Interactive commands
|
||||
(defun bungee-jump-to-definition ()
|
||||
"Jump to definition of symbol at point."
|
||||
(interactive)
|
||||
(let* ((symbol-name (or (thing-at-point 'symbol t)
|
||||
(read-string "Symbol: ")))
|
||||
(symbol (bungee-find-definition symbol-name)))
|
||||
(if symbol
|
||||
(progn
|
||||
(push-mark)
|
||||
(find-file (bungee-symbol-file-path symbol))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (bungee-symbol-line-number symbol)))
|
||||
(when (fboundp 'pulse-momentary-highlight-one-line)
|
||||
(pulse-momentary-highlight-one-line (point)))
|
||||
(message "Found: %s" (bungee-symbol-context symbol)))
|
||||
(message "No definition found for '%s'" symbol-name))))
|
||||
|
||||
(defun bungee-find-references ()
|
||||
"Find all references to symbol at point."
|
||||
(interactive)
|
||||
(let* ((symbol-name (or (thing-at-point 'symbol t)
|
||||
(read-string "Find references to: ")))
|
||||
(symbols (bungee-find-symbol symbol-name)))
|
||||
(if symbols
|
||||
(let ((buffer (get-buffer-create "*Bungee References*")))
|
||||
(with-current-buffer buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (format "References to '%s':\n\n" symbol-name))
|
||||
(dolist (symbol symbols)
|
||||
(insert (format "%s:%d: %s [%s]\n"
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
(bungee-symbol-context symbol)
|
||||
(bungee-symbol-symbol-type symbol))))
|
||||
(goto-char (point-min))
|
||||
(grep-mode)))
|
||||
(display-buffer buffer))
|
||||
(message "No references found for '%s'" symbol-name))))
|
||||
|
||||
(defun bungee-index-directory (&optional directory force)
|
||||
"Index all files in DIRECTORY. If FORCE is non-nil, reindex all files."
|
||||
(interactive "DDirectory to index: \nP")
|
||||
(let* ((dir (or directory default-directory))
|
||||
(files (directory-files-recursively
|
||||
dir
|
||||
"\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
|
||||
nil
|
||||
(lambda (d) (not (or (string-match-p "/\\." d)
|
||||
(string-match-p "/node_modules" d)
|
||||
(string-match-p "/CMakeFiles" d))))))
|
||||
(count 0))
|
||||
(dolist (file files)
|
||||
(when (or force (not (bungee--file-cached-p file)))
|
||||
(bungee--index-file file force)
|
||||
(setq count (1+ count))))
|
||||
(bungee--save-cache)
|
||||
(message "Indexed %d files" count)))
|
||||
|
||||
(defun bungee-index-current-file ()
|
||||
"Index or reindex the current file."
|
||||
(interactive)
|
||||
(when buffer-file-name
|
||||
(bungee--index-file buffer-file-name t)
|
||||
(bungee--save-cache)))
|
||||
|
||||
(defun bungee-cache-status ()
|
||||
"Show cache status."
|
||||
(interactive)
|
||||
(bungee--ensure-cache)
|
||||
(let ((file-count (hash-table-count bungee--index-cache))
|
||||
(symbol-count 0))
|
||||
(maphash (lambda (_ symbols)
|
||||
(setq symbol-count (+ symbol-count (length symbols))))
|
||||
bungee--symbol-cache)
|
||||
(message "Bungee cache: %d files, %d symbols" file-count symbol-count)))
|
||||
|
||||
(defun bungee-clear-cache ()
|
||||
"Clear the in-memory cache."
|
||||
(interactive)
|
||||
(setq bungee--symbol-cache nil
|
||||
bungee--index-cache nil
|
||||
bungee--cache-loaded nil)
|
||||
(message "Bungee cache cleared"))
|
||||
|
||||
;; Python indexer integration (optional)
|
||||
(defun bungee-index-with-python (&optional force)
|
||||
"Index using Python script if configured."
|
||||
(interactive "P")
|
||||
(if bungee-python-indexer
|
||||
(let* ((root (or (when (fboundp 'project-root)
|
||||
(car (project-roots (project-current))))
|
||||
default-directory))
|
||||
(cmd (format "python3 %s --index %s --root %s --cache-dir %s"
|
||||
(shell-quote-argument bungee-python-indexer)
|
||||
(if force "--force" "")
|
||||
(shell-quote-argument root)
|
||||
(shell-quote-argument (bungee--cache-dir)))))
|
||||
(message "Running: %s" cmd)
|
||||
(shell-command cmd)
|
||||
(bungee-clear-cache)
|
||||
(bungee--load-cache))
|
||||
(message "Python indexer not configured. Use `bungee-index-directory' instead.")))
|
||||
|
||||
;; Auto-update on save
|
||||
(defun bungee--after-save-hook ()
|
||||
"Update index after saving a file."
|
||||
(when (and bungee-auto-update
|
||||
buffer-file-name
|
||||
(string-match-p "\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
|
||||
buffer-file-name))
|
||||
(bungee--index-file buffer-file-name t)
|
||||
(bungee--save-cache)))
|
||||
|
||||
;; Minor mode
|
||||
(defvar bungee-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(define-key map (kbd "M-?") 'bungee-find-references)
|
||||
(define-key map (kbd "C-c b i") 'bungee-index-directory)
|
||||
(define-key map (kbd "C-c b f") 'bungee-index-current-file)
|
||||
(define-key map (kbd "C-c b s") 'bungee-cache-status)
|
||||
(define-key map (kbd "C-c b c") 'bungee-clear-cache)
|
||||
(define-key map (kbd "C-c b p") 'bungee-index-with-python)
|
||||
map)
|
||||
"Keymap for bungee-mode.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode bungee-mode
|
||||
"Minor mode for fast symbol navigation with caching."
|
||||
:lighter " Bungee"
|
||||
:keymap bungee-mode-map
|
||||
(if bungee-mode
|
||||
(add-hook 'after-save-hook 'bungee--after-save-hook nil t)
|
||||
(remove-hook 'after-save-hook 'bungee--after-save-hook t)))
|
||||
|
||||
;;;###autoload
|
||||
(define-globalized-minor-mode global-bungee-mode
|
||||
bungee-mode
|
||||
(lambda ()
|
||||
(when (and (not (minibufferp))
|
||||
buffer-file-name
|
||||
(string-match-p "\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
|
||||
buffer-file-name))
|
||||
(bungee-mode 1))))
|
||||
|
||||
;; xref integration
|
||||
(when (fboundp 'xref-make)
|
||||
(defun bungee-xref-backend () 'bungee)
|
||||
|
||||
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql bungee)))
|
||||
(thing-at-point 'symbol t))
|
||||
|
||||
(cl-defmethod xref-backend-definitions ((_backend (eql bungee)) identifier)
|
||||
(let ((symbol (bungee-find-definition identifier)))
|
||||
(when symbol
|
||||
(list (xref-make (bungee-symbol-context symbol)
|
||||
(xref-make-file-location
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
0))))))
|
||||
|
||||
(cl-defmethod xref-backend-references ((_backend (eql bungee)) identifier)
|
||||
(mapcar (lambda (symbol)
|
||||
(xref-make (bungee-symbol-context symbol)
|
||||
(xref-make-file-location
|
||||
(bungee-symbol-file-path symbol)
|
||||
(bungee-symbol-line-number symbol)
|
||||
0)))
|
||||
(bungee-find-symbol identifier)))
|
||||
|
||||
(add-hook 'bungee-mode-hook
|
||||
(lambda ()
|
||||
(add-hook 'xref-backend-functions 'bungee-xref-backend nil t))))
|
||||
|
||||
(provide 'bungee)
|
||||
;;; bungee.el ends here
|
||||
595
emacs-dev-config.el
Normal file
595
emacs-dev-config.el
Normal file
@@ -0,0 +1,595 @@
|
||||
;;; -*- lexical-binding: t -*-
|
||||
;;; emacs-dev-config.el --- Development configuration for Emacs
|
||||
;;; Commentary:
|
||||
;;; Development-related configurations including LSP, languages, debugging, etc.
|
||||
;;; Use M-x enable-dev-mode to activate this configuration
|
||||
;;; Use M-x disable-dev-mode to deactivate this configuration
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defvar dev-mode-enabled nil
|
||||
"Flag indicating whether development mode is enabled.")
|
||||
|
||||
(defvar dev-mode-packages
|
||||
`(;; Development tools
|
||||
lsp-mode lsp-ui lsp-treemacs
|
||||
company company-box
|
||||
flycheck yasnippet
|
||||
projectile
|
||||
ggtags multiple-cursors expand-region
|
||||
hl-todo rainbow-delimiters
|
||||
origami ;; Code folding
|
||||
|
||||
;; Version control
|
||||
magit
|
||||
,@(when (executable-find "delta") '(magit-delta)) ;; Only if delta is installed
|
||||
treemacs-magit
|
||||
|
||||
;; Helm integration for development
|
||||
helm-lsp helm-xref helm-projectile
|
||||
|
||||
;; Languages
|
||||
clang-format qml-mode company-qml
|
||||
|
||||
;; Debugging
|
||||
dap-mode)
|
||||
"List of packages required for development mode.")
|
||||
|
||||
(defun dev-mode-ensure-packages ()
|
||||
"Ensure all development packages are installed."
|
||||
(dolist (package dev-mode-packages)
|
||||
(unless (package-installed-p package)
|
||||
(package-refresh-contents)
|
||||
(package-install package))))
|
||||
|
||||
(defun dev-mode-setup-lsp ()
|
||||
"Configure LSP mode for development."
|
||||
(use-package lsp-mode
|
||||
:ensure t
|
||||
:hook ((c-mode c++-mode python-mode qml-mode) . lsp-deferred)
|
||||
:commands (lsp lsp-deferred)
|
||||
:config
|
||||
(setq lsp-keymap-prefix "C-c l")
|
||||
(setq lsp-idle-delay 0.5)
|
||||
(setq lsp-log-io nil)
|
||||
(setq lsp-completion-enable t)
|
||||
(setq lsp-headerline-breadcrumb-enable t)
|
||||
(setq lsp-enable-snippet t)
|
||||
(setq lsp-enable-semantic-highlighting t))
|
||||
|
||||
(use-package lsp-ui
|
||||
:ensure t
|
||||
:commands lsp-ui-mode
|
||||
:config
|
||||
(setq lsp-ui-doc-enable t)
|
||||
(setq lsp-ui-doc-position 'bottom)
|
||||
(setq lsp-ui-doc-delay 1)
|
||||
(setq lsp-ui-sideline-enable t)
|
||||
(setq lsp-ui-sideline-show-hover t)
|
||||
(setq lsp-ui-sideline-show-diagnostics t)
|
||||
(setq lsp-ui-sideline-show-code-actions t))
|
||||
|
||||
(use-package helm-lsp
|
||||
:ensure t
|
||||
:commands helm-lsp-workspace-symbol)
|
||||
|
||||
(use-package lsp-treemacs
|
||||
:ensure t
|
||||
:commands lsp-treemacs-errors-list))
|
||||
|
||||
(defun dev-mode-setup-company ()
|
||||
"Configure company mode for auto-completion."
|
||||
(use-package company
|
||||
:ensure t
|
||||
:hook (after-init . global-company-mode)
|
||||
:bind (:map company-active-map
|
||||
("C-n" . company-select-next)
|
||||
("C-p" . company-select-previous)
|
||||
("TAB" . company-complete-selection))
|
||||
:config
|
||||
(setq company-idle-delay 0.2)
|
||||
(setq company-minimum-prefix-length 1)
|
||||
(setq company-selection-wrap-around t)
|
||||
(setq company-show-numbers t)
|
||||
(setq company-tooltip-align-annotations t))
|
||||
|
||||
(use-package company-box
|
||||
:ensure t
|
||||
:hook (company-mode . company-box-mode)))
|
||||
|
||||
(defun dev-mode-setup-flycheck ()
|
||||
"Configure flycheck for syntax checking."
|
||||
(use-package flycheck
|
||||
:ensure t
|
||||
:init (global-flycheck-mode)
|
||||
:config
|
||||
(setq flycheck-display-errors-delay 0.3)
|
||||
(setq flycheck-gcc-language-standard "c++17")
|
||||
(setq flycheck-clang-language-standard "c++17")))
|
||||
|
||||
(defun dev-mode-setup-yasnippet ()
|
||||
"Configure yasnippet for code snippets."
|
||||
(use-package yasnippet
|
||||
:ensure t
|
||||
:config
|
||||
(yas-global-mode 1)))
|
||||
|
||||
(defun dev-mode-setup-projectile ()
|
||||
"Configure projectile for project management."
|
||||
(use-package projectile
|
||||
:ensure t
|
||||
:init
|
||||
(projectile-mode +1)
|
||||
:bind-keymap ("C-c p" . projectile-command-map)
|
||||
:config
|
||||
(setq projectile-completion-system 'helm)
|
||||
(setq projectile-switch-project-action #'projectile-dired)
|
||||
(setq projectile-enable-caching t))
|
||||
|
||||
(use-package helm-projectile
|
||||
:ensure t
|
||||
:after (helm projectile)
|
||||
:config
|
||||
(helm-projectile-on)))
|
||||
|
||||
(defun dev-mode-setup-ggtags ()
|
||||
"Configure ggtags for code navigation."
|
||||
(use-package ggtags
|
||||
:ensure t
|
||||
:hook ((c-mode c++-mode python-mode) . ggtags-mode)
|
||||
:bind (:map ggtags-mode-map
|
||||
("C-c g s" . ggtags-find-other-symbol)
|
||||
("C-c g h" . ggtags-view-tag-history)
|
||||
("C-c g r" . ggtags-find-reference)
|
||||
("C-c g f" . ggtags-find-file)
|
||||
("C-c g c" . ggtags-create-tags))
|
||||
:config
|
||||
(setq ggtags-completing-read-function nil)
|
||||
(setq ggtags-navigation-mode-lighter nil)
|
||||
(setq ggtags-mode-line-project-name nil)))
|
||||
|
||||
(defun dev-mode-setup-origami ()
|
||||
"Configure Origami for code folding."
|
||||
(use-package origami
|
||||
:ensure t
|
||||
:config
|
||||
;; Define global keybindings for origami
|
||||
(global-set-key (kbd "C-c f f") 'origami-toggle-node)
|
||||
(global-set-key (kbd "C-c f o") 'origami-open-node)
|
||||
(global-set-key (kbd "C-c f c") 'origami-close-node)
|
||||
(global-set-key (kbd "C-c f a") 'origami-close-all-nodes)
|
||||
(global-set-key (kbd "C-c f A") 'origami-open-all-nodes)
|
||||
(global-set-key (kbd "C-c f t") 'origami-toggle-all-nodes)
|
||||
(global-set-key (kbd "C-c f r") 'origami-recursively-toggle-node)
|
||||
(global-set-key (kbd "C-c f R") 'origami-open-node-recursively)
|
||||
(global-set-key (kbd "C-c f n") 'origami-next-fold)
|
||||
(global-set-key (kbd "C-c f p") 'origami-previous-fold)
|
||||
(global-set-key (kbd "C-c f s") 'origami-show-only-node)
|
||||
(global-set-key (kbd "C-c f u") 'origami-undo)
|
||||
(global-set-key (kbd "C-c f 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"))))
|
||||
|
||||
(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))
|
||||
|
||||
;; 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
|
||||
:config
|
||||
(require 'dap-python)
|
||||
(require 'dap-gdb-lldb)
|
||||
(dap-auto-configure-mode 1)
|
||||
(setq dap-auto-configure-features '(sessions locals controls tooltip))))
|
||||
|
||||
(defun dev-mode-custom-functions ()
|
||||
"Define custom development functions."
|
||||
(defun generate-cpp-tags ()
|
||||
"Generate TAGS file for C++ project."
|
||||
(interactive)
|
||||
(let ((project-root (or (projectile-project-root) default-directory)))
|
||||
(shell-command
|
||||
(format "cd %s && find . -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.hh' -o -name '*.c' -o -name '*.h' | etags -"
|
||||
project-root))
|
||||
(visit-tags-table (concat project-root "TAGS"))
|
||||
(message "C++ TAGS file generated for project: %s" project-root)))
|
||||
|
||||
(defun generate-python-tags ()
|
||||
"Generate TAGS file for Python project."
|
||||
(interactive)
|
||||
(let ((project-root (or (projectile-project-root) default-directory)))
|
||||
(shell-command
|
||||
(format "cd %s && find . -name '*.py' | etags -"
|
||||
project-root))
|
||||
(visit-tags-table (concat project-root "TAGS"))
|
||||
(message "Python TAGS file generated for project: %s" project-root)))
|
||||
|
||||
(defun generate-all-tags ()
|
||||
"Generate TAGS file for both C++ and Python files."
|
||||
(interactive)
|
||||
(let ((project-root (or (projectile-project-root) default-directory)))
|
||||
(shell-command
|
||||
(format "cd %s && find . -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.hh' -o -name '*.c' -o -name '*.h' -o -name '*.py' | etags -"
|
||||
project-root))
|
||||
(visit-tags-table (concat project-root "TAGS"))
|
||||
(message "TAGS file generated for all supported files in: %s" project-root)))
|
||||
|
||||
(defun quick-compile-and-run ()
|
||||
"Quick compile and run current file."
|
||||
(interactive)
|
||||
(save-buffer)
|
||||
(cond
|
||||
((derived-mode-p 'c++-mode)
|
||||
(shell-command
|
||||
(format "g++ -std=c++17 -o %s %s && ./%s"
|
||||
(file-name-sans-extension (buffer-name))
|
||||
(buffer-name)
|
||||
(file-name-sans-extension (buffer-name)))))
|
||||
((derived-mode-p 'python-mode)
|
||||
(shell-command (format "python3 %s" (buffer-name))))
|
||||
(t (message "Unsupported file type for quick compile and run")))))
|
||||
|
||||
(defun dev-mode-setup-keybindings ()
|
||||
"Set up development-specific keybindings."
|
||||
(global-set-key (kbd "C-c t c") 'generate-cpp-tags)
|
||||
(global-set-key (kbd "C-c t p") 'generate-python-tags)
|
||||
(global-set-key (kbd "C-c t a") 'generate-all-tags)
|
||||
(global-set-key (kbd "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 "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 t c : Generate C++ TAGS file\n")
|
||||
(princ " C-c t p : Generate Python TAGS file\n")
|
||||
(princ " 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 q : Quick compile and run\n")
|
||||
(princ " C-c C-c : Recompile (C++) or Send buffer to Python\n\n")
|
||||
(princ "PROJECT MANAGEMENT:\n")
|
||||
(princ " C-c p : Projectile commands prefix\n\n")
|
||||
(princ "VERSION CONTROL (MAGIT):\n")
|
||||
(princ " C-x g : Magit status\n")
|
||||
(princ " C-x M-g : Magit dispatch\n")
|
||||
(princ " C-c g : Magit file dispatch\n")
|
||||
(princ " C-c C-p : Save commit as patch (in Magit buffers)\n\n")
|
||||
(princ "EDITING:\n")
|
||||
(princ " C-= : Expand region\n")
|
||||
(princ " C-> : Mark next like this (multiple cursors)\n")
|
||||
(princ " C-< : Mark previous like this\n")
|
||||
(princ " C-S-d : Mark all like this\n\n")
|
||||
(princ "CODE FOLDING (ORIGAMI):\n")
|
||||
(princ " C-c f f : Toggle fold at point\n")
|
||||
(princ " C-c f o : Open fold\n")
|
||||
(princ " C-c f c : Close fold\n")
|
||||
(princ " C-c f a : Close all folds\n")
|
||||
(princ " C-c f A : Open all folds\n")
|
||||
(princ " C-c f t : Toggle all folds\n")
|
||||
(princ " C-c f r : Recursively toggle fold\n")
|
||||
(princ " C-c f n/p : Next/Previous fold\n")
|
||||
(princ " C-c f s : Show only current fold\n\n")
|
||||
(princ "LANGUAGE MODES:\n")
|
||||
(princ " .qml files : Always use qml-mode (not JavaScript mode)\n")
|
||||
(princ " .py files : Python mode with LSP support\n")
|
||||
(princ " .cpp/.hpp : C++ mode with LSP support\n\n")
|
||||
(princ "DEVELOPMENT MODE:\n")
|
||||
(princ " M-x enable-dev-mode : Enable development mode\n")
|
||||
(princ " M-x disable-dev-mode : Disable development mode\n")
|
||||
(princ " C-c h : Show this help\n")))
|
||||
|
||||
;;;###autoload
|
||||
(defun enable-dev-mode ()
|
||||
"Enable development mode with all programming tools."
|
||||
(interactive)
|
||||
(if dev-mode-enabled
|
||||
(message "Development mode is already enabled")
|
||||
(message "Enabling development mode...")
|
||||
;; Ensure packages are installed
|
||||
(dev-mode-ensure-packages)
|
||||
;; Set up all development features
|
||||
(dev-mode-setup-lsp)
|
||||
(dev-mode-setup-company)
|
||||
(dev-mode-setup-flycheck)
|
||||
(dev-mode-setup-yasnippet)
|
||||
(dev-mode-setup-projectile)
|
||||
(dev-mode-setup-ggtags)
|
||||
(dev-mode-setup-origami)
|
||||
(dev-mode-setup-editing-tools)
|
||||
(dev-mode-setup-languages)
|
||||
(dev-mode-setup-magit)
|
||||
(dev-mode-setup-debugging)
|
||||
(dev-mode-custom-functions)
|
||||
(dev-mode-setup-keybindings)
|
||||
;; Ensure QML files use qml-mode (override any JS mode associations)
|
||||
(add-to-list 'auto-mode-alist '("\\.qml\\'" . qml-mode))
|
||||
;; Set up help command
|
||||
(global-set-key (kbd "C-c h") 'show-dev-mode-help)
|
||||
(setq dev-mode-enabled t)
|
||||
(message "Development mode enabled! Press C-c h for help. QML files will use qml-mode.")))
|
||||
|
||||
;;;###autoload
|
||||
(defun disable-dev-mode ()
|
||||
"Disable development mode."
|
||||
(interactive)
|
||||
(if (not dev-mode-enabled)
|
||||
(message "Development mode is not enabled")
|
||||
(message "Disabling development mode...")
|
||||
;; Disable major modes that can be toggled
|
||||
(global-company-mode -1)
|
||||
(global-flycheck-mode -1)
|
||||
(yas-global-mode -1)
|
||||
(projectile-mode -1)
|
||||
(global-hl-todo-mode -1)
|
||||
;; Note: Some modes might require restarting Emacs to fully disable
|
||||
(setq dev-mode-enabled nil)
|
||||
(message "Development mode disabled. 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
|
||||
63
init-bungee.el
Normal file
63
init-bungee.el
Normal file
@@ -0,0 +1,63 @@
|
||||
;;; init-bungee.el --- Initialize Bungee symbol finder -*- lexical-binding: t; -*-
|
||||
|
||||
;; Load and configure the Bungee package
|
||||
(load-file "~/.emacs.d/bungee.el")
|
||||
(require 'bungee)
|
||||
|
||||
;; Configure cache directory
|
||||
(setq bungee-cache-directory ".symbol_cache") ; Use standard cache dir
|
||||
|
||||
;; Optional: Use Python indexer for better parsing
|
||||
;; Only set if the file exists
|
||||
(let ((python-indexer-path "~/sources/bungee/symbol_finder.py"))
|
||||
(when (file-exists-p (expand-file-name python-indexer-path))
|
||||
(setq bungee-python-indexer python-indexer-path)))
|
||||
|
||||
;; Optional: Save JSON cache for Python tool compatibility
|
||||
(setq bungee-save-json-cache nil) ; Set to t if you need Python compatibility
|
||||
|
||||
;; Enable auto-update when saving files
|
||||
(setq bungee-auto-update t)
|
||||
|
||||
;; Enable Bungee mode globally for supported files
|
||||
(global-bungee-mode 1)
|
||||
|
||||
;; Override M-. in QML and C++ files to use Bungee
|
||||
(add-hook 'qml-mode-hook
|
||||
(lambda ()
|
||||
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(local-set-key (kbd "M-?") 'bungee-find-references)
|
||||
(local-set-key (kbd "M-,") 'pop-tag-mark)))
|
||||
|
||||
(add-hook 'c++-mode-hook
|
||||
(lambda ()
|
||||
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(local-set-key (kbd "M-?") 'bungee-find-references)
|
||||
(local-set-key (kbd "M-,") 'pop-tag-mark)))
|
||||
|
||||
;; For .qml files if qml-mode is not available
|
||||
(add-to-list 'auto-mode-alist '("\\.qml\\'" . js-mode))
|
||||
(add-hook 'js-mode-hook
|
||||
(lambda ()
|
||||
(when (and buffer-file-name
|
||||
(string-match-p "\\.qml\\'" buffer-file-name))
|
||||
(bungee-mode 1)
|
||||
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
|
||||
(local-set-key (kbd "M-?") 'bungee-find-references)
|
||||
(local-set-key (kbd "M-,") 'pop-tag-mark))))
|
||||
|
||||
;; Convenient commands
|
||||
(defun bungee-reindex-project ()
|
||||
"Reindex the entire project using Python indexer."
|
||||
(interactive)
|
||||
(if bungee-python-indexer
|
||||
(bungee-index-with-python t)
|
||||
(bungee-index-directory nil t)))
|
||||
|
||||
(defun bungee-reindex-current-project ()
|
||||
"Force reindex current project."
|
||||
(interactive)
|
||||
(bungee-index-directory nil t))
|
||||
|
||||
(provide 'init-bungee)
|
||||
;;; init-bungee.el ends here
|
||||
660
init.el
Normal file
660
init.el
Normal file
@@ -0,0 +1,660 @@
|
||||
;;; -*- lexical-binding: t -*-
|
||||
;;; Commentary:
|
||||
|
||||
;;; Improved Emacs Configuration
|
||||
;;; Keeping: treemacs, helm, diff-hl, magit
|
||||
|
||||
;;; Package Management - Consolidated
|
||||
(require 'package)
|
||||
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
|
||||
(package-initialize)
|
||||
|
||||
;; Core package list - consolidated and cleaned
|
||||
(setq package-selected-packages
|
||||
'(;; Core utilities
|
||||
use-package which-key
|
||||
|
||||
;; Themes
|
||||
modus-themes
|
||||
|
||||
;; Version control
|
||||
diff-hl
|
||||
|
||||
;; File management
|
||||
treemacs treemacs-projectile treemacs-all-the-icons
|
||||
neotree all-the-icons all-the-icons-dired diredfl
|
||||
|
||||
;; Helm ecosystem
|
||||
helm helm-xref helm-projectile
|
||||
|
||||
;; Core project management
|
||||
projectile
|
||||
|
||||
;; Markdown & Notes
|
||||
markdown-mode markdown-toc grip-mode
|
||||
obsidian olivetti
|
||||
|
||||
;; Search and navigation
|
||||
deadgrep ripgrep wgrep anzu
|
||||
ibuffer-sidebar ibuffer-projectile
|
||||
|
||||
;; Required for some functionality
|
||||
org dash s f ht spinner lv hydra avy))
|
||||
|
||||
;; Auto-install missing packages
|
||||
(when (cl-find-if-not #'package-installed-p package-selected-packages)
|
||||
(package-refresh-contents)
|
||||
(mapc #'package-install package-selected-packages))
|
||||
|
||||
;; Ensure use-package is loaded
|
||||
(eval-when-compile
|
||||
(require 'use-package))
|
||||
(setq use-package-always-ensure t)
|
||||
|
||||
;;; Custom Settings (preserved from original)
|
||||
(custom-set-variables
|
||||
;; custom-set-variables was added by Custom.
|
||||
;; If you edit it by hand, you could mess it up, so be careful.
|
||||
;; Your init file should contain only one such instance.
|
||||
;; If there is more than one, they won't work right.
|
||||
'(before-save-hook '(delete-trailing-whitespace))
|
||||
'(column-number-mode t)
|
||||
'(cua-mode t)
|
||||
'(custom-enabled-themes '(modus-vivendi))
|
||||
'(custom-safe-themes
|
||||
'("9fb69436c074b82a62b78b8d733e6274d0bd16d156f7b094e2afe4345c040c49"
|
||||
"004f174754c688f358fa2afc4f8699b5db647fbfaa0d6b55ff39f63e05bfbbf5"
|
||||
"ca1b398ceb1b61709197478dc7f705b8337a0a9631e399948e643520c5557382"
|
||||
"75eef60308d7328ed14fa27002e85de255c2342e73275173a14ed3aa1643d545"
|
||||
"77f281064ea1c8b14938866e21c4e51e4168e05db98863bd7430f1352cab294a"
|
||||
"242e6f00c98aa43539b41c505ef094d21cbc981203809a82949efaa2bc6cb194"
|
||||
"c9e63647d90e7dc59d52c176fbfd46fd2cfed275cd2bad9b29b6cf620d3389b6"
|
||||
"ad7d874d137291e09fe2963babc33d381d087fa14928cb9d34350b67b6556b6d"
|
||||
default))
|
||||
'(diff-hl-global-modes t)
|
||||
'(display-line-numbers t)
|
||||
'(display-line-numbers-type 'relative)
|
||||
'(global-display-line-numbers-mode t)
|
||||
'(ls-lisp-dirs-first t)
|
||||
'(neo-show-hidden-files t)
|
||||
'(neo-window-width 40)
|
||||
'(package-selected-packages
|
||||
'(all-the-icons all-the-icons-dired anzu avy clang-format+ commenter
|
||||
cyberpunk-theme dash deadgrep diff-hl diredfl f
|
||||
grip-mode helm helm-projectile helm-xref ht hydra
|
||||
ibuffer-projectile ibuffer-sidebar lv magit
|
||||
magit-delta markdown-mode markdown-toc modus-themes
|
||||
neotree obsidian olivetti org projectile ripgrep s
|
||||
spinner treemacs treemacs-all-the-icons
|
||||
treemacs-magit treemacs-projectile use-package
|
||||
wgrep which-key))
|
||||
'(safe-local-variable-values
|
||||
'((company-backends
|
||||
(company-qml company-capf company-files company-yasnippet))
|
||||
(lsp-clients-qml-server-executable . "/usr/lib/qt6/bin/qmlls")
|
||||
(company-minimum-prefix-length . 1) (company-idle-delay . 0.2)
|
||||
(lsp-clangd-binary-path . "clangd")
|
||||
(lsp-clients-clangd-args
|
||||
"--compile-commands-dir=/home/jens/sources/thulio"
|
||||
"--background-index" "--clang-tidy"
|
||||
"--completion-style=detailed" "--header-insertion=iwyu"
|
||||
"--pch-storage=memory")
|
||||
(projectile-project-root . "/home/jens/sources/thulio")))
|
||||
'(save-place-mode t)
|
||||
'(show-trailing-whitespace t)
|
||||
'(tool-bar-mode nil)
|
||||
'(url-proxy-services
|
||||
'(("https" . "eudewerepo001:3128") ("http" . "eudewerepo001:3128"))))
|
||||
|
||||
(custom-set-faces
|
||||
;; custom-set-faces was added by Custom.
|
||||
;; If you edit it by hand, you could mess it up, so be careful.
|
||||
;; Your init file should contain only one such instance.
|
||||
;; If there is more than one, they won't work right.
|
||||
'(default ((t (:family "0xProto Nerd Font" :foundry "0x " :slant normal :weight regular :height 120 :width normal))))
|
||||
'(diff-hl-insert ((t (:background "green3" :foreground "green3"))))
|
||||
'(diff-hl-delete ((t (:background "red3" :foreground "red3"))))
|
||||
'(diff-hl-change ((t (:background "blue3" :foreground "blue3")))))
|
||||
|
||||
;;; Helm Configuration (Primary interface)
|
||||
(use-package helm
|
||||
:ensure t
|
||||
:demand t
|
||||
:bind (("M-x" . helm-M-x)
|
||||
("C-x C-f" . helm-find-files)
|
||||
("C-x b" . helm-buffers-list)
|
||||
("C-x r b" . helm-bookmarks)
|
||||
("M-y" . helm-show-kill-ring)
|
||||
("C-h SPC" . helm-all-mark-rings))
|
||||
:config
|
||||
(helm-mode 1)
|
||||
(setq helm-split-window-in-side-p t
|
||||
helm-move-to-line-cycle-in-source t
|
||||
helm-ff-search-library-in-sexp t
|
||||
helm-scroll-amount 8
|
||||
helm-ff-file-name-history-use-recentf t
|
||||
helm-echo-input-in-header-line t)
|
||||
;; Make helm more responsive
|
||||
(setq helm-input-idle-delay 0.01
|
||||
helm-cycle-resume-delay 2
|
||||
helm-follow-input-idle-delay 0.01)
|
||||
;; Prevent helm from opening new frames
|
||||
(setq helm-always-two-windows nil)
|
||||
(setq helm-display-buffer-default-height 15)
|
||||
(setq helm-default-display-buffer-functions '(display-buffer-in-side-window)))
|
||||
|
||||
(use-package helm-xref
|
||||
:ensure t
|
||||
:after helm)
|
||||
|
||||
(use-package helm-projectile
|
||||
:ensure t
|
||||
:after (helm projectile)
|
||||
:config
|
||||
(helm-projectile-on)
|
||||
;; Prevent helm-projectile from opening new frames
|
||||
(setq helm-projectile-sources-list
|
||||
'(helm-source-projectile-buffers-list
|
||||
helm-source-projectile-recentf-list
|
||||
helm-source-projectile-files-list
|
||||
helm-source-projectile-projects))
|
||||
;; Use current window for file actions
|
||||
(setq helm-display-function 'helm-default-display-buffer))
|
||||
|
||||
;;; Load development configuration (optional - use M-x enable-dev-mode)
|
||||
(let ((dev-config (expand-file-name "emacs-dev-config.el" user-emacs-directory)))
|
||||
(when (file-exists-p dev-config)
|
||||
(load-file dev-config)))
|
||||
|
||||
;;; Version Control (basic) - Magit moved to development mode
|
||||
|
||||
;;; Diff-hl Configuration
|
||||
(use-package diff-hl
|
||||
:ensure t
|
||||
:hook ((prog-mode . diff-hl-mode)
|
||||
(dired-mode . diff-hl-dired-mode)
|
||||
(text-mode . diff-hl-mode)
|
||||
(conf-mode . diff-hl-mode))
|
||||
:bind (("M-n" . diff-hl-next-hunk)
|
||||
("M-p" . diff-hl-previous-hunk)
|
||||
("C-c v r" . diff-hl-revert-hunk)
|
||||
("C-c v s" . diff-hl-diff-goto-hunk)
|
||||
("C-c v u" . diff-hl-refresh))
|
||||
:init
|
||||
;; Set fringe width before diff-hl loads
|
||||
(setq-default left-fringe-width 8)
|
||||
(setq-default right-fringe-width 8)
|
||||
;; Ensure fringes are visible
|
||||
(fringe-mode 8)
|
||||
:config
|
||||
;; Configure VC backend for Git
|
||||
(setq vc-handled-backends '(Git))
|
||||
(setq vc-git-diff-switches '("--histogram"))
|
||||
|
||||
;; IMPORTANT: Tell diff-hl to use VC backend, not Magit
|
||||
(setq diff-hl-reference-revision nil) ; Use working tree, not index
|
||||
(setq diff-hl-disable-on-remote nil) ; Work even on remote files
|
||||
|
||||
;; Make diff-hl use the left fringe
|
||||
(setq diff-hl-side 'left)
|
||||
|
||||
;; Ensure diff-hl draws in fringe, not margin
|
||||
(setq diff-hl-draw-borders nil)
|
||||
(setq diff-hl-margin-mode nil)
|
||||
|
||||
;; Set diff-hl fringe bitmaps (ensure they're visible)
|
||||
(setq diff-hl-fringe-bmp-function 'diff-hl-fringe-bmp-from-type)
|
||||
|
||||
;; Enable flydiff for real-time updates using VC
|
||||
(diff-hl-flydiff-mode 1)
|
||||
|
||||
;; Update immediately when visiting a file
|
||||
(setq diff-hl-flydiff-delay 0.3)
|
||||
|
||||
;; Make sure diff-hl updates on various events
|
||||
(add-hook 'after-save-hook 'diff-hl-update)
|
||||
(add-hook 'after-revert-hook 'diff-hl-update)
|
||||
(add-hook 'find-file-hook 'diff-hl-update)
|
||||
(add-hook 'vc-checkin-hook 'diff-hl-update)
|
||||
|
||||
;; Enable globally
|
||||
(global-diff-hl-mode 1)
|
||||
|
||||
;; Explicitly disable Magit integration in base config
|
||||
;; (Magit hooks will only be added when dev-mode is enabled)
|
||||
(setq diff-hl-disable-on-remote nil)
|
||||
|
||||
;; Manual refresh command
|
||||
(defun diff-hl-refresh ()
|
||||
"Manually refresh diff-hl indicators in all buffers."
|
||||
(interactive)
|
||||
(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when diff-hl-mode
|
||||
(diff-hl-update)))))
|
||||
|
||||
;; Troubleshooting function
|
||||
(defun diff-hl-diagnose ()
|
||||
"Diagnose diff-hl issues."
|
||||
(interactive)
|
||||
(let ((diagnosis '()))
|
||||
(push (format "diff-hl-mode: %s" (if diff-hl-mode "enabled" "disabled")) diagnosis)
|
||||
(push (format "Buffer file: %s" (or buffer-file-name "none")) diagnosis)
|
||||
(push (format "VC backend: %s" (or (vc-backend buffer-file-name) "none")) diagnosis)
|
||||
(push (format "VC responsible backend: %s" (or (vc-responsible-backend default-directory) "none")) diagnosis)
|
||||
(push (format "Modified: %s" (if (and buffer-file-name (buffer-modified-p)) "yes" "no")) diagnosis)
|
||||
(push (format "VC state: %s" (when buffer-file-name (vc-state buffer-file-name))) diagnosis)
|
||||
(push (format "Left fringe width: %s" left-fringe-width) diagnosis)
|
||||
(push (format "Right fringe width: %s" right-fringe-width) diagnosis)
|
||||
(push (format "diff-hl-side: %s" diff-hl-side) diagnosis)
|
||||
(push (format "diff-hl-margin-mode: %s" diff-hl-margin-mode) diagnosis)
|
||||
(push (format "diff-hl-reference-revision: %s" diff-hl-reference-revision) diagnosis)
|
||||
(push (format "Magit loaded: %s" (if (fboundp 'magit-status) "yes" "no")) diagnosis)
|
||||
(message (mapconcat 'identity (nreverse diagnosis) "\n"))))
|
||||
|
||||
;; Force VC to refresh its state
|
||||
(defun diff-hl-force-vc-refresh ()
|
||||
"Force VC to refresh state and then update diff-hl."
|
||||
(interactive)
|
||||
(when buffer-file-name
|
||||
(vc-refresh-state)
|
||||
(diff-hl-update)
|
||||
(message "VC state refreshed and diff-hl updated")))
|
||||
|
||||
;; Disable Magit integration if causing issues
|
||||
(defun diff-hl-disable-magit ()
|
||||
"Disable Magit integration with diff-hl."
|
||||
(interactive)
|
||||
(setq diff-hl-disable-magit-integration t)
|
||||
(remove-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
|
||||
(remove-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
|
||||
(message "Magit integration with diff-hl disabled. Using pure VC backend."))
|
||||
|
||||
;; Ensure we're using VC backend
|
||||
(defun diff-hl-ensure-vc-backend ()
|
||||
"Ensure diff-hl is using VC backend."
|
||||
(interactive)
|
||||
(setq diff-hl-reference-revision nil)
|
||||
(diff-hl-disable-magit)
|
||||
(diff-hl-refresh)
|
||||
(message "diff-hl configured to use VC backend only")))
|
||||
|
||||
;;; Treemacs Configuration
|
||||
(use-package treemacs
|
||||
:ensure t
|
||||
:defer t
|
||||
:bind (("M-0" . treemacs-select-window)
|
||||
("C-x t t" . treemacs)
|
||||
("C-x t 1" . treemacs-delete-other-windows)
|
||||
("C-x t d" . treemacs-select-directory)
|
||||
("C-x t B" . treemacs-bookmark)
|
||||
("<f8>" . treemacs)
|
||||
:map treemacs-mode-map
|
||||
("/" . treemacs-search-file)
|
||||
("C-s" . helm-projectile-find-file)
|
||||
("s" . helm-projectile-grep))
|
||||
:config
|
||||
(setq treemacs-collapse-dirs (if treemacs-python-executable 3 0)
|
||||
treemacs-deferred-git-apply-delay 0.5
|
||||
treemacs-directory-name-transformer #'identity
|
||||
treemacs-display-in-side-window t
|
||||
treemacs-eldoc-display 'simple
|
||||
treemacs-file-event-delay 2000
|
||||
treemacs-file-follow-delay 0.2
|
||||
treemacs-follow-after-init t
|
||||
treemacs-expand-after-init t
|
||||
treemacs-find-workspace-method 'find-for-file-or-pick-first
|
||||
treemacs-hide-dot-git-directory t
|
||||
treemacs-indentation 2
|
||||
treemacs-indentation-string " "
|
||||
treemacs-is-never-other-window nil
|
||||
treemacs-max-git-entries 5000
|
||||
treemacs-missing-project-action 'ask
|
||||
treemacs-move-forward-on-expand nil
|
||||
treemacs-no-delete-other-windows t
|
||||
treemacs-persist-file (expand-file-name ".cache/treemacs-persist" user-emacs-directory)
|
||||
treemacs-position 'left
|
||||
treemacs-recenter-distance 0.1
|
||||
treemacs-recenter-after-file-follow nil
|
||||
treemacs-recenter-after-tag-follow nil
|
||||
treemacs-recenter-after-project-jump 'always
|
||||
treemacs-recenter-after-project-expand 'on-distance
|
||||
treemacs-show-cursor nil
|
||||
treemacs-show-hidden-files t
|
||||
treemacs-silent-filewatch nil
|
||||
treemacs-silent-refresh nil
|
||||
treemacs-sorting 'alphabetic-asc
|
||||
treemacs-space-between-root-nodes t
|
||||
treemacs-tag-follow-cleanup t
|
||||
treemacs-tag-follow-delay 1.5
|
||||
treemacs-width 35
|
||||
treemacs-width-is-initially-locked t
|
||||
treemacs-workspace-switch-cleanup nil)
|
||||
|
||||
(treemacs-follow-mode t)
|
||||
(treemacs-filewatch-mode t)
|
||||
(treemacs-fringe-indicator-mode 'always)
|
||||
(when treemacs-python-executable
|
||||
(treemacs-git-commit-diff-mode t))
|
||||
|
||||
(pcase (cons (not (null (executable-find "git")))
|
||||
(not (null treemacs-python-executable)))
|
||||
(`(t . t) (treemacs-git-mode 'deferred))
|
||||
(`(t . _) (treemacs-git-mode 'simple)))
|
||||
|
||||
(treemacs-hide-gitignored-files-mode nil))
|
||||
|
||||
(use-package treemacs-projectile
|
||||
:ensure t
|
||||
:after (treemacs projectile))
|
||||
|
||||
(use-package treemacs-all-the-icons
|
||||
:ensure t
|
||||
:after (treemacs all-the-icons)
|
||||
:config
|
||||
(treemacs-load-theme "all-the-icons"))
|
||||
|
||||
;; Treemacs helper functions
|
||||
(defun treemacs-toggle-and-focus ()
|
||||
"Toggle treemacs and focus on it if it's visible."
|
||||
(interactive)
|
||||
(if (treemacs-get-local-window)
|
||||
(treemacs-toggle)
|
||||
(progn
|
||||
(treemacs)
|
||||
(treemacs-select-window))))
|
||||
|
||||
(defun treemacs-search-file ()
|
||||
"Search for a file in the current project using helm."
|
||||
(interactive)
|
||||
(if (fboundp 'helm-projectile-find-file)
|
||||
(helm-projectile-find-file)
|
||||
(helm-find-files)))
|
||||
|
||||
(defun treemacs-open-marked-files ()
|
||||
"Open all marked files in treemacs."
|
||||
(interactive)
|
||||
(when (eq major-mode 'treemacs-mode)
|
||||
(treemacs-bulk-file-actions
|
||||
:actions '(treemacs-visit-node-no-split))))
|
||||
|
||||
(defun treemacs-mark-visible-files ()
|
||||
"Mark all visible files in the current directory."
|
||||
(interactive)
|
||||
(when (eq major-mode 'treemacs-mode)
|
||||
(save-excursion
|
||||
(treemacs-goto-parent-node)
|
||||
(treemacs-TAB-action)
|
||||
(forward-line 1)
|
||||
(while (and (not (eobp))
|
||||
(> (treemacs--get-depth-of-item) 0))
|
||||
(when (treemacs-is-node-file?)
|
||||
(treemacs-do-mark))
|
||||
(forward-line 1)))))
|
||||
|
||||
(global-set-key (kbd "C-c t f") 'treemacs-toggle-and-focus)
|
||||
(global-set-key (kbd "C-c t s") 'treemacs-search-file)
|
||||
|
||||
;;; Search Configuration (with Helm)
|
||||
(use-package deadgrep
|
||||
:ensure t
|
||||
:bind (("C-c r" . deadgrep)))
|
||||
|
||||
(use-package wgrep
|
||||
:ensure t
|
||||
:config
|
||||
(setq wgrep-auto-save-buffer t)
|
||||
(setq wgrep-enable-key "r"))
|
||||
|
||||
(use-package anzu
|
||||
:ensure t
|
||||
:config
|
||||
(global-anzu-mode 1)
|
||||
:bind (([remap query-replace] . anzu-query-replace)
|
||||
([remap query-replace-regexp] . anzu-query-replace-regexp)))
|
||||
|
||||
;;; Project Management
|
||||
(use-package projectile
|
||||
:ensure t
|
||||
:init
|
||||
(projectile-mode +1)
|
||||
:bind-keymap ("C-c p" . projectile-command-map)
|
||||
:config
|
||||
(setq projectile-completion-system 'helm)
|
||||
(setq projectile-switch-project-action #'projectile-dired))
|
||||
|
||||
;;; Development Tools - Moved to emacs-dev-config.el
|
||||
;;; Use M-x enable-dev-mode to activate development features
|
||||
|
||||
;;; Editor Enhancements
|
||||
(use-package which-key
|
||||
:ensure t
|
||||
:init (which-key-mode)
|
||||
:config
|
||||
(setq which-key-idle-delay 0.3)
|
||||
(setq which-key-popup-type 'side-window))
|
||||
|
||||
;;; Editor Enhancements - Development-specific features moved to emacs-dev-config.el
|
||||
|
||||
(use-package all-the-icons-dired
|
||||
:ensure t
|
||||
:hook (dired-mode . all-the-icons-dired-mode))
|
||||
|
||||
(use-package diredfl
|
||||
:ensure t
|
||||
:config (diredfl-global-mode 1))
|
||||
|
||||
;; Enhanced Dired configuration for multi-file operations
|
||||
(use-package dired
|
||||
:ensure nil
|
||||
:bind (("C-c d" . dired-jump)
|
||||
("C-c D" . projectile-dired))
|
||||
:config
|
||||
(setq dired-dwim-target t) ; Guess target directory
|
||||
(setq dired-recursive-copies 'always)
|
||||
(setq dired-recursive-deletes 'always)
|
||||
(setq dired-listing-switches "-alh --group-directories-first")
|
||||
|
||||
;; Enable multiple file marking with mouse
|
||||
(define-key dired-mode-map (kbd "<mouse-2>") 'dired-find-file)
|
||||
(define-key dired-mode-map (kbd "C-<mouse-1>") 'dired-mouse-mark)
|
||||
|
||||
;; Quick marking shortcuts
|
||||
(define-key dired-mode-map (kbd "* .") 'dired-mark-extension)
|
||||
(define-key dired-mode-map (kbd "* /") 'dired-mark-directories))
|
||||
|
||||
;; Custom sorting: directories first (dotted first), then files (dotted first)
|
||||
(defun dired-sort-dotfiles-first ()
|
||||
"Sort dired: dirs first (dots first within), then files (dots first within)."
|
||||
(save-excursion
|
||||
(let (buffer-read-only)
|
||||
(goto-char (point-min))
|
||||
;; Skip past the directory header
|
||||
(while (and (not (eobp))
|
||||
(not (looking-at "^ \\|^\\s-*$")))
|
||||
(forward-line 1))
|
||||
(let ((start (point))
|
||||
dirs dotdirs files dotfiles special-entries)
|
||||
;; Collect all entries
|
||||
(while (not (eobp))
|
||||
(when (looking-at "^ \\(.*\\)$")
|
||||
(let* ((line (match-string 0))
|
||||
(filename (ignore-errors (dired-get-filename 'no-dir t))))
|
||||
(cond
|
||||
;; Keep . and .. entries separate to put at top
|
||||
((member filename '("." ".."))
|
||||
(push line special-entries))
|
||||
;; Process other entries
|
||||
(filename
|
||||
(let ((fullpath (ignore-errors (dired-get-filename t))))
|
||||
(when fullpath
|
||||
(cond
|
||||
;; Dot directory
|
||||
((and (file-directory-p fullpath)
|
||||
(string-prefix-p "." filename))
|
||||
(push line dotdirs))
|
||||
;; Regular directory
|
||||
((file-directory-p fullpath)
|
||||
(push line dirs))
|
||||
;; Dotfile
|
||||
((string-prefix-p "." filename)
|
||||
(push line dotfiles))
|
||||
;; Regular file
|
||||
(t
|
||||
(push line files)))))))))
|
||||
(forward-line 1))
|
||||
;; Delete old content and insert sorted
|
||||
(when (or special-entries dirs dotdirs files dotfiles)
|
||||
(delete-region start (point-max))
|
||||
(goto-char start)
|
||||
;; Insert in order: . and .., dot dirs, regular dirs, dotfiles, regular files
|
||||
(dolist (line (nreverse special-entries))
|
||||
(insert line "\n"))
|
||||
(dolist (line (sort (nreverse dotdirs) 'string<))
|
||||
(insert line "\n"))
|
||||
(dolist (line (sort (nreverse dirs) 'string<))
|
||||
(insert line "\n"))
|
||||
(dolist (line (sort (nreverse dotfiles) 'string<))
|
||||
(insert line "\n"))
|
||||
(dolist (line (sort (nreverse files) 'string<))
|
||||
(insert line "\n")))))
|
||||
(set-buffer-modified-p nil)))
|
||||
|
||||
;; Apply custom sorting after dired reads directory
|
||||
(add-hook 'dired-after-readin-hook 'dired-sort-dotfiles-first)
|
||||
|
||||
;;; Markdown Support
|
||||
(use-package markdown-mode
|
||||
:ensure t
|
||||
:mode (("\\.md\\'" . markdown-mode)
|
||||
("\\.markdown\\'" . markdown-mode))
|
||||
:hook (markdown-mode . (lambda ()
|
||||
(visual-line-mode 1)
|
||||
(flyspell-mode 1)
|
||||
(auto-fill-mode -1)))
|
||||
:bind (:map markdown-mode-map
|
||||
("C-c C-l" . markdown-insert-link)
|
||||
("C-c C-i" . markdown-insert-image)
|
||||
("C-c C-c p" . markdown-preview)
|
||||
("C-c C-c l" . markdown-live-preview-mode))
|
||||
:config
|
||||
(setq markdown-command "markdown")
|
||||
(setq markdown-enable-wiki-links t)
|
||||
(setq markdown-italic-underscore t)
|
||||
(setq markdown-asymmetric-header t)
|
||||
(setq markdown-make-gfm-checkboxes-buttons t)
|
||||
(setq markdown-gfm-uppercase-checkbox t)
|
||||
(setq markdown-fontify-code-blocks-natively t))
|
||||
|
||||
(use-package markdown-toc
|
||||
:ensure t
|
||||
:after markdown-mode
|
||||
:bind (:map markdown-mode-map
|
||||
("C-c C-t" . markdown-toc-generate-or-refresh-toc)))
|
||||
|
||||
(use-package grip-mode
|
||||
:ensure t
|
||||
:after markdown-mode
|
||||
:bind (:map markdown-mode-map
|
||||
("C-c C-c g" . grip-mode)))
|
||||
|
||||
|
||||
;;; UI Configuration
|
||||
(setq-default display-fill-column-indicator-column 80)
|
||||
(setq-default display-fill-column-indicator-character ?\u2502)
|
||||
(global-display-fill-column-indicator-mode 1)
|
||||
(set-face-attribute 'fill-column-indicator nil :foreground "red")
|
||||
|
||||
;; Enable mouse window resizing
|
||||
(setq mouse-autoselect-window nil) ; Don't auto-select windows on mouse hover
|
||||
(setq window-divider-default-places t) ; Show dividers everywhere
|
||||
(setq window-divider-default-bottom-width 1) ; Bottom divider width
|
||||
(setq window-divider-default-right-width 1) ; Right divider width
|
||||
(window-divider-mode 1) ; Enable window dividers for easier mouse dragging
|
||||
|
||||
;; CUA mode for rectangles
|
||||
(setq cua-auto-tabify-rectangles nil)
|
||||
(setq cua-keep-region-after-copy t)
|
||||
(global-set-key (kbd "C-<return>") 'cua-set-rectangle-mark)
|
||||
|
||||
;;; Session Management
|
||||
(desktop-save-mode 1)
|
||||
(setq desktop-save t)
|
||||
(setq desktop-auto-save-timeout 300)
|
||||
(setq desktop-path '("~/.emacs.d/"))
|
||||
(setq desktop-dirname "~/.emacs.d/")
|
||||
(setq desktop-base-file-name "emacs-desktop")
|
||||
(setq desktop-restore-eager 10)
|
||||
(setq desktop-restore-frames t)
|
||||
|
||||
(savehist-mode 1)
|
||||
(setq savehist-file "~/.emacs.d/savehist")
|
||||
(setq history-length 1000)
|
||||
|
||||
(save-place-mode 1)
|
||||
(setq save-place-file "~/.emacs.d/saveplace")
|
||||
|
||||
(recentf-mode 1)
|
||||
(setq recentf-max-menu-items 50)
|
||||
(setq recentf-max-saved-items 200)
|
||||
|
||||
;;; Performance Optimizations
|
||||
(setq gc-cons-threshold 100000000)
|
||||
(setq read-process-output-max (* 1024 1024))
|
||||
|
||||
;;; General Settings
|
||||
(setq-default indent-tabs-mode nil)
|
||||
(setq tab-width 4)
|
||||
(setq inhibit-startup-screen t)
|
||||
(global-auto-revert-mode t)
|
||||
(electric-pair-mode 1)
|
||||
(show-paren-mode 1)
|
||||
(setq show-paren-delay 0)
|
||||
(global-hl-line-mode 1)
|
||||
|
||||
;; Use system ls for better performance and features
|
||||
(setq ls-lisp-use-insert-directory-program t) ; Use system's ls command
|
||||
(setq insert-directory-program "ls") ; Explicitly set to use ls
|
||||
|
||||
;; Auto-save and backup settings
|
||||
(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))
|
||||
(setq delete-old-versions t
|
||||
kept-new-versions 6
|
||||
kept-old-versions 2
|
||||
version-control t)
|
||||
(setq auto-save-file-name-transforms
|
||||
'((".*" "~/.emacs.d/auto-save-list/" t)))
|
||||
|
||||
;;; Custom Functions
|
||||
(defun kill-current-buffer-no-confirm ()
|
||||
"Kill the current buffer without confirmation, unless it has unsaved changes."
|
||||
(interactive)
|
||||
(kill-buffer (current-buffer)))
|
||||
|
||||
(defun reload-emacs-config ()
|
||||
"Reload the Emacs configuration file."
|
||||
(interactive)
|
||||
(load-file (expand-file-name "init.el" user-emacs-directory))
|
||||
(message "Emacs configuration reloaded!"))
|
||||
|
||||
;;; Final Keybindings
|
||||
(global-set-key (kbd "C-x k") 'kill-current-buffer-no-confirm)
|
||||
(global-set-key (kbd "C-c C-r") 'reload-emacs-config)
|
||||
(global-set-key (kbd "C-x C-b") 'helm-buffers-list)
|
||||
|
||||
;;; Development Mode Information
|
||||
(defun show-dev-mode-info ()
|
||||
"Show information about development mode."
|
||||
(interactive)
|
||||
(message "Development mode is available. Use M-x enable-dev-mode to activate LSP, company-mode, flycheck, and other development tools."))
|
||||
|
||||
;; Show info on startup if dev config is loaded
|
||||
(when (featurep 'emacs-dev-config)
|
||||
(add-hook 'emacs-startup-hook
|
||||
(lambda ()
|
||||
(run-with-timer 1 nil
|
||||
(lambda ()
|
||||
(message "Development mode available. Use M-x enable-dev-mode to activate."))))))
|
||||
|
||||
(provide 'init)
|
||||
;;; init.el ends here
|
||||
77
keybinding-reference.md
Normal file
77
keybinding-reference.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Emacs Keybinding Reference
|
||||
|
||||
## Navigation & Interface
|
||||
- `M-x` - Helm command execution
|
||||
- `C-x C-f` - Helm find files
|
||||
- `C-x b` - Helm switch buffer
|
||||
- `C-x C-b` - Helm buffer list
|
||||
- `M-y` - Helm kill ring
|
||||
- `C-c h o` - Helm occur
|
||||
- `C-h SPC` - Helm mark rings
|
||||
|
||||
## Version Control (Magit)
|
||||
- `C-x g` - Magit status
|
||||
- `C-x M-g` - Magit dispatch
|
||||
- `C-c g` - Magit file dispatch
|
||||
- `C-c C-p` - Save commit as patch (in magit modes)
|
||||
|
||||
## Diff Highlighting
|
||||
- `M-n` - Next diff hunk
|
||||
- `M-p` - Previous diff hunk
|
||||
- `C-c v r` - Revert hunk
|
||||
- `C-c v s` - Show diff at hunk
|
||||
- `C-c v u` - Update diff indicators
|
||||
|
||||
## Treemacs
|
||||
- `M-0` - Select treemacs window
|
||||
- `C-x t t` - Toggle treemacs
|
||||
- `<F8>` - Toggle treemacs (alternative)
|
||||
- `C-x t d` - Select directory
|
||||
- `C-x t B` - Treemacs bookmark
|
||||
- `C-c t f` - Toggle and focus treemacs
|
||||
|
||||
## Search
|
||||
- `C-c r` - Deadgrep (ripgrep search)
|
||||
- `C-c p` - Projectile prefix
|
||||
|
||||
## Development
|
||||
- `C-c l` - LSP prefix
|
||||
- `C-c c` - Compile
|
||||
- `C-c q` - Quick compile and run
|
||||
- `C-c t c` - Generate C++ tags
|
||||
- `C-c t p` - Generate Python tags
|
||||
- `C-c t a` - Generate all tags
|
||||
|
||||
## Multiple Cursors
|
||||
- `C-S-l` - Edit lines
|
||||
- `C-S-d` - Mark all like this
|
||||
- `C->` - Mark next like this
|
||||
- `C-<` - Mark previous like this
|
||||
- `C-c m n` - Skip to next
|
||||
- `C-c m p` - Skip to previous
|
||||
- `C-S-<mouse-1>` - Add cursor on click
|
||||
|
||||
## Editor
|
||||
- `C-=` - Expand region
|
||||
- `C-<return>` - CUA rectangle mark
|
||||
|
||||
## Markdown/Notes
|
||||
- `C-c o j` - Obsidian jump to note
|
||||
- `C-c o n` - New Obsidian note
|
||||
- `C-c o l` - Insert Obsidian link
|
||||
- `C-c o s` - Search Obsidian vault
|
||||
- `C-c C-t` - Generate markdown TOC
|
||||
- `C-c z` - Olivetti mode (distraction-free)
|
||||
|
||||
## File Management
|
||||
- `C-c n t` - Toggle neotree
|
||||
- `C-c h` - Show dev config help
|
||||
|
||||
## System
|
||||
- `C-x k` - Kill buffer (no confirm)
|
||||
- `C-c C-r` - Reload Emacs config
|
||||
|
||||
## Conflicts Resolved
|
||||
- Removed `C-d` override (now uses default delete-char)
|
||||
- Helm replaces all Ivy/Counsel/Swiper bindings
|
||||
- Single interface for completion (Helm only)
|
||||
113
qml-config.el
Normal file
113
qml-config.el
Normal file
@@ -0,0 +1,113 @@
|
||||
;;; -*- lexical-binding: t -*-
|
||||
;; QML Language Server Configuration Helper
|
||||
;; This file provides enhanced QML auto-completion setup
|
||||
|
||||
;; Function to find QML language server executable
|
||||
(defun find-qml-language-server ()
|
||||
"Find the QML language server executable in common locations."
|
||||
(or (executable-find "qml-lsp")
|
||||
(executable-find "qmlls")
|
||||
(executable-find "qml6-lsp")
|
||||
;; Try common Qt installation paths
|
||||
"/usr/lib/qt6/bin/qmlls"
|
||||
"/usr/lib/x86_64-linux-gnu/qt6/bin/qmlls"
|
||||
"/opt/qt6/bin/qmlls"
|
||||
;; Fallback - will show an error if not found
|
||||
"qmlls"))
|
||||
|
||||
;; Enhanced QML mode configuration
|
||||
(use-package qml-mode
|
||||
:ensure t
|
||||
:mode ("\\.qml\\'" . qml-mode)
|
||||
:config
|
||||
;; Set up proper indentation
|
||||
(setq qml-indent-offset 4)
|
||||
|
||||
;; Add QML-specific keywords for better syntax highlighting
|
||||
(font-lock-add-keywords 'qml-mode
|
||||
'(("\\<\\(readonly\\|default\\|signal\\|alias\\)\\>" . font-lock-keyword-face)
|
||||
("\\<\\(Qt\\|QtQuick\\|QtQuick\\.Controls\\)\\>" . font-lock-constant-face))))
|
||||
|
||||
;; Enhanced LSP configuration for QML
|
||||
(with-eval-after-load 'lsp-mode
|
||||
;; Register QML language server
|
||||
(lsp-register-client
|
||||
(make-lsp-client :new-connection (lsp-stdio-connection
|
||||
(lambda () (list (find-qml-language-server))))
|
||||
:activation-fn (lsp-activate-on "qml")
|
||||
:server-id 'qmlls))
|
||||
|
||||
;; QML-specific LSP settings
|
||||
(setq lsp-qml-server-command (find-qml-language-server))
|
||||
|
||||
;; Enable LSP for QML files
|
||||
(add-to-list 'lsp-language-id-configuration '(qml-mode . "qml")))
|
||||
|
||||
;; Enhanced company configuration for QML with error handling
|
||||
(with-eval-after-load 'company
|
||||
;; Check if company-qml is working properly
|
||||
(defun company-qml-safe-p ()
|
||||
"Check if company-qml backend is available and working."
|
||||
(and (featurep 'company-qml)
|
||||
(condition-case nil
|
||||
(progn
|
||||
(company-qml 'candidates "test")
|
||||
t)
|
||||
(error nil))))
|
||||
|
||||
;; QML-specific company backends with fallback
|
||||
(defun setup-qml-company-backends ()
|
||||
"Set up company backends for QML mode with error handling."
|
||||
(if (company-qml-safe-p)
|
||||
;; Use company-qml if it works
|
||||
(setq-local company-backends
|
||||
'((company-qml
|
||||
company-capf
|
||||
company-files
|
||||
company-yasnippet
|
||||
company-dabbrev-code)
|
||||
company-dabbrev))
|
||||
;; Fallback without company-qml
|
||||
(progn
|
||||
(message "company-qml not available or has errors, using LSP-based completion")
|
||||
(setq-local company-backends
|
||||
'((company-capf ; LSP completions (primary for QML)
|
||||
company-files ; File completions
|
||||
company-yasnippet ; Snippet completions
|
||||
company-dabbrev-code ; Code word completions
|
||||
company-keywords) ; Language keywords
|
||||
company-dabbrev))))) ; General word completions
|
||||
|
||||
;; Apply to QML mode
|
||||
(add-hook 'qml-mode-hook 'setup-qml-company-backends))
|
||||
|
||||
;; QML snippets configuration
|
||||
(with-eval-after-load 'yasnippet
|
||||
;; Create QML snippets directory if it doesn't exist
|
||||
(let ((qml-snippets-dir (expand-file-name "snippets/qml-mode" user-emacs-directory)))
|
||||
(unless (file-exists-p qml-snippets-dir)
|
||||
(make-directory qml-snippets-dir t))))
|
||||
|
||||
;; Enhanced QML development settings
|
||||
(add-hook 'qml-mode-hook
|
||||
(lambda ()
|
||||
;; Enable electric pair mode for automatic bracket/quote pairing
|
||||
(electric-pair-local-mode 1)
|
||||
;; Enable automatic indentation
|
||||
(electric-indent-local-mode 1)
|
||||
;; Set tab width
|
||||
(setq tab-width 4)
|
||||
;; Use spaces instead of tabs
|
||||
(setq indent-tabs-mode nil)
|
||||
;; Enable line numbers
|
||||
(display-line-numbers-mode 1)
|
||||
;; Enable syntax checking
|
||||
(flycheck-mode 1)))
|
||||
|
||||
;; Key bindings for QML development
|
||||
(with-eval-after-load 'qml-mode
|
||||
(define-key qml-mode-map (kbd "C-c C-r") 'lsp-rename)
|
||||
(define-key qml-mode-map (kbd "C-c C-d") 'lsp-find-definition)
|
||||
(define-key qml-mode-map (kbd "C-c C-f") 'lsp-find-references))
|
||||
|
||||
(provide 'qml-config)
|
||||
343
symbol-finder.el
Normal file
343
symbol-finder.el
Normal file
@@ -0,0 +1,343 @@
|
||||
;;; symbol-finder.el --- Jump to symbol definitions using Python symbol finder -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; This package provides integration with the Python symbol_finder.py tool
|
||||
;; for quickly jumping to symbol definitions in C++ and QML files.
|
||||
;;
|
||||
;; Usage:
|
||||
;; 1. Add this file to your Emacs load path
|
||||
;; 2. Add (require 'symbol-finder) to your init.el
|
||||
;; 3. Optionally customize symbol-finder-python-script path
|
||||
;; 4. Use M-. to jump to definition (or customize keybinding)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defgroup symbol-finder nil
|
||||
"Jump to symbol definitions using Python symbol finder."
|
||||
:group 'tools)
|
||||
|
||||
(defcustom symbol-finder-python-script
|
||||
(expand-file-name "symbol_finder.py"
|
||||
(file-name-directory (or load-file-name buffer-file-name)))
|
||||
"Path to the symbol_finder.py script."
|
||||
:type 'file
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-root-directory nil
|
||||
"Root directory for symbol indexing. If nil, use project root or default-directory."
|
||||
:type '(choice (const :tag "Auto-detect" nil)
|
||||
(directory :tag "Directory"))
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-cache-directory ".symbol_cache"
|
||||
"Directory name for symbol cache (relative to root or absolute path)."
|
||||
:type 'string
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-use-absolute-cache nil
|
||||
"If non-nil, treat cache-directory as an absolute path."
|
||||
:type 'boolean
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defcustom symbol-finder-auto-index t
|
||||
"Whether to automatically index files when cache is missing."
|
||||
:type 'boolean
|
||||
:group 'symbol-finder)
|
||||
|
||||
(defvar symbol-finder--history nil
|
||||
"History of searched symbols.")
|
||||
|
||||
(defvar symbol-finder--marker-ring (make-ring 20)
|
||||
"Ring of markers for jumping back.")
|
||||
|
||||
(defun symbol-finder--get-root ()
|
||||
"Get the root directory for symbol operations."
|
||||
(or symbol-finder-root-directory
|
||||
(when (fboundp 'projectile-project-root)
|
||||
(projectile-project-root))
|
||||
(when (fboundp 'project-root)
|
||||
(car (project-roots (project-current))))
|
||||
default-directory))
|
||||
|
||||
(defun symbol-finder--get-cache-dir ()
|
||||
"Get the cache directory path."
|
||||
(if (or symbol-finder-use-absolute-cache
|
||||
(file-name-absolute-p symbol-finder-cache-directory))
|
||||
symbol-finder-cache-directory
|
||||
(expand-file-name symbol-finder-cache-directory (symbol-finder--get-root))))
|
||||
|
||||
(defun symbol-finder--run-command (args)
|
||||
"Run symbol_finder.py with ARGS and return output."
|
||||
(let* ((root-dir (symbol-finder--get-root))
|
||||
(cache-dir (symbol-finder--get-cache-dir))
|
||||
(default-directory root-dir)
|
||||
(cmd (format "python3 %s --root %s --cache-dir %s %s"
|
||||
(shell-quote-argument symbol-finder-python-script)
|
||||
(shell-quote-argument root-dir)
|
||||
(shell-quote-argument cache-dir)
|
||||
args)))
|
||||
(shell-command-to-string cmd)))
|
||||
|
||||
(defun symbol-finder-index (&optional force)
|
||||
"Index all source files. With prefix arg FORCE, force reindex."
|
||||
(interactive "P")
|
||||
(message "Indexing files...")
|
||||
(let* ((args (if force "--index --force" "--index"))
|
||||
(output (symbol-finder--run-command args)))
|
||||
(message "%s" (string-trim output))))
|
||||
|
||||
(defun symbol-finder--parse-emacs-output (output)
|
||||
"Parse Emacs-formatted output from symbol finder."
|
||||
(let ((lines (split-string output "\n" t))
|
||||
results)
|
||||
(dolist (line lines)
|
||||
(when (string-match "\\(.+?\\):\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)" line)
|
||||
(push `(:file ,(match-string 1 line)
|
||||
:line ,(string-to-number (match-string 2 line))
|
||||
:column ,(string-to-number (match-string 3 line))
|
||||
:context ,(match-string 4 line))
|
||||
results)))
|
||||
(nreverse results)))
|
||||
|
||||
(defun symbol-finder--push-mark ()
|
||||
"Push current position to marker ring."
|
||||
(ring-insert symbol-finder--marker-ring (point-marker)))
|
||||
|
||||
(defun symbol-finder-jump-to-definition (&optional symbol)
|
||||
"Jump to definition of SYMBOL at point or prompt for symbol."
|
||||
(interactive)
|
||||
(message "symbol-finder-jump-to-definition called") ;; Debug
|
||||
(let* ((symbol (or symbol
|
||||
(thing-at-point 'symbol t)
|
||||
(read-string "Symbol: " nil 'symbol-finder--history)))
|
||||
(cmd (format "--definition %s --emacs" (shell-quote-argument symbol)))
|
||||
(output (symbol-finder--run-command cmd)))
|
||||
(message "Command: python3 %s %s" symbol-finder-python-script cmd) ;; Debug
|
||||
(message "Output: %s" output) ;; Debug
|
||||
(let ((results (symbol-finder--parse-emacs-output output)))
|
||||
(cond
|
||||
((null results)
|
||||
(message "No definition found for '%s'" symbol))
|
||||
((= 1 (length results))
|
||||
(let ((result (car results)))
|
||||
(symbol-finder--push-mark)
|
||||
(find-file (plist-get result :file))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (plist-get result :line)))
|
||||
(move-to-column (1- (plist-get result :column)))
|
||||
(pulse-momentary-highlight-one-line (point))))
|
||||
(t
|
||||
(symbol-finder--select-and-jump results symbol))))))
|
||||
|
||||
(defun symbol-finder-find-references (&optional symbol)
|
||||
"Find all references to SYMBOL at point or prompt for symbol."
|
||||
(interactive)
|
||||
(let* ((symbol (or symbol
|
||||
(thing-at-point 'symbol t)
|
||||
(read-string "Find references to: " nil 'symbol-finder--history)))
|
||||
(output (symbol-finder--run-command
|
||||
(format "--references %s --emacs" (shell-quote-argument symbol)))))
|
||||
(with-current-buffer (get-buffer-create "*Symbol References*")
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert output)
|
||||
(grep-mode)
|
||||
(goto-char (point-min)))
|
||||
(display-buffer (current-buffer)))))
|
||||
|
||||
(defun symbol-finder-find-symbol (&optional exact)
|
||||
"Find symbol by name. With prefix arg EXACT, use exact match."
|
||||
(interactive "P")
|
||||
(let* ((initial (thing-at-point 'symbol t))
|
||||
(symbol (read-string (format "Find symbol%s: " (if exact " (exact)" ""))
|
||||
initial 'symbol-finder--history))
|
||||
(args (format "--find %s --emacs %s"
|
||||
(shell-quote-argument symbol)
|
||||
(if exact "--exact" "")))
|
||||
(output (symbol-finder--run-command args))
|
||||
(results (symbol-finder--parse-emacs-output output)))
|
||||
(cond
|
||||
((null results)
|
||||
(message "No symbols found matching '%s'" symbol))
|
||||
((= 1 (length results))
|
||||
(let ((result (car results)))
|
||||
(symbol-finder--push-mark)
|
||||
(find-file (plist-get result :file))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (plist-get result :line)))
|
||||
(pulse-momentary-highlight-one-line (point))))
|
||||
(t
|
||||
(symbol-finder--select-and-jump results symbol)))))
|
||||
|
||||
(defun symbol-finder--select-and-jump (results symbol)
|
||||
"Let user select from RESULTS and jump to selected SYMBOL."
|
||||
(let* ((choices (mapcar (lambda (r)
|
||||
(format "%s:%d: %s"
|
||||
(file-name-nondirectory (plist-get r :file))
|
||||
(plist-get r :line)
|
||||
(plist-get r :context)))
|
||||
results))
|
||||
(choice (completing-read (format "Select %s: " symbol) choices nil t))
|
||||
(index (cl-position choice choices :test 'equal)))
|
||||
(when index
|
||||
(let ((result (nth index results)))
|
||||
(symbol-finder--push-mark)
|
||||
(find-file (plist-get result :file))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (plist-get result :line)))
|
||||
(pulse-momentary-highlight-one-line (point))))))
|
||||
|
||||
(defun symbol-finder-cache-status ()
|
||||
"Show cache status and statistics."
|
||||
(interactive)
|
||||
(let* ((root-dir (symbol-finder--get-root))
|
||||
(output (symbol-finder--run-command "--stats")))
|
||||
(with-current-buffer (get-buffer-create "*Symbol Cache Status*")
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert "Symbol Finder Cache Status\n")
|
||||
(insert "==========================\n\n")
|
||||
(insert (format "Root directory: %s\n" root-dir))
|
||||
(insert (format "Python script: %s\n\n" symbol-finder-python-script))
|
||||
(insert output)
|
||||
(goto-char (point-min)))
|
||||
(display-buffer (current-buffer)))))
|
||||
|
||||
(defun symbol-finder-diagnose ()
|
||||
"Diagnose symbol-finder setup and keybindings."
|
||||
(interactive)
|
||||
(let ((global-binding (global-key-binding (kbd "M-.")))
|
||||
(local-binding (local-key-binding (kbd "M-.")))
|
||||
(mode-binding (and (boundp 'symbol-finder-mode-map)
|
||||
(lookup-key symbol-finder-mode-map (kbd "M-.")))))
|
||||
(message "=== Symbol-Finder Diagnostic ===")
|
||||
(message "Symbol-finder-mode: %s" (if symbol-finder-mode "ON" "OFF"))
|
||||
(message "Python script: %s" (if (file-exists-p symbol-finder-python-script)
|
||||
"FOUND" "NOT FOUND"))
|
||||
(message "M-. global binding: %s" global-binding)
|
||||
(message "M-. local binding: %s" local-binding)
|
||||
(message "M-. mode binding: %s" mode-binding)
|
||||
(message "Active minor modes: %s" (mapcar 'car minor-mode-alist))
|
||||
(when (eq global-binding 'xref-find-definitions)
|
||||
(message "NOTE: M-. is bound to xref. You may want to use symbol-finder-setup-override-xref"))))
|
||||
|
||||
(defun symbol-finder-setup-override-xref ()
|
||||
"Override xref M-. binding with symbol-finder in current buffer."
|
||||
(interactive)
|
||||
(local-set-key (kbd "M-.") 'symbol-finder-jump-to-definition)
|
||||
(local-set-key (kbd "M-,") 'symbol-finder-pop-mark)
|
||||
(local-set-key (kbd "M-?") 'symbol-finder-find-references)
|
||||
(message "M-. now bound to symbol-finder-jump-to-definition in this buffer"))
|
||||
|
||||
(defun symbol-finder-pop-mark ()
|
||||
"Pop back to previous position in marker ring."
|
||||
(interactive)
|
||||
(if (ring-empty-p symbol-finder--marker-ring)
|
||||
(message "No previous position")
|
||||
(let ((marker (ring-remove symbol-finder--marker-ring 0)))
|
||||
(switch-to-buffer (marker-buffer marker))
|
||||
(goto-char marker))))
|
||||
|
||||
(defun symbol-finder-update-file ()
|
||||
"Update index for current file."
|
||||
(interactive)
|
||||
(when buffer-file-name
|
||||
(message "Updating index for %s..." buffer-file-name)
|
||||
(let* ((args (format "--index --force --root %s"
|
||||
(shell-quote-argument (file-name-directory buffer-file-name))))
|
||||
(output (symbol-finder--run-command args)))
|
||||
(message "Index updated"))))
|
||||
|
||||
;; Auto-update on save
|
||||
(defun symbol-finder--after-save-hook ()
|
||||
"Hook to update index after saving."
|
||||
(when (and buffer-file-name
|
||||
(string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\|qml\\|js\\)$"
|
||||
buffer-file-name))
|
||||
(symbol-finder-update-file)))
|
||||
|
||||
(defcustom symbol-finder-auto-update-on-save nil
|
||||
"Whether to automatically update index on file save."
|
||||
:type 'boolean
|
||||
:group 'symbol-finder
|
||||
:set (lambda (symbol value)
|
||||
(set-default symbol value)
|
||||
(if value
|
||||
(add-hook 'after-save-hook 'symbol-finder--after-save-hook)
|
||||
(remove-hook 'after-save-hook 'symbol-finder--after-save-hook))))
|
||||
|
||||
;; Minor mode for keybindings
|
||||
(defvar symbol-finder-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "M-.") 'symbol-finder-jump-to-definition)
|
||||
(define-key map (kbd "M-?") 'symbol-finder-find-references)
|
||||
(define-key map (kbd "M-,") 'symbol-finder-pop-mark)
|
||||
(define-key map (kbd "C-c s f") 'symbol-finder-find-symbol)
|
||||
(define-key map (kbd "C-c s i") 'symbol-finder-index)
|
||||
(define-key map (kbd "C-c s u") 'symbol-finder-update-file)
|
||||
map)
|
||||
"Keymap for symbol-finder-mode.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode symbol-finder-mode
|
||||
"Minor mode for symbol navigation using Python symbol finder."
|
||||
:lighter " SymF"
|
||||
:keymap symbol-finder-mode-map
|
||||
:group 'symbol-finder
|
||||
(when symbol-finder-mode
|
||||
;; Debug message
|
||||
(message "Symbol-finder-mode activated. M-. bound to: %s"
|
||||
(lookup-key symbol-finder-mode-map (kbd "M-.")))
|
||||
(when (and symbol-finder-auto-index
|
||||
(not (file-exists-p
|
||||
(expand-file-name symbol-finder-cache-directory
|
||||
(symbol-finder--get-root)))))
|
||||
(when (y-or-n-p "No symbol cache found. Index files now?")
|
||||
(symbol-finder-index)))))
|
||||
|
||||
;;;###autoload
|
||||
(define-globalized-minor-mode global-symbol-finder-mode
|
||||
symbol-finder-mode
|
||||
(lambda ()
|
||||
(when (and (not (minibufferp))
|
||||
(string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\|qml\\|js\\)$"
|
||||
(or buffer-file-name "")))
|
||||
(symbol-finder-mode 1))))
|
||||
|
||||
;; Compatibility with xref (optional)
|
||||
(when (fboundp 'xref-make)
|
||||
(defun symbol-finder-xref-backend ()
|
||||
"Symbol finder backend for xref."
|
||||
'symbol-finder)
|
||||
|
||||
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql symbol-finder)))
|
||||
(thing-at-point 'symbol t))
|
||||
|
||||
(cl-defmethod xref-backend-definitions ((_backend (eql symbol-finder)) identifier)
|
||||
(let* ((output (symbol-finder--run-command
|
||||
(format "--definition %s --emacs" (shell-quote-argument identifier))))
|
||||
(results (symbol-finder--parse-emacs-output output)))
|
||||
(mapcar (lambda (r)
|
||||
(xref-make (plist-get r :context)
|
||||
(xref-make-file-location (plist-get r :file)
|
||||
(plist-get r :line)
|
||||
(1- (plist-get r :column)))))
|
||||
results)))
|
||||
|
||||
(cl-defmethod xref-backend-references ((_backend (eql symbol-finder)) identifier)
|
||||
(let* ((output (symbol-finder--run-command
|
||||
(format "--references %s --emacs" (shell-quote-argument identifier))))
|
||||
(results (symbol-finder--parse-emacs-output output)))
|
||||
(mapcar (lambda (r)
|
||||
(xref-make (plist-get r :context)
|
||||
(xref-make-file-location (plist-get r :file)
|
||||
(plist-get r :line)
|
||||
(1- (plist-get r :column)))))
|
||||
results)))
|
||||
|
||||
(add-hook 'symbol-finder-mode-hook
|
||||
(lambda ()
|
||||
(add-hook 'xref-backend-functions 'symbol-finder-xref-backend nil t))))
|
||||
|
||||
(provide 'symbol-finder)
|
||||
;;; symbol-finder.el ends here
|
||||
508
symbol_finder.py
Executable file
508
symbol_finder.py
Executable file
@@ -0,0 +1,508 @@
|
||||
#!/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