Commit c50f57f05fbc

Vincent Demeester <vincent@sbr.pm>
2025-05-20 18:54:11
tools/emacs: add template and site-lisp to mini
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent db47538
tools/emacs/mini/site-lisp/init-func.el
@@ -0,0 +1,19 @@
+;;; init-func.el --- -*- lexical-binding: t -*-
+;;
+
+;;;###autoload
+(defun vde/el-load-dir (dir)
+  "Load el files from the given folder `DIR'."
+  (let ((files (directory-files dir nil "\.el$")))
+    (while files
+      (load-file (concat dir (pop files))))))
+
+;;;###autoload
+(defun vde/short-hostname ()
+  "Return hostname in short (aka wakasu.local -> wakasu)."
+  (string-match "[0-9A-Za-z-]+" system-name)
+  (substring system-name (match-beginning 0) (match-end 0)))
+
+
+(provide 'init-func)
+;;; init-func.el ends here
tools/emacs/mini/site-lisp/passage.el
@@ -0,0 +1,14 @@
+;;; passage.el --- -*- lexical-binding: t -*-
+;;; Commentary:
+;; Code:
+(defvar passage-program "passage"
+  "The path to the `passage` executable.")
+
+;;;###autoload
+(defun passage-get (password-name)
+  "Return the password for PASSWORD-NAME from `passage show`."
+  (let ((password (shell-command-to-string (concat passage-program " show " (shell-quote-argument password-name)))))
+    (string-trim password))) ; Trim whitespace here
+
+(provide 'passage)
+;;; passage.el ends here
tools/emacs/mini/site-lisp/project-func.el
@@ -0,0 +1,156 @@
+;;; project-func.el --- -*- lexical-binding: t -*-
+;;; Commentary:
+;;; Code:
+(require 'project)
+(require 'vterm)
+(require 'json)
+(require 'vc)
+
+(defun in-git-repo-p ()
+  "Check if current directory is in a git repository."
+  (eq (vc-backend (or buffer-file-name default-directory))
+      'Git))
+
+(defun is-github-repo-p ()
+  "Check if current git repository has a GitHub remote."
+  (when (in-git-repo-p)
+    (string-match-p "github\\.com"
+                    (shell-command-to-string "git remote -v"))))
+
+(defun fetch-github-prs ()
+  "Fetch GitHub PRs synchronously."
+  (let* ((output (shell-command-to-string "gh pr list --limit=5000 --json number,title,author,url,baseRefName,labels"))
+         (prs (json-read-from-string output)))
+    prs))
+
+(defun format-pr-candidates (prs)
+  "Format PR data into candidates for completion."
+  (mapcar (lambda (pr)
+            (let-alist pr
+              (cons (format "#%d %s (by @%s) on %s" .number .title .author.login .baseRefName)
+                    .number)))
+          prs))
+
+;;;###autoload
+(defun checkout-github-pr ()
+  "Interactive function to select and checkout a GitHub PR."
+  (interactive)
+  (cond
+   ((not (in-git-repo-p))
+    (message "Not in a Git repository"))
+   ((not (is-github-repo-p))
+    (message "Not a GitHub repository"))
+   (t
+    (let* ((prs (fetch-github-prs))
+           (candidates (format-pr-candidates prs))
+           (selected (if candidates
+                         (cdr (assoc (completing-read "Checkout PR: " candidates)
+                                     candidates))
+                       nil)))
+      (if selected
+          (shell-command (format "gh pr checkout %d" selected))
+        (message "No pull requests found"))))))
+
+;;;###autoload
+(defun vde-project--project-current ()
+  "Return directory from `project-current' based on Emacs version."
+  (if (>= emacs-major-version 29)
+      (project-root (project-current))
+    (cdr (project-current))))
+
+;;;###autoload
+(defun vde-project--project-root-or-default-directory ()
+  "Return path to the project root *or* the default-directory."
+  (cond
+   ((and (featurep 'project) (project-current))
+    (project-root (project-current)))
+   (t default-directory)))
+
+;;;##autoload
+(defun vde/project-run-in-vterm (command &optional directory)
+  "Run the given `COMMAND' in a new vterm buffer in `project-root' or the
+given `DIRECTORY'.
+
+This is similar to `compile' but with vterm.
+One reason for this is to be able to run commands that needs a TTY."
+  (interactive "sCommand: ")
+  (let* ((cwd (or directory (vde-project--project-root-or-default-directory)))
+	 (default-directory cwd)
+	 (buffer-name (format "*vterm %s: %s*" cwd command))
+         (buffer (get-buffer buffer-name))
+         (vterm-kill-buffer-on-exit nil)
+	 (vterm-shell (concat "bash -c '" command ";exit'")))
+    (when buffer
+      (kill-buffer buffer))
+    (let ((buffer (generate-new-buffer buffer-name)))
+      (pop-to-buffer buffer)
+      (with-current-buffer buffer
+        (vterm-mode)))))
+
+;;;###autoload
+(defun vde/open-readme ()
+  "Open a README file in the current project.
+It will search for README.org, README.md or README in that order"
+  (interactive)
+  (let* ((default-directory (vde-project--project-current)))
+    (cond ((file-exists-p (expand-file-name "README.org" default-directory))
+	   (find-file "README.org"))
+	  ((file-exists-p (expand-file-name "README.md" default-directory))
+	   (find-file "README.md"))
+	  ((file-exists-p (expand-file-name "README" default-directory))
+	   (find-file "README")))))
+
+;;;###autoload
+(defun vde/project-try-local (dir)
+  "Determine if DIR is a non-VC project."
+  (if-let ((root (if (listp vde/project-local-identifier)
+                     (seq-some (lambda (n)
+                                 (locate-dominating-file dir n))
+                               vde/project-local-identifier)
+                   (locate-dominating-file dir vde/project-local-identifier))))
+      (cons 'local root)))
+
+;;;###autoload
+(defun vde/project-vterm (&optional command)
+  "Run `vterm' on project.
+If a buffer already exists for running a vterm shell in the project's root,
+switch to it. Otherwise, create a new vterm shell."
+  (interactive)
+  (let* ((default-directory (vde-project--project-current))
+         (default-project-vterm-name (project-prefixed-buffer-name "vterm"))
+         (vterm-buffer (get-buffer default-project-vterm-name)))
+    (if (and vterm-buffer (not current-prefix-arg))
+        (pop-to-buffer-same-window vterm-buffer)
+      (let* ((cd-cmd (concat " cd " (shell-quote-argument default-directory))))
+        (vterm default-project-vterm-name)
+        (with-current-buffer vterm-buffer
+          (vterm-send-string cd-cmd)
+          (vterm-send-return))))
+    (when command
+      (vterm-send-string command)
+      (vterm-send-return))))
+
+;;;###autoload
+(defun vde/project-eat ()
+  "Run Eat term in the current project's root directory.
+If a buffer already exists for running Eshell in the project's root,
+switch to it.  Otherwise, create a new Eshell buffer.
+With \\[universal-argument] prefix arg, create a new Eshell buffer even
+if one already exists."
+  (interactive)
+  (defvar eat-buffer-name)
+  (let* ((default-directory (project-root (project-current t)))
+	 (eat-buffer-name (project-prefixed-buffer-name "eat"))
+	 (eat-buffer (get-buffer eat-buffer-name)))
+    (if (and eat-buffer (not current-prefix-arg))
+	(pop-to-buffer eat-buffer (bound-and-true-p display-comint-buffer-action))
+      (eat shell-file-name))))
+
+;;;###autoload
+(defun vde/project-magit-status ()
+  "Run `magit-status' on project."
+  (interactive)
+  (magit-status (vde-project--project-current)))
+
+(provide 'project-func)
+;;; project-func.el ends here
tools/emacs/mini/site-lisp/vde-buffers.el
@@ -0,0 +1,11 @@
+;;; vde-buffers.el --- -*- lexical-binding: t; -*-
+;; Commentary:
+;;; Helper function related to buffers
+;; Code:
+
+;;;###autoload
+(defun vde/buffer-has-project-p (buffer action)
+  (with-current-buffer buffer (project-current nil)))
+
+(provide 'vde-buffers)
+;;; vde-buffers.el ends here
tools/emacs/mini/site-lisp/vde-windows.el
@@ -0,0 +1,35 @@
+;;; vde-windows.el --- -*- lexical-binding: t; -*-
+;; Commentary:
+;;; Helper function related to window management
+;; Code:
+
+;;;###autoload
+(defun vde/split-window-below (&optional arg)
+  "Split window below from the parent or from the roo with ARG."
+  (interactive "P")
+  (split-window (if arg (frame-root-window)
+		 (window-parent (selected-window)))
+	       nil 'below nil))
+
+;;;###autoload
+(defun vde/split-window-right (&optional arg)
+  "Split window right from the parent or from the roo with ARG."
+  (interactive "P")
+  (split-window (if arg (frame-root-window)
+		 (window-parent (selected-window)))
+		nil 'right nil))
+
+;;;###autoload
+(defun vde/toggle-window-dedication ()
+  "Toggles window dedication in the selected window."
+  (interactive)
+  (set-window-dedicated-p (selected-window)
+			  (not (window-dedicated-p (selected-window)))))
+
+;;;###autoload
+(defun make-display-buffer-matcher-function (major-modes)
+  (lambda (buffer-name action)
+    (with-current-buffer buffer-name (apply #'derived-mode-p major-modes))))
+
+(provide 'vde-windows)
+;;; vde-windows.el ends here
tools/emacs/mini/templates
@@ -0,0 +1,156 @@
+;; ~/.config/emacs/templates
+
+fundamental-mode ;; Available everywhere
+
+(today (format-time-string "%Y-%m-%d"))
+
+prog-mode
+
+(fixme (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "FIXME ")
+(todo (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "TODO ")
+(bug (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "BUG ")
+(hack (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "HACK ")
+
+lisp-mode emacs-lisp-mode ;; Specify multiple modes
+
+(lambda "(lambda (" p ")" n> r> ")")
+
+emacs-lisp-mode
+
+(autoload ";;;###autoload")
+(pt "(point)")
+(var "(defvar " p "\n  \"" p "\")")
+(local "(defvar-local " p "\n  \"" p "\")")
+(const "(defconst " p "\n  \"" p "\")")
+(custom "(defcustom " p "\n  \"" p "\"" n> ":type '" p ")")
+(face "(defface " p " '((t :inherit " p "))\n  \"" p "\")")
+(group "(defgroup " p " nil\n  \"" p "\"" n> ":group '" p n> ":prefix \"" p "-\")")
+(macro "(defmacro " p " (" p ")\n  \"" p "\"" n> r> ")")
+(alias "(defalias '" p " '" p ")")
+(fun "(defun " p " (" p ")\n  \"" p "\"" n> r> ")")
+(iflet "(if-let (" p ")" n> r> ")")
+(whenlet "(when-let (" p ")" n> r> ")")
+(whilelet "(while-let (" p ")" n> r> ")")
+(andlet "(and-let* (" p ")" n> r> ")")
+(cond "(cond" n "(" q "))" >)
+(pcase "(pcase " (p "scrutinee") n "(" q "))" >)
+(let "(let (" p ")" n> r> ")")
+(lett "(let* (" p ")" n> r> ")")
+(pcaselet "(pcase-let (" p ")" n> r> ")")
+(pcaselett "(pcase-let* (" p ")" n> r> ")")
+(rec "(letrec (" p ")" n> r> ")")
+(dotimes "(dotimes (" p ")" n> r> ")")
+(dolist "(dolist (" p ")" n> r> ")")
+(loop "(cl-loop for " p " in " p " do" n> r> ")")
+(command "(defun " p " (" p ")\n  \"" p "\"" n> "(interactive" p ")" n> r> ")")
+(advice "(defun " (p "adv" name) " (&rest app)" n> p n> "(apply app))" n>
+        "(advice-add #'" (p "fun") " " (p ":around") " #'" (s name) ")")
+(header ";;; " (file-name-nondirectory (or (buffer-file-name) (buffer-name)))
+        " -- " p " -*- lexical-binding: t -*-" n
+        ";;; Commentary:" n ";;; Code:" n n)
+(provide "(provide '" (file-name-base (or (buffer-file-name) (buffer-name))) ")" n
+         ";;; " (file-name-nondirectory (or (buffer-file-name) (buffer-name)))
+         " ends here" n)
+(package ";;; " (file-name-base (or (buffer-file-name) (buffer-name))) " --- " p " -*- lexical-binding: t -*-"
+   n n
+   ";; Copyright (C) " (format-time-string "%Y") " Vincent Demeester" n
+   ";; Author: Vincent Demeester <vincent@sbr.pm>" n n
+   ";; This file is NOT part of GNU Emacs." n
+   ";;; Commentary:" n p n
+   ";;; Code:" n p n
+   "(provide '" (file-name-base (or (buffer-file-name) (buffer-name))) ")" n
+   ";;; " (file-name-nondirectory (or (buffer-file-name) (buffer-name))) " ends here" n)
+
+eshell-mode
+
+(for "for " (p "i") " in " p " { " q " }")
+(while "while { " p " } { " q " }")
+(until "until { " p " } { " q " }")
+(if "if { " p " } { " q " }")
+(ife "if { " p " } { " p " } { " q " }")
+(unl "unless { " p " } { " q " }")
+(unle "unless { " p " } { " p " } { " q " }")
+
+text-mode
+
+(box "┌─" (make-string (length str) ?─) "─┐" n
+     "│ " (s str)                       " │" n
+     "└─" (make-string (length str) ?─) "─┘" n)
+(abox "+-" (make-string (length str) ?-) "-+" n
+      "| " (s str)                       " |" n
+      "+-" (make-string (length str) ?-) "-+" n)
+(cut "--8<---------------cut here---------------start------------->8---" n r n
+     "--8<---------------cut here---------------end--------------->8---" n)
+(rot13 (p "plain text" text) n "----" n (rot13 text))
+(calc (p "taylor(sin(x),x=0,3)" formula) n "----" n (format "%s" (calc-eval formula)))
+
+org-mode
+
+(caption "#+caption: ")
+(drawer ":" p ":" n r ":end:")
+(begin "#+begin_" (s name) n> r> n "#+end_" name)
+(quote "#+begin_quote" n> r> n "#+end_quote")
+(sidenote "#+begin_sidenote" n> r> n "#+end_sidenote")
+(marginnote "#+begin_marginnote" n> r> n "#+end_marginnote")
+(example "#+begin_example" n> r> n "#+end_example")
+(center "#+begin_center" n> r> n "#+end_center")
+(ascii "#+begin_export ascii" n> r> n "#+end_export")
+(html "#+begin_export html" n> r> n "#+end_export")
+(latex "#+begin_export latex" n> r> n "#+end_export")
+(comment "#+begin_comment" n> r> n "#+end_comment")
+(verse "#+begin_verse" n> r> n "#+end_verse")
+(src "#+begin_src " q n r n "#+end_src")
+(gnuplot "#+begin_src gnuplot :var data=" (p "table") " :file " (p "plot.png") n r n "#+end_src" :post (org-edit-src-code))
+(elisp "#+begin_src emacs-lisp" n r n "#+end_src" :post (org-edit-src-code))
+(inlsrc "src_" p "{" q "}")
+(title "#+title: " p n "#+author: Daniel Mendler" n "#+language: en")
+(category "#+category: " (vde/org-category-from-buffer))
+(call & "#+call: " (p "name" name) "(" (s var) "=\"" (s value) "\")")
+(/ "[/]" (org-update-statistics-cookies nil))
+(tsm p " :: " (with-temp-buffer (org-insert-time-stamp (current-time) t t)))
+(pdeadline "#+PROJECT_DEADLINE: " (org-read-date))
+(ppath "#+PROJECT_PATHS: (" p " . " q ")")
+(backlinks "#+BEGIN: denote-backlinks :sort-by-component nil :reverse-sort nil :id-only nil" n> "#+END:")
+
+bash-ts-mode bash-mode
+
+(! & "#!/usr/bin/env bash" n "set -euxfo pipefail" n q)
+(setf "set -euxfo pipefail")
+(varu "${" p ":-" p "}")
+(var "${" p "}")
+(getargs "while getopts \"h" p ":\" o; do"
+ n> "case \"${o}\" in"
+ n> ""p")"
+ n> "s=${OPTARG}" q
+ n> ";;"
+ n> "h)"
+ n> "help"
+ n> "exit 0"
+ n> ";;"
+ n> "*)"
+ n> "echo \"Invalid option\"; help; exit 1;"
+ n> "help"
+ n> "exit 1"
+ n> ";;"
+ n> "esac"
+ n> "done"
+ n> "shift $((OPTIND-1))")
+(whiche "command -v " (s bin) " >/dev/null 2>&1 || { echo \"need " bin " in path\"; exit 1; }")
+(tmpf "TMP=$(mktemp /tmp/.mm.XXXXXX)"
+ n> "clean() { rm -f ${TMP}; }"
+ n> "trap clean EXIT"
+ n> q)
+
+go-ts-mode go-mode
+
+(fpln "fmt.Println(" p ")")
+(fpf "fmt.Printf(" p ", " p ")")
+(main "package main" n n "import (" n> "\"fmt\"" n ")" n n "func main() {" n> q n "}")
+(testcase "tests := []struct {" n> "name string" n> p "}{" n> "}" n> "for _, tt := range tests {" n> "t.Run(tt.name, func(t *testing.T) {" n> q n> "})" n> "}")
+(testfunc "func Test" (s name) "(t *testing.T) {" n> q n "}")
+
+
+;; Local Variables:
+;; mode: lisp-data
+;; outline-regexp: "[a-z]"
+;; End: