update emac

This commit is contained in:
2025-12-26 17:16:44 -05:00
parent 927e5f0e0c
commit c6be6f7027
3 changed files with 385 additions and 7 deletions

View File

@@ -23,13 +23,112 @@
(require 's) (require 's)
(require 'dash) (require 'dash)
(require 'popup) (require 'popup)
(require 'simpc-mode)
;; Automatically enabling simpc-mode on files with extensions like .h, .c, .cpp, .hpp
(add-to-list 'auto-mode-alist '("\\.[hc]\\(pp\\)?\\'" . simpc-mode))
;; ctags for xref, with dumb-jump as fallback ;; ctags for xref, with dumb-jump as fallback
(require 'dumb-jump) (require 'dumb-jump)
(setq dumb-jump-force-searcher 'grep) (setq dumb-jump-force-searcher 'grep)
(setq dumb-jump-selector 'ivy)
(setq xref-show-definitions-function #'xref-show-definitions-completing-read) (setq xref-show-definitions-function #'xref-show-definitions-completing-read)
(setq tags-revert-without-query t) (setq tags-revert-without-query t)
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate 100) (add-hook 'xref-backend-functions #'dumb-jump-xref-activate 100)
;; eglot LSP client
;; When eglot is active, xref commands (F12, M-f12) automatically use LSP
;; instead of CTAGS. Eglot registers itself as a higher-priority xref backend.
(require 'eglot)
(setq eglot-autoshutdown t) ; shutdown server when last buffer is closed
(setq eglot-confirm-server-initiated-edits nil) ; don't ask for confirmation on renames
;; disable LSP visual indicators but keep diagnostics available
(add-to-list 'eglot-ignored-server-capabilities :documentHighlightProvider)
;; auto-confirm "modified buffer" prompts when jumping from diagnostics
(defun my-auto-confirm-modified-buffer (orig-fun prompt)
"Auto-confirm prompts about modified buffers having wrong location."
(if (string-match-p "has been modified.*wrong location" prompt)
t
(funcall orig-fun prompt)))
(advice-add 'y-or-n-p :around #'my-auto-confirm-modified-buffer)
;; hide squiggly underlines
(set-face-attribute 'flymake-error nil :underline nil)
(set-face-attribute 'flymake-warning nil :underline nil)
(set-face-attribute 'flymake-note nil :underline nil)
;; hide fringe indicators
(setq flymake-fringe-indicator-position nil)
;; Go: auto-start eglot (requires gopls: go install golang.org/x/tools/gopls@latest)
(add-hook 'go-mode-hook 'eglot-ensure)
(global-set-key (kbd "<f2>") 'eglot-rename)
(global-set-key (kbd "C-S-m") 'my-flymake-show-project-errors-warnings)
(global-set-key (kbd "<S-f2>") 'eglot-reconnect)
(global-set-key (kbd "C-t") 'my-lsp-find-workspace-symbol)
(defun my-flymake-show-project-errors-warnings ()
"Show project diagnostics, filtering out notes (only errors and warnings)."
(interactive)
;; Kill existing diagnostics buffer to force refresh
(when-let ((buf (get-buffer "*Flymake diagnostics*")))
(kill-buffer buf))
;; Refresh flymake on all project buffers
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and (bound-and-true-p flymake-mode)
(buffer-file-name))
(flymake-start))))
(flymake-show-project-diagnostics)
(run-at-time 0.1 nil
(lambda ()
(when-let ((buf (get-buffer "*Flymake diagnostics*")))
(with-current-buffer buf
(let ((inhibit-read-only t))
(goto-char (point-min))
(while (not (eobp))
(if (looking-at ".*\\bnote\\b.*$")
(delete-region (line-beginning-position) (1+ (line-end-position)))
(forward-line 1)))))))))
(defun my-lsp-find-workspace-symbol ()
"Interactively search for symbols in workspace using LSP."
(interactive)
(if (eglot-managed-p)
(let ((server (eglot-current-server))
(root (project-root (project-current))))
(ivy-read "Symbol: "
(lambda (input)
(when (and input (>= (length input) 1))
(condition-case nil
(let* ((resp (jsonrpc-request server :workspace/symbol
`(:query ,input)))
(items (append resp nil)))
(delq nil
(mapcar (lambda (item)
(condition-case nil
(let* ((name (plist-get item :name))
(loc (plist-get item :location))
(uri (plist-get loc :uri))
(range (plist-get loc :range))
(start (plist-get range :start))
(line (1+ (plist-get start :line)))
(file (eglot-uri-to-path uri))
(rel-path (file-relative-name file root)))
(propertize (format "%s %s:%d" name rel-path line)
'file file
'line line))
(error nil)))
items)))
(error nil))))
:dynamic-collection t
:require-match t
:action (lambda (candidate)
(when (and candidate (get-text-property 0 'file candidate))
(find-file (get-text-property 0 'file candidate))
(goto-char (point-min))
(forward-line (1- (get-text-property 0 'line candidate)))))))
(call-interactively 'xref-find-apropos)))
;; vundo for visual undo tree ;; vundo for visual undo tree
(require 'vundo) (require 'vundo)
(ivy-mode 1) (ivy-mode 1)
@@ -61,6 +160,11 @@
(setq-local tab-width 4) (setq-local tab-width 4)
(setq-local stupid-indent-level 4))) (setq-local stupid-indent-level 4)))
(add-hook 'simpc-mode-hook (lambda ()
(setq-local indent-tabs-mode t)
(setq-local tab-width 4)
(setq-local stupid-indent-level 4)))
(add-hook 'python-mode-hook (lambda () (add-hook 'python-mode-hook (lambda ()
(setq-local indent-tabs-mode nil) (setq-local indent-tabs-mode nil)
(setq-local tab-width 4) (setq-local tab-width 4)
@@ -81,9 +185,10 @@
;; bottom panel settings (compilation, xref, etc.) ;; bottom panel settings (compilation, xref, etc.)
(setq compilation-scroll-output -1) (setq compilation-scroll-output -1)
(setq compilation-save-buffers-predicate 'ignore)
;; bottom panel buffer patterns ;; bottom panel buffer patterns
(defvar my-bottom-panel-buffers '("\\*compilation\\*" "\\*xref\\*") (defvar my-bottom-panel-buffers '("\\*compilation\\*" "\\*xref\\*" "\\*Flymake diagnostics.*\\*")
"List of buffer name patterns for bottom panel.") "List of buffer name patterns for bottom panel.")
(defun my-bottom-panel-buffer-p (buf) (defun my-bottom-panel-buffer-p (buf)
@@ -118,6 +223,8 @@
'("\\*compilation\\*" (my-display-in-bottom-panel) (window-height . 0.25))) '("\\*compilation\\*" (my-display-in-bottom-panel) (window-height . 0.25)))
(add-to-list 'display-buffer-alist (add-to-list 'display-buffer-alist
'("\\*xref\\*" (my-display-in-bottom-panel) (window-height . 0.25))) '("\\*xref\\*" (my-display-in-bottom-panel) (window-height . 0.25)))
(add-to-list 'display-buffer-alist
'("\\*Flymake diagnostics.*\\*" (my-display-in-bottom-panel) (window-height . 0.25)))
(defun my-bottom-panel-toggle () (defun my-bottom-panel-toggle ()
@@ -256,8 +363,16 @@
(global-set-key (kbd "C-p") 'project-find-file) (global-set-key (kbd "C-p") 'project-find-file)
(global-set-key (kbd "C-s") 'save-buffer) (global-set-key (kbd "C-s") 'save-buffer)
(global-set-key (kbd "<f3>") 'my-select-theme) (global-set-key (kbd "<f3>") 'my-select-theme)
(global-set-key (kbd "<f12>") 'xref-find-definitions) (global-set-key (kbd "C-<mouse-1>") 'my-xref-find-definitions-at-click)
(global-set-key (kbd "<C-S-f12>") 'xref-pop-marker-stack) (global-set-key (kbd "C-S-<mouse-1>") 'my-xref-find-references-at-click)
(global-set-key (kbd "<f12>") 'my-xref-find-definitions-same-pane)
(global-set-key (kbd "C-<f12>") 'xref-find-references)
(global-set-key (kbd "C-{") 'xref-go-back)
(global-set-key (kbd "C-}") 'xref-go-forward)
(global-set-key (kbd "C-]") 'stupid-indent)
(global-set-key (kbd "C-[") 'stupid-outdent)
(global-set-key (kbd "<mouse-3>") 'xref-go-back)
(global-set-key (kbd "<mouse-4>") 'xref-go-forward)
(global-set-key (kbd "C-q") 'save-buffers-kill-terminal) (global-set-key (kbd "C-q") 'save-buffers-kill-terminal)
(global-set-key (kbd "C-l") 'my-select-line) (global-set-key (kbd "C-l") 'my-select-line)
(global-set-key (kbd "C-e") 'my-copy-path-with-line) (global-set-key (kbd "C-e") 'my-copy-path-with-line)
@@ -265,7 +380,8 @@
(define-key isearch-mode-map (kbd "<return>") 'isearch-repeat-forward) (define-key isearch-mode-map (kbd "<return>") 'isearch-repeat-forward)
(define-key isearch-mode-map (kbd "S-<return>") 'isearch-repeat-backward) (define-key isearch-mode-map (kbd "S-<return>") 'isearch-repeat-backward)
(define-key isearch-mode-map (kbd "<backspace>") 'isearch-del-char) (define-key isearch-mode-map (kbd "<backspace>") 'isearch-del-char)
(define-key isearch-mode-map (kbd "<escape>") 'isearch-exit) (define-key isearch-mode-map (kbd "C-v") 'isearch-yank-kill)
(define-key isearch-mode-map (kbd "<escape>") 'my-isearch-cancel-or-exit)
(define-key isearch-mode-map (kbd "C-g") 'my-isearch-cancel-or-exit) (define-key isearch-mode-map (kbd "C-g") 'my-isearch-cancel-or-exit)
(defun my-isearch-cancel-or-exit () (defun my-isearch-cancel-or-exit ()
@@ -274,11 +390,69 @@
(if (string= isearch-string "") (if (string= isearch-string "")
(isearch-cancel) (isearch-cancel)
(isearch-exit))) (isearch-exit)))
(defun my-move-line-up ()
"Move current line or region up one line."
(interactive)
(if (use-region-p)
(my-move-region-up)
(let ((col (current-column)))
(transpose-lines 1)
(forward-line -2)
(move-to-column col))))
(defun my-move-line-down ()
"Move current line or region down one line."
(interactive)
(if (use-region-p)
(my-move-region-down)
(let ((col (current-column)))
(forward-line 1)
(transpose-lines 1)
(forward-line -1)
(move-to-column col))))
(defun my-move-region-up ()
"Move selected region up one line, keeping selection."
(let* ((rbeg (region-beginning))
(rend (region-end))
(beg (save-excursion (goto-char rbeg) (line-beginning-position)))
(end (save-excursion (goto-char rend) (if (bolp) (point) (1+ (line-end-position)))))
(offset-from-beg (- rbeg beg))
(region-len (- rend rbeg))
(text (delete-and-extract-region beg end)))
(forward-line -1)
(let ((new-beg (point)))
(insert text)
(set-mark (+ new-beg offset-from-beg))
(goto-char (+ new-beg offset-from-beg region-len))
(setq deactivate-mark nil))))
(defun my-move-region-down ()
"Move selected region down one line, keeping selection."
(let* ((rbeg (region-beginning))
(rend (region-end))
(beg (save-excursion (goto-char rbeg) (line-beginning-position)))
(end (save-excursion (goto-char rend) (if (bolp) (point) (1+ (line-end-position)))))
(offset-from-beg (- rbeg beg))
(region-len (- rend rbeg))
(lines (count-lines beg end))
(text (delete-and-extract-region beg end)))
(forward-line 1)
(let ((new-beg (point)))
(insert text)
(set-mark (+ new-beg offset-from-beg))
(goto-char (+ new-beg offset-from-beg region-len))
(setq deactivate-mark nil))))
(global-set-key (kbd "M-<up>") 'my-move-line-up)
(global-set-key (kbd "M-<down>") 'my-move-line-down)
(setq isearch-wrap-pause 'no) (setq isearch-wrap-pause 'no)
;; multiple cursors (vscode-style) ;; multiple cursors (vscode-style)
(setq mc/always-run-for-all t) (setq mc/always-run-for-all t)
(global-set-key (kbd "C-S-<mouse-1>") 'mc/add-cursor-on-click) (global-set-key (kbd "M-<down-mouse-1>") 'ignore)
(global-set-key (kbd "M-<mouse-1>") 'mc/add-cursor-on-click)
(global-set-key (kbd "C-M-<up>") (lambda () (interactive) (mc/mark-previous-lines 1))) (global-set-key (kbd "C-M-<up>") (lambda () (interactive) (mc/mark-previous-lines 1)))
(global-set-key (kbd "C-M-<down>") (lambda () (interactive) (mc/mark-next-lines 1))) (global-set-key (kbd "C-M-<down>") (lambda () (interactive) (mc/mark-next-lines 1)))
(define-key mc/keymap (kbd "<escape>") 'mc/keyboard-quit) (define-key mc/keymap (kbd "<escape>") 'mc/keyboard-quit)
@@ -312,6 +486,32 @@
(split-window-right) (split-window-right)
(other-window 1))))) (other-window 1)))))
(defun my-xref-find-definitions-same-pane ()
"Find definition and show it in the same pane.
Falls back to dumb-jump if xref fails."
(interactive)
(let ((identifier (thing-at-point 'symbol t)))
(if (null identifier)
(message "No symbol at point")
(condition-case nil
(let ((xrefs (xref-backend-definitions (xref-find-backend) identifier)))
(if xrefs
(xref-find-definitions identifier)
(dumb-jump-go)))
(error (dumb-jump-go))))))
(defun my-xref-find-definitions-at-click (event)
"Find definition of the symbol clicked on."
(interactive "e")
(mouse-set-point event)
(my-xref-find-definitions-same-pane))
(defun my-xref-find-references-at-click (event)
"Find references of the symbol clicked on."
(interactive "e")
(mouse-set-point event)
(xref-find-references (thing-at-point 'symbol t)))
;; custom bind minor mode ;; custom bind minor mode
;; this allows binding keys that override all other modes ;; this allows binding keys that override all other modes
(defvar my-keys-minor-mode-map (defvar my-keys-minor-mode-map
@@ -471,8 +671,6 @@
(visit-tags-table tags-path) (visit-tags-table tags-path)
(message "TAGS generated and saved: %s" tags-path))) (message "TAGS generated and saved: %s" tags-path)))
(global-set-key (kbd "M-<f12>") 'xref-find-references)
;; find and replace with modes ;; find and replace with modes
(defun my-find-replace () (defun my-find-replace ()
"Find and replace with mode selection: project, file, or selection." "Find and replace with mode selection: project, file, or selection."

127
.emacs.d/lisp/simpc-mode.el Normal file
View File

@@ -0,0 +1,127 @@
(require 'subr-x)
(defvar simpc-mode-syntax-table
(let ((table (make-syntax-table)))
;; C/C++ style comments
(modify-syntax-entry ?/ ". 124b" table)
(modify-syntax-entry ?* ". 23" table)
(modify-syntax-entry ?\n "> b" table)
;; Preprocessor stuff?
(modify-syntax-entry ?# "." table)
;; Chars are the same as strings
(modify-syntax-entry ?' "\"" table)
;; Treat <> as punctuation (needed to highlight C++ keywords
;; properly in template syntax)
(modify-syntax-entry ?< "." table)
(modify-syntax-entry ?> "." table)
(modify-syntax-entry ?& "." table)
(modify-syntax-entry ?% "." table)
table))
(defun simpc-types ()
'("char" "int" "long" "short" "void" "bool" "float" "double" "signed" "unsigned"
"char16_t" "char32_t" "char8_t"
"int8_t" "uint8_t" "int16_t" "uint16_t" "int32_t" "uint32_t" "int64_t" "uint64_t"
"uintptr_t"
"size_t"
"va_list"))
(defun simpc-keywords ()
'("auto" "break" "case" "const" "continue" "default" "do"
"else" "enum" "extern" "for" "goto" "if" "register"
"return" "sizeof" "static" "struct" "switch" "typedef"
"union" "volatile" "while" "alignas" "alignof" "and"
"and_eq" "asm" "atomic_cancel" "atomic_commit" "atomic_noexcept" "bitand"
"bitor" "catch" "class" "co_await"
"co_return" "co_yield" "compl" "concept" "const_cast" "consteval" "constexpr"
"constinit" "decltype" "delete" "dynamic_cast" "explicit" "export" "false"
"friend" "inline" "mutable" "namespace" "new" "noexcept" "not" "not_eq"
"nullptr" "operator" "or" "or_eq" "private" "protected" "public" "reflexpr"
"reinterpret_cast" "requires" "static_assert" "static_cast" "synchronized"
"template" "this" "thread_local" "throw" "true" "try" "typeid" "typename"
"using" "virtual" "wchar_t" "xor" "xor_eq"))
(defun simpc-font-lock-keywords ()
(list
`("# *\\(warn\\|error\\)" . font-lock-warning-face)
`("# *[#a-zA-Z0-9_]+" . font-lock-preprocessor-face)
`("# *include\\(?:_next\\)?\\s-+\\(\\(<\\|\"\\).*\\(>\\|\"\\)\\)" . (1 font-lock-string-face))
`("\\(?:enum\\|struct\\)\\s-+\\([a-zA-Z0-9_]+\\)" . (1 font-lock-type-face))
`(,(regexp-opt (simpc-keywords) 'symbols) . font-lock-keyword-face)
`(,(regexp-opt (simpc-types) 'symbols) . font-lock-type-face)))
(defun simpc--previous-non-empty-line ()
"Returns either NIL when there is no such line or a pair (line . indentation)"
(save-excursion
;; If you are on the first line, but not at the beginning of buffer (BOB) the `(bobp)`
;; function does not return `t`. So we have to move to the beginning of the line first.
;; TODO: feel free to suggest a better approach for checking BOB here.
(move-beginning-of-line nil)
(if (bobp)
;; If you are standing at the BOB, you by definition don't have a previous non-empty line.
nil
;; Moving one line backwards because the current line is by definition is not
;; the previous non-empty line.
(forward-line -1)
;; Keep moving backwards until we hit BOB or a non-empty line.
(while (and (not (bobp))
(string-empty-p
(string-trim-right
(thing-at-point 'line t))))
(forward-line -1))
(if (string-empty-p
(string-trim-right
(thing-at-point 'line t)))
;; If after moving backwards for this long we still look at an empty
;; line we by definition didn't find the previous non-empty line.
nil
;; We found the previous non-empty line!
(cons (thing-at-point 'line t)
(current-indentation))))))
(defun simpc--desired-indentation ()
(let ((prev (simpc--previous-non-empty-line)))
(if (not prev)
(current-indentation)
(let ((indent-len 4)
(cur-line (string-trim-right (thing-at-point 'line t)))
(prev-line (string-trim-right (car prev)))
(prev-indent (cdr prev)))
(cond
((string-match-p "^\\s-*switch\\s-*(.+)" prev-line)
prev-indent)
((and (string-suffix-p "{" prev-line)
(string-prefix-p "}" (string-trim-left cur-line)))
prev-indent)
((string-suffix-p "{" prev-line)
(+ prev-indent indent-len))
((string-prefix-p "}" (string-trim-left cur-line))
(max (- prev-indent indent-len) 0))
((string-suffix-p ":" prev-line)
(if (string-suffix-p ":" cur-line)
prev-indent
(+ prev-indent indent-len)))
((string-suffix-p ":" cur-line)
(max (- prev-indent indent-len) 0))
(t prev-indent))))))
;;; TODO: customizable indentation (amount of spaces, tabs, etc)
(defun simpc-indent-line ()
(interactive)
(when (not (bobp))
(let* ((desired-indentation
(simpc--desired-indentation))
(n (max (- (current-column) (current-indentation)) 0)))
(indent-line-to desired-indentation)
(forward-char n))))
(define-derived-mode simpc-mode prog-mode "Simple C"
"Simple major mode for editing C files."
:syntax-table simpc-mode-syntax-table
(setq-local font-lock-defaults '(simpc-font-lock-keywords))
(setq-local indent-line-function 'simpc-indent-line)
(setq-local comment-start "// "))
(provide 'simpc-mode)

53
sync.sh
View File

@@ -84,12 +84,56 @@ copy_file() {
fi fi
} }
sync_emacs_dir() {
local src_base="$1"
local dest_base="$2"
local action="$3"
# Files to sync
local files=("custom.el" "init.el")
# Directories to sync (cleared first)
local dirs=("lisp" "themes")
# Sync individual files
for file in "${files[@]}"; do
local src="$src_base/$file"
local dest="$dest_base/$file"
if [[ -f "$src" ]]; then
ensure_dir "$dest_base"
cp "$src" "$dest"
fi
done
# Sync directories (clear destination first)
for dir in "${dirs[@]}"; do
local src_dir="$src_base/$dir"
local dest_dir="$dest_base/$dir"
if [[ -d "$src_dir" ]]; then
# Clear destination directory
if [[ -d "$dest_dir" ]]; then
rm -rf "$dest_dir"
fi
# Copy directory
cp -r "$src_dir" "$dest_dir"
fi
done
}
push_dotfiles() { push_dotfiles() {
echo "Pushing dotfiles from repo to home directory..." echo "Pushing dotfiles from repo to home directory..."
# Handle .emacs.d specially (clear folders before syncing)
if [[ -d "$DOTFILES_DIR/.emacs.d" ]]; then
sync_emacs_dir "$DOTFILES_DIR/.emacs.d" "$HOME_DIR/.emacs.d" "Push"
fi
local count=0 local count=0
while IFS= read -r src_file; do while IFS= read -r src_file; do
rel_path="${src_file#$DOTFILES_DIR/}" rel_path="${src_file#$DOTFILES_DIR/}"
# Skip .emacs.d files (handled above)
if [[ "$rel_path" == .emacs.d/* ]]; then
continue
fi
dest_file="$(get_system_path "$rel_path")" dest_file="$(get_system_path "$rel_path")"
copy_file "$src_file" "$dest_file" "Push" copy_file "$src_file" "$dest_file" "Push"
((count++)) ((count++))
@@ -101,9 +145,18 @@ push_dotfiles() {
pull_dotfiles() { pull_dotfiles() {
echo "Pulling dotfiles from home directory to repo..." echo "Pulling dotfiles from home directory to repo..."
# Handle .emacs.d specially (clear folders before syncing)
if [[ -d "$HOME_DIR/.emacs.d" ]]; then
sync_emacs_dir "$HOME_DIR/.emacs.d" "$DOTFILES_DIR/.emacs.d" "Pull"
fi
local count=0 local count=0
while IFS= read -r dest_file; do while IFS= read -r dest_file; do
rel_path="${dest_file#$DOTFILES_DIR/}" rel_path="${dest_file#$DOTFILES_DIR/}"
# Skip .emacs.d files (handled above)
if [[ "$rel_path" == .emacs.d/* ]]; then
continue
fi
src_file="$(get_system_path "$rel_path")" src_file="$(get_system_path "$rel_path")"
copy_file "$src_file" "$dest_file" "Pull" copy_file "$src_file" "$dest_file" "Pull"
((count++)) ((count++))