about summary refs log tree commit diff
path: root/users/tazjin/emacs/config/functions.el
diff options
context:
space:
mode:
Diffstat (limited to 'users/tazjin/emacs/config/functions.el')
-rw-r--r--users/tazjin/emacs/config/functions.el178
1 files changed, 88 insertions, 90 deletions
diff --git a/users/tazjin/emacs/config/functions.el b/users/tazjin/emacs/config/functions.el
index e725c23ccf..68a384d20f 100644
--- a/users/tazjin/emacs/config/functions.el
+++ b/users/tazjin/emacs/config/functions.el
@@ -2,9 +2,7 @@
 (require 'dash)
 (require 'map)
 
-(defun load-file-if-exists (filename)
-  (if (file-exists-p filename)
-      (load filename)))
+(require 'gio-list-apps) ;; native module!
 
 (defun goto-line-with-feedback ()
   "Show line numbers temporarily, while prompting for the line number input"
@@ -17,24 +15,19 @@
           (goto-line target)))
     (setq-local display-line-numbers nil)))
 
-;; These come from the emacs starter kit
-
 (defun esk-add-watchwords ()
   (font-lock-add-keywords
    nil '(("\\<\\(FIX\\(ME\\)?\\|TODO\\|DEBUG\\|HACK\\|REFACTOR\\|NOCOMMIT\\)"
           1 font-lock-warning-face t))))
 
+(add-hook 'prog-mode-hook 'esk-add-watchwords)
+
 (defun esk-sudo-edit (&optional arg)
   (interactive "p")
   (if (or arg (not buffer-file-name))
       (find-file (concat "/sudo:root@localhost:" (read-file-name "File: ")))
     (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))
 
-;; Open the NixOS man page
-(defun nixos-man ()
-  (interactive)
-  (man "configuration.nix"))
-
 ;; Get the nix store path for a given derivation.
 ;; If the derivation has not been built before, this will trigger a build.
 (defun nix-store-path (derivation)
@@ -114,7 +107,9 @@ the GPG agent correctly."
                    nil ;; predicate
                    t   ;; require-match
                    ))
-         (password (auth-source-pass-get 'secret entry)))
+         (password (or (let ((epa-suppress-error-buffer t))
+                         (auth-source-pass-get 'secret entry))
+                       (error "failed to decrypt '%s', wrong password?" entry))))
     (password-store-clear)
     (kill-new password)
     (setq password-store-kill-ring-pointer kill-ring-yank-pointer)
@@ -124,23 +119,6 @@ the GPG agent correctly."
           (run-at-time (password-store-timeout)
                        nil 'password-store-clear))))
 
-(defun browse-repositories ()
-  "Select a git repository and open its associated magit buffer."
-
-  (interactive)
-  (magit-status
-   (completing-read "Repository: " (magit-list-repos))))
-
-(defun bottom-right-window-p ()
-  "Determines whether the last (i.e. bottom-right) window of the
-  active frame is showing the buffer in which this function is
-  executed."
-  (let* ((frame (selected-frame))
-         (right-windows (window-at-side-list frame 'right))
-         (bottom-windows (window-at-side-list frame 'bottom))
-         (last-window (car (seq-intersection right-windows bottom-windows))))
-    (eq (current-buffer) (window-buffer last-window))))
-
 (defhydra mc/mark-more-hydra (:color pink)
   ("<up>" mc/mmlte--up "Mark previous like this")
   ("<down>" mc/mmlte--down "Mark next like this")
@@ -178,27 +156,6 @@ the GPG agent correctly."
                             mc/mark-more-hydra/mmlte--up
                             mc/mark-more-hydra/nil))
 
-(defun memespace-region ()
-  "Make a meme out of it."
-
-  (interactive)
-  (let* ((start (region-beginning))
-         (end (region-end))
-         (memed
-          (message
-           (s-trim-right
-            (apply #'string
-                   (-flatten
-                    (nreverse
-                     (-reduce-from (lambda (acc x)
-                                     (cons (cons x (-repeat (+ 1 (length acc)) 32)) acc))
-                                   '()
-                                   (string-to-list (buffer-substring-no-properties start end))))))))))
-
-    (save-excursion (delete-region start end)
-                    (goto-char start)
-                    (insert memed))))
-
 (defun insert-todo-comment (prefix todo)
   "Insert a comment at point with something for me to do."
 
@@ -241,11 +198,16 @@ the GPG agent correctly."
   (if prefix (text-scale-adjust 0)
     (set-face-attribute 'default nil :height (or to 120))))
 
-(defun scrot-select ()
+(defun screenshot-select (filename)
   "Take a screenshot based on a mouse-selection and save it to
   ~/screenshots."
-  (interactive)
-  (shell-command "scrot '$a_%Y-%m-%d_%s.png' -s -e 'mv $f ~/screenshots/'"))
+  (interactive "sScreenshot filename: ")
+  (let* ((path (f-join "~/screenshots"
+                       (format "%s-%d.png"
+                               (if (string-empty-p filename) "shot" filename)
+                               (time-convert nil 'integer)))))
+    (shell-command (format "maim --select %s" path))
+    (message "Wrote screenshot to %s" path)))
 
 (defun graph-unread-mails ()
   "Create a bar chart of unread mails based on notmuch tags.
@@ -293,6 +255,17 @@ the GPG agent correctly."
 
 (add-to-list 'project-find-functions #'find-depot-project)
 
+(defun find-cargo-project (dir)
+  "Attempt to find the current project in `project-find-functions'
+by looking for a `Cargo.toml' file."
+  (when dir
+    (unless (equal "/" dir)
+      (if (f-exists-p (f-join dir "Cargo.toml"))
+          (cons 'transient dir)
+        (find-cargo-project (f-parent dir))))))
+
+(add-to-list 'project-find-functions #'find-cargo-project)
+
 (defun magit-find-file-worktree ()
   (interactive)
   "Find a file in the current (ma)git worktree."
@@ -300,45 +273,14 @@ the GPG agent correctly."
                              (magit-read-file-from-rev "HEAD" "Find file")
                              #'pop-to-buffer-same-window))
 
-(defun songwhip--handle-result (status &optional cbargs)
-  ;; TODO(tazjin): Inspect status, which looks different in practice
-  ;; than the manual claims.
-  (if-let* ((response (json-parse-string
-                       (buffer-substring url-http-end-of-headers (point-max))))
-            (sw-path (ht-get* response "data" "path"))
-            (link (format "https://songwhip.com/%s" sw-path))
-            (select-enable-clipboard t))
-      (progn
-        (kill-new link)
-        (message "Copied Songwhip link (%s)" link))
-    (warn "Something went wrong while retrieving Songwhip link!")
-    ;; For debug purposes, the buffer is persisted in this case.
-    (setq songwhip--debug-buffer (current-buffer))))
-
-(defun songwhip-lookup-url (url)
-  "Look up URL on Songwhip and copy the resulting link to the clipboard."
-  (interactive "sEnter source URL: ")
-  (let ((songwhip-url "https://songwhip.com/api/")
-        (url-request-method "POST")
-        (url-request-extra-headers '(("Content-Type" . "application/json")))
-        (url-request-data
-         (json-serialize `((country . "GB")
-                           (url . ,url)))))
-    (url-retrieve "https://songwhip.com/api/" #'songwhip--handle-result nil t t)
-    (message "Requesting Songwhip URL ... please hold the line.")))
-
-(defun rg-in-project (&optional prefix)
-  "Interactively call ripgrep in the current project, or fall
-  back to ripgrep default behaviour if prefix is set."
-  (interactive "P")
-  (counsel-rg nil (unless prefix
-                    (if-let ((pr (project-current)))
-                        (project-root pr)))))
-
-(defun zoxide-open-magit ()
-  "Query Zoxide for paths and open magit in the result."
+(defun zoxide-open-project ()
+  "Query Zoxide for paths, and open the result as appropriate (magit or dired)."
   (interactive)
-  (zoxide-open-with nil #'magit-status-setup-buffer))
+  (zoxide-open-with
+   nil
+   (lambda (path)
+     (condition-case err (magit-status-setup-buffer path)
+       (magit-outside-git-repo (dired path))))))
 
 (defun toggle-nix-test-and-exp ()
   "Switch between the .nix and .exp file in a Tvix/Nix test."
@@ -351,4 +293,60 @@ the GPG agent correctly."
                     (error "Not a .nix/.exp file!")))))
     (find-file other)))
 
+(defun reliably-switch-buffer ()
+  "Reliably and interactively switch buffers, without ending up in a
+situation where the buffer was renamed during selection and an
+empty new buffer is created.
+
+This is done by, in contrast to most buffer-switching functions,
+retaining a list of the buffer *objects* and their associated
+names, instead of only their names (which might change)."
+
+  (interactive)
+  (let* ((buffers (seq-map (lambda (b) (cons (buffer-name b) b))
+                           (seq-filter (lambda (b) (not (string-prefix-p " " (buffer-name b))))
+                                       (buffer-list))))
+
+         ;; Annotate buffers that display remote files. I frequently
+         ;; want to see it, because I might have identically named
+         ;; files open locally and remotely at the same time, and it
+         ;; helps with differentiating them.
+         (completion-extra-properties
+          '(:annotation-function
+            (lambda (name)
+              (if-let* ((file (buffer-file-name (cdr (assoc name buffers))))
+                        (remote (file-remote-p file)))
+                  (format " [%s]" remote)))))
+
+         (name (completing-read "Switch to buffer: " (seq-map #'car buffers)))
+         (selected (or (cdr (assoc name buffers))
+                       ;; Allow users to manually select invisible buffers ...
+                       (get-buffer name))))
+    (switch-to-buffer (or selected name) nil 't)))
+
+(defun run-xdg-app ()
+  "Use `//users/tazjin/gio-list-apps' to retrieve a list of
+installed (and visible) XDG apps, and let users launch them."
+  (interactive)
+  (let* ((apps (taz-list-xdg-apps))
+
+         ;; Display the command that will be run as an annotation
+         (completion-extra-properties
+          '(:annotation-function (lambda (app) (format " [%s]" (cdr (assoc app apps)))))))
+
+    (run-external-command--handler (cdr (assoc (completing-read "App: " apps nil t) apps)))))
+
+(defun advice-remove-all (sym)
+  "Remove all advices from symbol SYM."
+  (interactive "aFunction symbol: ")
+  (advice-mapc (lambda (advice _props) (advice-remove sym advice)) sym))
+
+(defun M-x-always-same-window ()
+  "Run `execute-extended-command', but ensure that whatever it does
+always opens in the same window in which the command was invoked."
+  (interactive)
+  (let ((display-buffer-overriding-action
+         '((display-buffer-same-window) . ((inhibit-same-window . nil)))))
+    (call-interactively #'execute-extended-command)))
+
 (provide 'functions)