175 lines
6.7 KiB
EmacsLisp
175 lines
6.7 KiB
EmacsLisp
;;; utils.el --- General utility functions -*- lexical-binding: t; -*-
|
|
|
|
(defun dired-follow-symlink ()
|
|
"In dired, visit the file or directory at point by its true physical path."
|
|
(interactive)
|
|
(let ((filename (dired-get-file-for-visit)))
|
|
(if filename
|
|
(find-file (file-truename filename))
|
|
(message "No file at point"))))
|
|
|
|
(defun my/delete-word-backward ()
|
|
"Delete the previous 'block' of characters without killing them.
|
|
Blocks are defined by: whitespace, alphanumerics, or groups of symbols."
|
|
(interactive)
|
|
(if (use-region-p)
|
|
(delete-region (region-beginning) (region-end))
|
|
(let ((end (point)))
|
|
(save-excursion
|
|
(cond
|
|
;; Case 1: Multiple spaces or tabs (but not a newline)
|
|
((looking-back "[ \t]+" 1)
|
|
(re-search-backward "[^ \t]" nil t)
|
|
(forward-char 1))
|
|
|
|
;; Case 2: Newlines
|
|
((looking-back "\n+" 1)
|
|
(re-search-backward "[^\n]" nil t)
|
|
(forward-char 1))
|
|
|
|
;; Case 3: Alphanumerics (Words)
|
|
((looking-back "[[:alnum:]]+" 1)
|
|
(re-search-backward "[^[:alnum:]]" nil t)
|
|
(forward-char 1))
|
|
|
|
;; Case 4: Special Characters (Symbols)
|
|
(t
|
|
(re-search-backward "[[:alnum:][:space:]]" nil t)
|
|
(forward-char 1)))
|
|
|
|
(delete-region (point) end)))))
|
|
|
|
(defun my-set-buffer-font ()
|
|
"Set a specific font for the current buffer only."
|
|
(buffer-face-set '(:family "Noto Sans" :height 140)))
|
|
|
|
(defun my/toggle-quote-wrap-all-in-region (beg end)
|
|
"Toggle wrapping all items in region with double quotes."
|
|
;;https://xenodium.com/emacs-quote-wrap-all-in-region
|
|
(interactive (list (mark) (point)))
|
|
(unless (region-active-p)
|
|
(user-error "no region to wrap"))
|
|
(let ((deactivate-mark nil)
|
|
(replacement (string-join
|
|
(mapcar (lambda (item)
|
|
(if (string-match-p "^\".*\"$" item)
|
|
(string-trim item "\"" "\"")
|
|
(format "\"%s\"" item)))
|
|
(split-string (buffer-substring beg end)))
|
|
" ")))
|
|
(delete-region beg end)
|
|
(insert replacement)))
|
|
|
|
(defun my/meow-wrap-arbitrary (s e)
|
|
"Wrap the current meow selection with arbitrary strings."
|
|
(interactive "r")
|
|
(let ((start-str (read-string "Start: "))
|
|
(end-str (read-string "End: ")))
|
|
(save-excursion
|
|
(goto-char e)
|
|
(insert end-str)
|
|
(goto-char s)
|
|
(insert start-str))))
|
|
|
|
(defun my/column-align-regexp ()
|
|
"Aligns the matched regexp to `comment-column`.
|
|
If the text preceding the regexp is already past `comment-column`,
|
|
it ensures exactly one space of separation."
|
|
(interactive)
|
|
(let* ((regexp (read-string "Align on regexp: "))
|
|
;; Use comment-column if set, otherwise default to 40
|
|
(target-col (if (and (boundp 'comment-column)
|
|
(numberp comment-column))
|
|
comment-column
|
|
40))
|
|
(start (region-beginning))
|
|
(end (region-end)))
|
|
|
|
(save-excursion
|
|
(save-restriction
|
|
(narrow-to-region start end)
|
|
(goto-char (point-min))
|
|
(while (re-search-forward regexp nil t)
|
|
(let* ((match-start (match-beginning 0))
|
|
(match-end (match-end 0)))
|
|
;; 1. Find the end of the actual content before the match
|
|
;; We move back from the match start and skip all whitespace
|
|
(save-excursion
|
|
(goto-char match-start)
|
|
(skip-chars-backward " \t")
|
|
(let ((content-end-pos (point))
|
|
(content-end-col (current-column)))
|
|
|
|
;; Delete the whitespace between the content and the match
|
|
(delete-region content-end-pos match-start)
|
|
|
|
;; Calculate how much space we need
|
|
(if (< content-end-col target-col)
|
|
(let ((num-spaces (- target-col content-end-col)))
|
|
(insert (make-string num-spaces ?\s)))
|
|
;; If content is already past the target, just insert one space
|
|
(insert " "))))
|
|
|
|
;; Move point to the end of the match to continue searching
|
|
(goto-char match-end)))))))
|
|
|
|
;; Optional: Bind it to a key for easier access
|
|
;; (global-set-key (kbd "C-c a l") 'my/column-align-regexp)
|
|
|
|
|
|
(defun diff-buffers (buffer-a buffer-b)
|
|
"Generate a diff between BUFFER-A and BUFFER-B using standard diff.
|
|
Prompts for the two buffers, defaulting to the current buffer and the
|
|
most recently active one."
|
|
(interactive
|
|
(let* ((current (current-buffer))
|
|
(other (other-buffer current t))
|
|
(buf-a (read-buffer "New buffer (to diff): " current t))
|
|
(buf-b (read-buffer "Old buffer (against): " other t)))
|
|
(list (get-buffer buf-a) (get-buffer buf-b))))
|
|
|
|
(let ((name-a (buffer-name buffer-a))
|
|
(name-b (buffer-name buffer-b)))
|
|
(diff buffer-b buffer-a nil t)
|
|
;; Rename the diff output buffer to make it clear what was diffed
|
|
(when (get-buffer "*Diff*")
|
|
(with-current-buffer "*Diff*"
|
|
(rename-buffer (format "*Diff: %s vs %s*" name-b name-a) t)))))
|
|
|
|
(defun my/list-no-prefix-bindings (keymap &optional map-name)
|
|
"Extract and display all single-key (no-prefix) bindings from KEYMAP.
|
|
Outputs a clean list into a dedicated buffer."
|
|
(interactive (list global-map "global-map"))
|
|
(let ((buf (get-buffer-create "*No-Prefix Bindings*"))
|
|
(bindings '()))
|
|
(cl-labels ((map-extract (map)
|
|
(map-keymap
|
|
(lambda (event binding)
|
|
(cond
|
|
;; Skip nested/prefix keymaps entirely
|
|
((keymapp binding) nil)
|
|
;; Handle standard events (symbols or characters)
|
|
((or (symbolp event) (integerp event))
|
|
(let ((key-str (single-key-description event)))
|
|
;; Filter for simple keys (no spaces, meaning no multi-key sequences)
|
|
(unless (string-match-p " " key-str)
|
|
(push (cons key-str binding) bindings))))))
|
|
map)))
|
|
(map-extract keymap)
|
|
;; Sort logically by key description
|
|
;; Print the results cleanly
|
|
(with-current-buffer buf
|
|
(text-mode)
|
|
(setq buffer-read-only nil)
|
|
(erase-buffer)
|
|
(insert (format "No-Prefix Bindings for: %s\n" (or map-name "Selected Map")))
|
|
(insert "==================================================\n\n")
|
|
(dolist (b bindings)
|
|
(insert (format "%-15s -> %s\n" (car b) (cdr b))))
|
|
(setq buffer-read-only t))
|
|
(pop-to-buffer buf))))
|
|
|
|
(provide 'utils)
|
|
|
|
|