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:
Jens Luedicke
2025-09-05 13:25:33 +02:00
commit ef79598cfc
9 changed files with 3061 additions and 0 deletions

115
.gitignore vendored Normal file
View 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
View File

@@ -0,0 +1,587 @@
;;; bungee.el --- Fast symbol navigation with Elisp-based caching -*- lexical-binding: t; -*-
;;; Commentary:
;; Bungee provides fast symbol navigation for C++ and QML files using
;; an Elisp-based cache system. It can work standalone or with the
;; Python symbol_finder.py for indexing.
;;; Code:
(require 'json)
(require 'cl-lib)
(require 'xref nil t)
(require 'pulse nil t)
(require 'grep nil t)
(defgroup bungee nil
"Fast symbol navigation with caching."
:group 'tools)
(defcustom bungee-cache-directory ".symbol_cache"
"Directory for symbol cache files."
:type 'string
:group 'bungee)
(defcustom bungee-python-indexer nil
"Path to Python indexer script (optional). If nil, use Elisp indexer."
:type '(choice (const :tag "Use Elisp indexer" nil)
(file :tag "Python script path"))
:group 'bungee)
(defcustom bungee-auto-update t
"Automatically update cache when files change."
:type 'boolean
:group 'bungee)
(defcustom bungee-save-json-cache nil
"Also save cache in JSON format for Python compatibility."
:type 'boolean
:group 'bungee)
;; Cache data structures
(defvar bungee--symbol-cache nil
"In-memory symbol cache. Hash table mapping file paths to symbol lists.")
(defvar bungee--index-cache nil
"In-memory index cache. Hash table mapping file paths to modification times.")
(defvar bungee--cache-loaded nil
"Whether the cache has been loaded from disk.")
(cl-defstruct bungee-symbol
"A symbol in the codebase."
name ; Symbol name
file-path ; Absolute file path
line-number ; Line number (1-based)
symbol-type ; 'class, 'function, 'property, etc.
context) ; Line content for context
;; Cache management functions
(defun bungee--cache-dir ()
"Get the cache directory path."
(let ((root (or (and (fboundp 'project-current)
(project-current)
(fboundp 'project-root)
(ignore-errors
(car (project-roots (project-current)))))
default-directory)))
(expand-file-name bungee-cache-directory root)))
(defun bungee--cache-file-path (filename)
"Get path for cache FILENAME."
(expand-file-name filename (bungee--cache-dir)))
(defun bungee--load-cache ()
"Load cache from disk into memory."
(let ((cache-file (bungee--cache-file-path "bungee-cache.el"))
(json-symbols-file (bungee--cache-file-path "symbols.json"))
(json-index-file (bungee--cache-file-path "index.json")))
;; Initialize hash tables
(setq bungee--symbol-cache (make-hash-table :test 'equal))
(setq bungee--index-cache (make-hash-table :test 'equal))
(cond
;; Prefer Elisp cache if it exists
((file-exists-p cache-file)
(load cache-file nil t)
;; Convert loaded lists back to symbol structs
(let ((new-cache (make-hash-table :test 'equal)))
(maphash (lambda (file-path symbol-lists)
(puthash file-path
(mapcar (lambda (s)
(make-bungee-symbol
:name (nth 0 s)
:file-path (nth 1 s)
:line-number (nth 2 s)
:symbol-type (nth 3 s)
:context (nth 4 s)))
symbol-lists)
new-cache))
bungee--symbol-cache)
(setq bungee--symbol-cache new-cache))
(message "Loaded Elisp cache: %d files" (hash-table-count bungee--index-cache)))
;; Fall back to JSON if available
((and (file-exists-p json-symbols-file) (file-exists-p json-index-file))
(bungee--load-json-cache json-symbols-file json-index-file)
(message "Loaded JSON cache: %d files" (hash-table-count bungee--index-cache)))
(t
(message "No cache found. Run `bungee-index-directory' to create one.")))
(setq bungee--cache-loaded t)))
(defun bungee--load-json-cache (symbols-file index-file)
"Load JSON cache from SYMBOLS-FILE and INDEX-FILE."
;; Load symbols
(when (file-exists-p symbols-file)
(let* ((json-object-type 'hash-table)
(json-array-type 'list)
(json-key-type 'string)
(symbols-data (json-read-file symbols-file)))
(maphash (lambda (file-path symbols-list)
(let ((symbols (mapcar (lambda (s)
(make-bungee-symbol
:name (gethash "name" s)
:file-path (gethash "file_path" s)
:line-number (gethash "line_number" s)
:symbol-type (intern (gethash "symbol_type" s))
:context (gethash "context" s)))
symbols-list)))
(puthash file-path symbols bungee--symbol-cache)))
symbols-data)))
;; Load index
(when (file-exists-p index-file)
(let* ((json-object-type 'hash-table)
(json-key-type 'string)
(index-data (json-read-file index-file)))
(maphash (lambda (file-path info)
(puthash file-path (gethash "mtime" info) bungee--index-cache))
index-data))))
(defun bungee--ensure-cache ()
"Ensure cache is loaded."
(unless bungee--cache-loaded
(bungee--load-cache)))
(defun bungee--save-cache ()
"Save in-memory cache to disk as Elisp code."
(let ((cache-dir (bungee--cache-dir)))
(unless (file-exists-p cache-dir)
(make-directory cache-dir t))
;; Save as Elisp file for fast loading
(with-temp-file (bungee--cache-file-path "bungee-cache.el")
(insert ";;; bungee-cache.el --- Bungee symbol cache -*- lexical-binding: t; -*-\n")
(insert ";;; This file is auto-generated. Do not edit.\n\n")
;; Save symbol cache
(insert "(setq bungee--symbol-cache (make-hash-table :test 'equal))\n\n")
(maphash (lambda (file-path symbols)
(insert (format "(puthash %S\n '(" (abbreviate-file-name file-path)))
(dolist (symbol symbols)
(insert (format "\n %S"
(list (bungee-symbol-name symbol)
(bungee-symbol-file-path symbol)
(bungee-symbol-line-number symbol)
(bungee-symbol-symbol-type symbol)
(bungee-symbol-context symbol)))))
(insert ")\n bungee--symbol-cache)\n\n"))
bungee--symbol-cache)
;; Save index cache
(insert "(setq bungee--index-cache (make-hash-table :test 'equal))\n\n")
(maphash (lambda (file-path mtime)
(insert (format "(puthash %S %S bungee--index-cache)\n"
(abbreviate-file-name file-path) mtime)))
bungee--index-cache)
(insert "\n;;; bungee-cache.el ends here\n"))
;; Optionally save as JSON for compatibility with Python tool
(when bungee-save-json-cache
(bungee--save-json-cache))))
(defun bungee--save-json-cache ()
"Save cache in JSON format for Python compatibility."
(let ((cache-dir (bungee--cache-dir)))
;; Save symbols
(let ((symbols-file (expand-file-name "symbols.json" cache-dir))
(symbols-data (make-hash-table :test 'equal)))
(maphash (lambda (file-path symbols)
(puthash file-path
(mapcar (lambda (s)
`((name . ,(bungee-symbol-name s))
(file_path . ,(bungee-symbol-file-path s))
(line_number . ,(bungee-symbol-line-number s))
(symbol_type . ,(symbol-name (bungee-symbol-symbol-type s)))
(context . ,(bungee-symbol-context s))))
symbols)
symbols-data))
bungee--symbol-cache)
(with-temp-file symbols-file
(insert (json-encode symbols-data))))
;; Save index
(let ((index-file (expand-file-name "index.json" cache-dir))
(index-data (make-hash-table :test 'equal)))
(maphash (lambda (file-path mtime)
(puthash file-path
`((mtime . ,mtime)
(symbol_count . ,(length (gethash file-path bungee--symbol-cache))))
index-data))
bungee--index-cache)
(with-temp-file index-file
(insert (json-encode index-data))))))
(defun bungee--file-cached-p (file-path)
"Check if FILE-PATH is cached and up to date."
(bungee--ensure-cache)
(let ((cached-mtime (gethash file-path bungee--index-cache))
(current-mtime (and (file-exists-p file-path)
(float-time (nth 5 (file-attributes file-path))))))
(and cached-mtime current-mtime (>= cached-mtime current-mtime))))
;; Elisp-based parsing
(defun bungee--parse-qml-file (file-path)
"Parse a QML file and extract symbols."
(let ((symbols '())
(case-fold-search nil))
(with-temp-buffer
(insert-file-contents file-path)
(goto-char (point-min))
;; Find QML types
(save-excursion
(while (re-search-forward "^\\s-*\\([A-Z]\\w*\\)\\s-*{" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'class
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols)))
;; Find properties (including readonly)
;; Format: [readonly] property <type> <name>
(goto-char (point-min))
(while (re-search-forward "^\\s-*\\(?:readonly\\s-+\\)?property\\s-+[\\w.<>]+\\s-+\\([a-zA-Z_]\\w*\\)" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'property
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols))
;; Find signals
(goto-char (point-min))
(while (re-search-forward "^\\s-*signal\\s-+\\([a-zA-Z_]\\w*\\)" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'signal
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols))
;; Find functions
(goto-char (point-min))
(while (re-search-forward "^\\s-*function\\s-+\\([a-zA-Z_]\\w*\\)\\s-*(" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'function
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols))
;; Find ids
(goto-char (point-min))
(while (re-search-forward "^\\s-*id:\\s-*\\([a-zA-Z_]\\w*\\)" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'variable
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols)))
(nreverse symbols)))
(defun bungee--parse-cpp-file (file-path)
"Parse a C++ file and extract symbols."
(let ((symbols '())
(case-fold-search nil))
(with-temp-buffer
(insert-file-contents file-path)
(goto-char (point-min))
;; Find classes/structs
(save-excursion
(while (re-search-forward "^\\s-*\\(?:class\\|struct\\|union\\)\\s-+\\([A-Za-z_]\\w*\\)" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'class
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols)))
;; Find namespaces
(goto-char (point-min))
(while (re-search-forward "^\\s-*namespace\\s-+\\([A-Za-z_]\\w*\\)" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'namespace
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols))
;; Find enums
(goto-char (point-min))
(while (re-search-forward "^\\s-*enum\\s-+\\(?:class\\s-+\\)?\\([A-Za-z_]\\w*\\)" nil t)
(push (make-bungee-symbol
:name (match-string 1)
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'enum
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols))
;; Basic function detection (simplified)
(goto-char (point-min))
(while (re-search-forward "^\\s-*\\(?:\\w+\\s-+\\)*\\([A-Za-z_]\\w*\\)\\s-*([^)]*)[^;{]*{" nil t)
(let ((name (match-string 1)))
(unless (member name '("if" "while" "for" "switch" "catch"))
(push (make-bungee-symbol
:name name
:file-path file-path
:line-number (line-number-at-pos)
:symbol-type 'function
:context (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
symbols)))))
(nreverse symbols)))
(defun bungee--index-file (file-path &optional force)
"Index FILE-PATH. If FORCE is non-nil, reindex even if cached."
(bungee--ensure-cache)
(when (or force (not (bungee--file-cached-p file-path)))
(let ((symbols (cond
((string-match-p "\\.qml\\'" file-path)
(bungee--parse-qml-file file-path))
((string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'" file-path)
(bungee--parse-cpp-file file-path))
(t nil))))
(when symbols
(puthash file-path symbols bungee--symbol-cache)
(puthash file-path (float-time (nth 5 (file-attributes file-path))) bungee--index-cache)
(message "Indexed %s: %d symbols" file-path (length symbols))))))
;; Symbol lookup functions
(defun bungee-find-symbol (symbol-name &optional exact-match)
"Find all symbols matching SYMBOL-NAME. If EXACT-MATCH is non-nil, use exact matching."
(bungee--ensure-cache)
(let ((results '()))
(maphash (lambda (_ symbols)
(dolist (symbol symbols)
(when (if exact-match
(string= (bungee-symbol-name symbol) symbol-name)
(string-match-p (regexp-quote symbol-name)
(bungee-symbol-name symbol)))
(push symbol results))))
bungee--symbol-cache)
(sort results (lambda (a b)
(or (string< (bungee-symbol-name a) (bungee-symbol-name b))
(< (bungee-symbol-line-number a) (bungee-symbol-line-number b)))))))
(defun bungee-find-definition (symbol-name)
"Find the most likely definition of SYMBOL-NAME."
(let ((symbols (bungee-find-symbol symbol-name t)))
;; Prioritize by symbol type
(or (cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'class)) symbols)
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'namespace)) symbols)
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'function)) symbols)
(cl-find-if (lambda (s) (eq (bungee-symbol-symbol-type s) 'property)) symbols)
(car symbols))))
;; Interactive commands
(defun bungee-jump-to-definition ()
"Jump to definition of symbol at point."
(interactive)
(let* ((symbol-name (or (thing-at-point 'symbol t)
(read-string "Symbol: ")))
(symbol (bungee-find-definition symbol-name)))
(if symbol
(progn
(push-mark)
(find-file (bungee-symbol-file-path symbol))
(goto-char (point-min))
(forward-line (1- (bungee-symbol-line-number symbol)))
(when (fboundp 'pulse-momentary-highlight-one-line)
(pulse-momentary-highlight-one-line (point)))
(message "Found: %s" (bungee-symbol-context symbol)))
(message "No definition found for '%s'" symbol-name))))
(defun bungee-find-references ()
"Find all references to symbol at point."
(interactive)
(let* ((symbol-name (or (thing-at-point 'symbol t)
(read-string "Find references to: ")))
(symbols (bungee-find-symbol symbol-name)))
(if symbols
(let ((buffer (get-buffer-create "*Bungee References*")))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(insert (format "References to '%s':\n\n" symbol-name))
(dolist (symbol symbols)
(insert (format "%s:%d: %s [%s]\n"
(bungee-symbol-file-path symbol)
(bungee-symbol-line-number symbol)
(bungee-symbol-context symbol)
(bungee-symbol-symbol-type symbol))))
(goto-char (point-min))
(grep-mode)))
(display-buffer buffer))
(message "No references found for '%s'" symbol-name))))
(defun bungee-index-directory (&optional directory force)
"Index all files in DIRECTORY. If FORCE is non-nil, reindex all files."
(interactive "DDirectory to index: \nP")
(let* ((dir (or directory default-directory))
(files (directory-files-recursively
dir
"\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
nil
(lambda (d) (not (or (string-match-p "/\\." d)
(string-match-p "/node_modules" d)
(string-match-p "/CMakeFiles" d))))))
(count 0))
(dolist (file files)
(when (or force (not (bungee--file-cached-p file)))
(bungee--index-file file force)
(setq count (1+ count))))
(bungee--save-cache)
(message "Indexed %d files" count)))
(defun bungee-index-current-file ()
"Index or reindex the current file."
(interactive)
(when buffer-file-name
(bungee--index-file buffer-file-name t)
(bungee--save-cache)))
(defun bungee-cache-status ()
"Show cache status."
(interactive)
(bungee--ensure-cache)
(let ((file-count (hash-table-count bungee--index-cache))
(symbol-count 0))
(maphash (lambda (_ symbols)
(setq symbol-count (+ symbol-count (length symbols))))
bungee--symbol-cache)
(message "Bungee cache: %d files, %d symbols" file-count symbol-count)))
(defun bungee-clear-cache ()
"Clear the in-memory cache."
(interactive)
(setq bungee--symbol-cache nil
bungee--index-cache nil
bungee--cache-loaded nil)
(message "Bungee cache cleared"))
;; Python indexer integration (optional)
(defun bungee-index-with-python (&optional force)
"Index using Python script if configured."
(interactive "P")
(if bungee-python-indexer
(let* ((root (or (when (fboundp 'project-root)
(car (project-roots (project-current))))
default-directory))
(cmd (format "python3 %s --index %s --root %s --cache-dir %s"
(shell-quote-argument bungee-python-indexer)
(if force "--force" "")
(shell-quote-argument root)
(shell-quote-argument (bungee--cache-dir)))))
(message "Running: %s" cmd)
(shell-command cmd)
(bungee-clear-cache)
(bungee--load-cache))
(message "Python indexer not configured. Use `bungee-index-directory' instead.")))
;; Auto-update on save
(defun bungee--after-save-hook ()
"Update index after saving a file."
(when (and bungee-auto-update
buffer-file-name
(string-match-p "\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
buffer-file-name))
(bungee--index-file buffer-file-name t)
(bungee--save-cache)))
;; Minor mode
(defvar bungee-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-.") 'bungee-jump-to-definition)
(define-key map (kbd "M-?") 'bungee-find-references)
(define-key map (kbd "C-c b i") 'bungee-index-directory)
(define-key map (kbd "C-c b f") 'bungee-index-current-file)
(define-key map (kbd "C-c b s") 'bungee-cache-status)
(define-key map (kbd "C-c b c") 'bungee-clear-cache)
(define-key map (kbd "C-c b p") 'bungee-index-with-python)
map)
"Keymap for bungee-mode.")
;;;###autoload
(define-minor-mode bungee-mode
"Minor mode for fast symbol navigation with caching."
:lighter " Bungee"
:keymap bungee-mode-map
(if bungee-mode
(add-hook 'after-save-hook 'bungee--after-save-hook nil t)
(remove-hook 'after-save-hook 'bungee--after-save-hook t)))
;;;###autoload
(define-globalized-minor-mode global-bungee-mode
bungee-mode
(lambda ()
(when (and (not (minibufferp))
buffer-file-name
(string-match-p "\\.\\(qml\\|cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\)\\'"
buffer-file-name))
(bungee-mode 1))))
;; xref integration
(when (fboundp 'xref-make)
(defun bungee-xref-backend () 'bungee)
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql bungee)))
(thing-at-point 'symbol t))
(cl-defmethod xref-backend-definitions ((_backend (eql bungee)) identifier)
(let ((symbol (bungee-find-definition identifier)))
(when symbol
(list (xref-make (bungee-symbol-context symbol)
(xref-make-file-location
(bungee-symbol-file-path symbol)
(bungee-symbol-line-number symbol)
0))))))
(cl-defmethod xref-backend-references ((_backend (eql bungee)) identifier)
(mapcar (lambda (symbol)
(xref-make (bungee-symbol-context symbol)
(xref-make-file-location
(bungee-symbol-file-path symbol)
(bungee-symbol-line-number symbol)
0)))
(bungee-find-symbol identifier)))
(add-hook 'bungee-mode-hook
(lambda ()
(add-hook 'xref-backend-functions 'bungee-xref-backend nil t))))
(provide 'bungee)
;;; bungee.el ends here

595
emacs-dev-config.el Normal file
View 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
View File

@@ -0,0 +1,63 @@
;;; init-bungee.el --- Initialize Bungee symbol finder -*- lexical-binding: t; -*-
;; Load and configure the Bungee package
(load-file "~/.emacs.d/bungee.el")
(require 'bungee)
;; Configure cache directory
(setq bungee-cache-directory ".symbol_cache") ; Use standard cache dir
;; Optional: Use Python indexer for better parsing
;; Only set if the file exists
(let ((python-indexer-path "~/sources/bungee/symbol_finder.py"))
(when (file-exists-p (expand-file-name python-indexer-path))
(setq bungee-python-indexer python-indexer-path)))
;; Optional: Save JSON cache for Python tool compatibility
(setq bungee-save-json-cache nil) ; Set to t if you need Python compatibility
;; Enable auto-update when saving files
(setq bungee-auto-update t)
;; Enable Bungee mode globally for supported files
(global-bungee-mode 1)
;; Override M-. in QML and C++ files to use Bungee
(add-hook 'qml-mode-hook
(lambda ()
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
(local-set-key (kbd "M-?") 'bungee-find-references)
(local-set-key (kbd "M-,") 'pop-tag-mark)))
(add-hook 'c++-mode-hook
(lambda ()
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
(local-set-key (kbd "M-?") 'bungee-find-references)
(local-set-key (kbd "M-,") 'pop-tag-mark)))
;; For .qml files if qml-mode is not available
(add-to-list 'auto-mode-alist '("\\.qml\\'" . js-mode))
(add-hook 'js-mode-hook
(lambda ()
(when (and buffer-file-name
(string-match-p "\\.qml\\'" buffer-file-name))
(bungee-mode 1)
(local-set-key (kbd "M-.") 'bungee-jump-to-definition)
(local-set-key (kbd "M-?") 'bungee-find-references)
(local-set-key (kbd "M-,") 'pop-tag-mark))))
;; Convenient commands
(defun bungee-reindex-project ()
"Reindex the entire project using Python indexer."
(interactive)
(if bungee-python-indexer
(bungee-index-with-python t)
(bungee-index-directory nil t)))
(defun bungee-reindex-current-project ()
"Force reindex current project."
(interactive)
(bungee-index-directory nil t))
(provide 'init-bungee)
;;; init-bungee.el ends here

660
init.el Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,343 @@
;;; symbol-finder.el --- Jump to symbol definitions using Python symbol finder -*- lexical-binding: t; -*-
;;; Commentary:
;; This package provides integration with the Python symbol_finder.py tool
;; for quickly jumping to symbol definitions in C++ and QML files.
;;
;; Usage:
;; 1. Add this file to your Emacs load path
;; 2. Add (require 'symbol-finder) to your init.el
;; 3. Optionally customize symbol-finder-python-script path
;; 4. Use M-. to jump to definition (or customize keybinding)
;;; Code:
(defgroup symbol-finder nil
"Jump to symbol definitions using Python symbol finder."
:group 'tools)
(defcustom symbol-finder-python-script
(expand-file-name "symbol_finder.py"
(file-name-directory (or load-file-name buffer-file-name)))
"Path to the symbol_finder.py script."
:type 'file
:group 'symbol-finder)
(defcustom symbol-finder-root-directory nil
"Root directory for symbol indexing. If nil, use project root or default-directory."
:type '(choice (const :tag "Auto-detect" nil)
(directory :tag "Directory"))
:group 'symbol-finder)
(defcustom symbol-finder-cache-directory ".symbol_cache"
"Directory name for symbol cache (relative to root or absolute path)."
:type 'string
:group 'symbol-finder)
(defcustom symbol-finder-use-absolute-cache nil
"If non-nil, treat cache-directory as an absolute path."
:type 'boolean
:group 'symbol-finder)
(defcustom symbol-finder-auto-index t
"Whether to automatically index files when cache is missing."
:type 'boolean
:group 'symbol-finder)
(defvar symbol-finder--history nil
"History of searched symbols.")
(defvar symbol-finder--marker-ring (make-ring 20)
"Ring of markers for jumping back.")
(defun symbol-finder--get-root ()
"Get the root directory for symbol operations."
(or symbol-finder-root-directory
(when (fboundp 'projectile-project-root)
(projectile-project-root))
(when (fboundp 'project-root)
(car (project-roots (project-current))))
default-directory))
(defun symbol-finder--get-cache-dir ()
"Get the cache directory path."
(if (or symbol-finder-use-absolute-cache
(file-name-absolute-p symbol-finder-cache-directory))
symbol-finder-cache-directory
(expand-file-name symbol-finder-cache-directory (symbol-finder--get-root))))
(defun symbol-finder--run-command (args)
"Run symbol_finder.py with ARGS and return output."
(let* ((root-dir (symbol-finder--get-root))
(cache-dir (symbol-finder--get-cache-dir))
(default-directory root-dir)
(cmd (format "python3 %s --root %s --cache-dir %s %s"
(shell-quote-argument symbol-finder-python-script)
(shell-quote-argument root-dir)
(shell-quote-argument cache-dir)
args)))
(shell-command-to-string cmd)))
(defun symbol-finder-index (&optional force)
"Index all source files. With prefix arg FORCE, force reindex."
(interactive "P")
(message "Indexing files...")
(let* ((args (if force "--index --force" "--index"))
(output (symbol-finder--run-command args)))
(message "%s" (string-trim output))))
(defun symbol-finder--parse-emacs-output (output)
"Parse Emacs-formatted output from symbol finder."
(let ((lines (split-string output "\n" t))
results)
(dolist (line lines)
(when (string-match "\\(.+?\\):\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)" line)
(push `(:file ,(match-string 1 line)
:line ,(string-to-number (match-string 2 line))
:column ,(string-to-number (match-string 3 line))
:context ,(match-string 4 line))
results)))
(nreverse results)))
(defun symbol-finder--push-mark ()
"Push current position to marker ring."
(ring-insert symbol-finder--marker-ring (point-marker)))
(defun symbol-finder-jump-to-definition (&optional symbol)
"Jump to definition of SYMBOL at point or prompt for symbol."
(interactive)
(message "symbol-finder-jump-to-definition called") ;; Debug
(let* ((symbol (or symbol
(thing-at-point 'symbol t)
(read-string "Symbol: " nil 'symbol-finder--history)))
(cmd (format "--definition %s --emacs" (shell-quote-argument symbol)))
(output (symbol-finder--run-command cmd)))
(message "Command: python3 %s %s" symbol-finder-python-script cmd) ;; Debug
(message "Output: %s" output) ;; Debug
(let ((results (symbol-finder--parse-emacs-output output)))
(cond
((null results)
(message "No definition found for '%s'" symbol))
((= 1 (length results))
(let ((result (car results)))
(symbol-finder--push-mark)
(find-file (plist-get result :file))
(goto-char (point-min))
(forward-line (1- (plist-get result :line)))
(move-to-column (1- (plist-get result :column)))
(pulse-momentary-highlight-one-line (point))))
(t
(symbol-finder--select-and-jump results symbol))))))
(defun symbol-finder-find-references (&optional symbol)
"Find all references to SYMBOL at point or prompt for symbol."
(interactive)
(let* ((symbol (or symbol
(thing-at-point 'symbol t)
(read-string "Find references to: " nil 'symbol-finder--history)))
(output (symbol-finder--run-command
(format "--references %s --emacs" (shell-quote-argument symbol)))))
(with-current-buffer (get-buffer-create "*Symbol References*")
(let ((inhibit-read-only t))
(erase-buffer)
(insert output)
(grep-mode)
(goto-char (point-min)))
(display-buffer (current-buffer)))))
(defun symbol-finder-find-symbol (&optional exact)
"Find symbol by name. With prefix arg EXACT, use exact match."
(interactive "P")
(let* ((initial (thing-at-point 'symbol t))
(symbol (read-string (format "Find symbol%s: " (if exact " (exact)" ""))
initial 'symbol-finder--history))
(args (format "--find %s --emacs %s"
(shell-quote-argument symbol)
(if exact "--exact" "")))
(output (symbol-finder--run-command args))
(results (symbol-finder--parse-emacs-output output)))
(cond
((null results)
(message "No symbols found matching '%s'" symbol))
((= 1 (length results))
(let ((result (car results)))
(symbol-finder--push-mark)
(find-file (plist-get result :file))
(goto-char (point-min))
(forward-line (1- (plist-get result :line)))
(pulse-momentary-highlight-one-line (point))))
(t
(symbol-finder--select-and-jump results symbol)))))
(defun symbol-finder--select-and-jump (results symbol)
"Let user select from RESULTS and jump to selected SYMBOL."
(let* ((choices (mapcar (lambda (r)
(format "%s:%d: %s"
(file-name-nondirectory (plist-get r :file))
(plist-get r :line)
(plist-get r :context)))
results))
(choice (completing-read (format "Select %s: " symbol) choices nil t))
(index (cl-position choice choices :test 'equal)))
(when index
(let ((result (nth index results)))
(symbol-finder--push-mark)
(find-file (plist-get result :file))
(goto-char (point-min))
(forward-line (1- (plist-get result :line)))
(pulse-momentary-highlight-one-line (point))))))
(defun symbol-finder-cache-status ()
"Show cache status and statistics."
(interactive)
(let* ((root-dir (symbol-finder--get-root))
(output (symbol-finder--run-command "--stats")))
(with-current-buffer (get-buffer-create "*Symbol Cache Status*")
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Symbol Finder Cache Status\n")
(insert "==========================\n\n")
(insert (format "Root directory: %s\n" root-dir))
(insert (format "Python script: %s\n\n" symbol-finder-python-script))
(insert output)
(goto-char (point-min)))
(display-buffer (current-buffer)))))
(defun symbol-finder-diagnose ()
"Diagnose symbol-finder setup and keybindings."
(interactive)
(let ((global-binding (global-key-binding (kbd "M-.")))
(local-binding (local-key-binding (kbd "M-.")))
(mode-binding (and (boundp 'symbol-finder-mode-map)
(lookup-key symbol-finder-mode-map (kbd "M-.")))))
(message "=== Symbol-Finder Diagnostic ===")
(message "Symbol-finder-mode: %s" (if symbol-finder-mode "ON" "OFF"))
(message "Python script: %s" (if (file-exists-p symbol-finder-python-script)
"FOUND" "NOT FOUND"))
(message "M-. global binding: %s" global-binding)
(message "M-. local binding: %s" local-binding)
(message "M-. mode binding: %s" mode-binding)
(message "Active minor modes: %s" (mapcar 'car minor-mode-alist))
(when (eq global-binding 'xref-find-definitions)
(message "NOTE: M-. is bound to xref. You may want to use symbol-finder-setup-override-xref"))))
(defun symbol-finder-setup-override-xref ()
"Override xref M-. binding with symbol-finder in current buffer."
(interactive)
(local-set-key (kbd "M-.") 'symbol-finder-jump-to-definition)
(local-set-key (kbd "M-,") 'symbol-finder-pop-mark)
(local-set-key (kbd "M-?") 'symbol-finder-find-references)
(message "M-. now bound to symbol-finder-jump-to-definition in this buffer"))
(defun symbol-finder-pop-mark ()
"Pop back to previous position in marker ring."
(interactive)
(if (ring-empty-p symbol-finder--marker-ring)
(message "No previous position")
(let ((marker (ring-remove symbol-finder--marker-ring 0)))
(switch-to-buffer (marker-buffer marker))
(goto-char marker))))
(defun symbol-finder-update-file ()
"Update index for current file."
(interactive)
(when buffer-file-name
(message "Updating index for %s..." buffer-file-name)
(let* ((args (format "--index --force --root %s"
(shell-quote-argument (file-name-directory buffer-file-name))))
(output (symbol-finder--run-command args)))
(message "Index updated"))))
;; Auto-update on save
(defun symbol-finder--after-save-hook ()
"Hook to update index after saving."
(when (and buffer-file-name
(string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\|qml\\|js\\)$"
buffer-file-name))
(symbol-finder-update-file)))
(defcustom symbol-finder-auto-update-on-save nil
"Whether to automatically update index on file save."
:type 'boolean
:group 'symbol-finder
:set (lambda (symbol value)
(set-default symbol value)
(if value
(add-hook 'after-save-hook 'symbol-finder--after-save-hook)
(remove-hook 'after-save-hook 'symbol-finder--after-save-hook))))
;; Minor mode for keybindings
(defvar symbol-finder-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-.") 'symbol-finder-jump-to-definition)
(define-key map (kbd "M-?") 'symbol-finder-find-references)
(define-key map (kbd "M-,") 'symbol-finder-pop-mark)
(define-key map (kbd "C-c s f") 'symbol-finder-find-symbol)
(define-key map (kbd "C-c s i") 'symbol-finder-index)
(define-key map (kbd "C-c s u") 'symbol-finder-update-file)
map)
"Keymap for symbol-finder-mode.")
;;;###autoload
(define-minor-mode symbol-finder-mode
"Minor mode for symbol navigation using Python symbol finder."
:lighter " SymF"
:keymap symbol-finder-mode-map
:group 'symbol-finder
(when symbol-finder-mode
;; Debug message
(message "Symbol-finder-mode activated. M-. bound to: %s"
(lookup-key symbol-finder-mode-map (kbd "M-.")))
(when (and symbol-finder-auto-index
(not (file-exists-p
(expand-file-name symbol-finder-cache-directory
(symbol-finder--get-root)))))
(when (y-or-n-p "No symbol cache found. Index files now?")
(symbol-finder-index)))))
;;;###autoload
(define-globalized-minor-mode global-symbol-finder-mode
symbol-finder-mode
(lambda ()
(when (and (not (minibufferp))
(string-match-p "\\.\\(cpp\\|cc\\|cxx\\|c\\+\\+\\|hpp\\|h\\|hh\\|hxx\\|h\\+\\+\\|qml\\|js\\)$"
(or buffer-file-name "")))
(symbol-finder-mode 1))))
;; Compatibility with xref (optional)
(when (fboundp 'xref-make)
(defun symbol-finder-xref-backend ()
"Symbol finder backend for xref."
'symbol-finder)
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql symbol-finder)))
(thing-at-point 'symbol t))
(cl-defmethod xref-backend-definitions ((_backend (eql symbol-finder)) identifier)
(let* ((output (symbol-finder--run-command
(format "--definition %s --emacs" (shell-quote-argument identifier))))
(results (symbol-finder--parse-emacs-output output)))
(mapcar (lambda (r)
(xref-make (plist-get r :context)
(xref-make-file-location (plist-get r :file)
(plist-get r :line)
(1- (plist-get r :column)))))
results)))
(cl-defmethod xref-backend-references ((_backend (eql symbol-finder)) identifier)
(let* ((output (symbol-finder--run-command
(format "--references %s --emacs" (shell-quote-argument identifier))))
(results (symbol-finder--parse-emacs-output output)))
(mapcar (lambda (r)
(xref-make (plist-get r :context)
(xref-make-file-location (plist-get r :file)
(plist-get r :line)
(1- (plist-get r :column)))))
results)))
(add-hook 'symbol-finder-mode-hook
(lambda ()
(add-hook 'xref-backend-functions 'symbol-finder-xref-backend nil t))))
(provide 'symbol-finder)
;;; symbol-finder.el ends here

508
symbol_finder.py Executable file
View 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()