diff options
Diffstat (limited to '.config/emacs')
| -rw-r--r-- | .config/emacs/early-init.el | 9 | ||||
| -rw-r--r-- | .config/emacs/init.el | 41 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-abbrev.el | 8 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-editing.el | 66 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-humanwave.el | 141 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-modeline.el | 2 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-projects.el | 29 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-theme.el | 20 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-treesit.el | 22 | ||||
| -rw-r--r-- | .config/emacs/site-lisp/gh.el | 59 | ||||
| -rw-r--r-- | .config/emacs/site-lisp/highlighter.el | 128 | ||||
| -rw-r--r-- | .config/emacs/site-lisp/live-jq.el | 101 | ||||
| -rw-r--r-- | .config/emacs/templates | 10 |
13 files changed, 594 insertions, 42 deletions
diff --git a/.config/emacs/early-init.el b/.config/emacs/early-init.el index b48d643..0dfbfa6 100644 --- a/.config/emacs/early-init.el +++ b/.config/emacs/early-init.el @@ -76,17 +76,14 @@ ;; set threshold to 8 MiB which seems to be a good middleground for now. ;; A higher threshold means less garbage collections but I’ve had issues ;; with those garbage collections causing long freezes when they occur. -(let ((saved-file-name-handler-alist file-name-handler-alist) - (saved-vc-handled-backends vc-handled-backends)) - (setopt file-name-handler-alist nil - vc-handled-backends nil) +(let ((saved-file-name-handler-alist file-name-handler-alist)) + (setopt file-name-handler-alist nil) (add-hook 'emacs-startup-hook (defun mm-restore-emacs-settings () (setopt gc-cons-threshold (* 1024 1024 8) gc-cons-percentage 0.1 - file-name-handler-alist saved-file-name-handler-alist - vc-handled-backends saved-vc-handled-backends)))) + file-name-handler-alist saved-file-name-handler-alist)))) ;;; Avoid Flashbang diff --git a/.config/emacs/init.el b/.config/emacs/init.el index a8620d6..eb0f23e 100644 --- a/.config/emacs/init.el +++ b/.config/emacs/init.el @@ -8,7 +8,7 @@ ;; Lord knows why this needs to be so complicated… ;; ;; The ‘eval’ is required in the case that this file is byte-compiled. -(if mm-darwin-p +(if mm-humanwave-p (eval '(setq inhibit-startup-echo-area-message "thomasvoss")) (eval '(setq inhibit-startup-echo-area-message "thomas"))) @@ -177,17 +177,17 @@ buffer suppressed." (echo-keystrokes 0.01) ; 0 disables echoing (echo-keystrokes-help nil) (extended-command-suggest-shorter nil) - (initial-buffer-choice t) + (initial-buffer-choice nil) (initial-scratch-message mm-initial-scratch-message) (kill-do-not-save-duplicates t) (large-file-warning-threshold nil) (make-backup-files nil) - (mode-require-final-newline nil) + (mode-require-final-newline t) (next-error-recenter '(4)) ; ‘center of window’ (read-extended-command-predicate #'command-completion-default-include-p) (remote-file-name-inhibit-auto-save t) (remote-file-name-inhibit-delete-by-moving-to-trash t) - (require-final-newline nil) + (require-final-newline mm-humanwave-p) (save-interprogram-paste-before-kill t) (user-full-name "Thomas Voss") (user-mail-address "mail@thomasvoss.com") @@ -294,15 +294,6 @@ the buffer without saving it." (advice-add command :around #'mm-auto-create-directories)) -;;; Emacs Lisp Libraries - -;; The following are included here because they’re required by some of -;; the modules. - -(use-package async - :ensure t) - - ;;; Load Modules (require 'mm-abbrev) ; Text Expansion @@ -326,6 +317,28 @@ the buffer without saving it." (require 'mm-darwin)) ; MacOS (when mm-lsp-p (require 'mm-lsp)) ; Language Server Protocol +(when mm-humanwave-p + (require 'mm-humanwave)) ; Humanwave Extras + +;; TODO: Put this somewhere reasonable + +(defvar mm-placeholder-text "‹XX›") + +(defun mm-placeholder-insert () + (interactive) + (insert mm-placeholder-text)) + +(defun mm-placeholder-next () + (interactive) + (let ((point (point))) + (if (search-forward mm-placeholder-text nil :noerror) + (delete-region (match-beginning 0) (match-end 0)) + (goto-char point) + (message "No more placeholders after point.")))) + +(keymap-global-set "C-c i p" #'mm-placeholder-insert) +(keymap-global-set "C-c n" #'mm-placeholder-next) + ;;; Postamble @@ -333,4 +346,4 @@ the buffer without saving it." (add-hook 'after-init-hook (defun mm-echo-init-time () (message (emacs-init-time "Emacs initialized in %.2f seconds"))) - 100)
\ No newline at end of file + 100) diff --git a/.config/emacs/modules/mm-abbrev.el b/.config/emacs/modules/mm-abbrev.el index 937d558..66ca8d7 100644 --- a/.config/emacs/modules/mm-abbrev.el +++ b/.config/emacs/modules/mm-abbrev.el @@ -78,6 +78,14 @@ case-sensitive to avoid unexpected abbreviation expansions." "sme" "save-mark-and-excursion" "sr" "save-restriction") +(when mm-humanwave-p + (with-eval-after-load 'python-ts-mode + (mm-define-abbreviations python-ts-mode-abbrev-table + "empb" "with emphasize.Block():" + "empf" "@emphasize.func" + "empi" "from shared.system import emphasize" + "empt" "emphasize.this"))) + ;;; Template Configuration diff --git a/.config/emacs/modules/mm-editing.el b/.config/emacs/modules/mm-editing.el index 14f5211..6054f5a 100644 --- a/.config/emacs/modules/mm-editing.el +++ b/.config/emacs/modules/mm-editing.el @@ -56,6 +56,7 @@ (go-ts-mode . (:extras go-ts-mode-indent-offset)) (gsp-ts-mode . (:width 2 :extras gsp-ts-mode-indent-rules)) (helpful-mode . (:width 8)) ; GNU code uses 8-column tabs + (json-ts-mode . (:extras json-ts-mode-indent-offset)) (latex-mode . (:width 2)) (lisp-data-mode . (:spaces t)) (lisp-interaction-mode . (:spaces t)) @@ -66,8 +67,12 @@ (python-ts-mode . (:extras python-indent-offset)) (sgml-mode . (:extras sgml-basic-offset)) (sh-mode . (:extras sh-basic-offset)) + (sql-mode . (:extras sqlind-basic-offset)) (tex-mode . (:width 2)) - (vimscript-ts-mode . (:extras vimscript-ts-mode-indent-level))) + (typescript-ts-mode . (:extras typescript-ts-mode-indent-offset)) + (vimscript-ts-mode . (:extras vimscript-ts-mode-indent-level)) + (vue-ts-mode . (:extras (typescript-ts-mode-indent-offset + vue-ts-mode-indent-offset)))) "Alist of indentation settings. Each pair in this alist is of the form (MODE . SETTINGS) where MODE specifies the mode for which the given SETTINGS should apply. @@ -122,6 +127,11 @@ those should be listed in `mm-editing-indentation-settings'." extras)) (set (make-local-variable extra) tabsize))))) +(use-package sh-mode + :custom + (sh-indent-for-case-label 0) + (sh-indent-for-case-alt #'+)) + ;;; Code Commenting @@ -295,6 +305,18 @@ surround with spaces." (push '("`" . "'") surround-pairs)))) +;;; Insert Webpage Contents + +(defun mm-insert-from-url (url) + "Insert the contents of URL at point." + (interactive + (let ((url-at-point (thing-at-point 'url))) + (list (read-string + (format-prompt "URL" url-at-point) + nil nil url-at-point)))) + (call-process "curl" nil '(t nil) nil url)) + + ;;; Emmet Mode (defun mm-editing-emmet-dwim (arg) @@ -315,6 +337,36 @@ is as described by `emmet-expand-line'." (emmet-self-closing-tag-style "")) +;;; JQ Manipulation in JSON Mode + +(defun mm-jq-filter (query &optional beg end) + "TODO" + (interactive + (list + (read-string (format-prompt "Query" nil)) + (when (use-region-p) (region-beginning)) + (when (use-region-p) (region-end)))) + (let* ((beg (or beg (point-min))) + (end (or end (point-max))) + (temp-buffer (generate-new-buffer "* jq temp*")) + (exit-code (call-process-region beg end "jq" nil temp-buffer nil + "--tab" query)) + (output (with-current-buffer temp-buffer (buffer-string)))) + (if (zerop exit-code) + (atomic-change-group + (delete-region beg end) + (insert output) + (indent-region beg (point))) + (message "%s" output)) + (kill-buffer temp-buffer))) + +(use-package json-ts-mode + :bind ( :map json-ts-mode-map + ("C-|" . #'mm-jq-filter)) + :config + (require 'live-jq)) + + ;;; Number Formatting (use-package number-format-mode @@ -326,7 +378,9 @@ is as described by `emmet-expand-line'." ;;; Additional Major Modes (use-package awk-ts-mode :ensure t) +(use-package cmake-mode :ensure t) (use-package git-modes :ensure t) +(use-package po-mode :ensure t) (use-package sed-mode :ensure t) (use-package csv-mode @@ -358,6 +412,12 @@ is as described by `emmet-expand-line'." ;;; Add Missing Extensions (dolist (pattern '("\\.tmac\\'" "\\.mom\\'")) - (add-to-list 'auto-mode-alist (cons pattern 'nroff-mode))) + (add-to-list 'auto-mode-alist (cons pattern #'nroff-mode))) + + +;;; Subword Navigation + +(use-package subword + :hook prog-mode) -(provide 'mm-editing)
\ No newline at end of file +(provide 'mm-editing) diff --git a/.config/emacs/modules/mm-humanwave.el b/.config/emacs/modules/mm-humanwave.el new file mode 100644 index 0000000..3aa97b3 --- /dev/null +++ b/.config/emacs/modules/mm-humanwave.el @@ -0,0 +1,141 @@ +;;; mm-humanwave.el --- Humanwave extras -*- lexical-binding: t; -*- + + +;;; Query the Backend + +(defvar mm--humanwave-query-history nil + "History for endpoints given to `mm-humanwave-query'.") + +(defun mm-humanwave-query (endpoint &optional method) + "Query and display the result of an HTTP reqiest on ENDPOINT. +If METHOD is nil, a GET request is performed." + (interactive + (let* ((query (read-string (format-prompt "Query" nil) + (car-safe mm--humanwave-query-history) + 'mm--humanwave-query-history)) + (parts (string-split (string-trim query) " " :omit-nulls))) + (when (length> parts 2) + (user-error "Queries must be of the form `METHOD ENDPOINT' or `ENDPOINT'.")) + (nreverse parts))) + (let* ((project-root (project-root (project-current :maybe-prompt))) + (qry-path (expand-file-name "qry" project-root)) + extras) + (unless (file-executable-p qry-path) + (user-error "No `qry' executable found in the project root")) + (let ((output-buffer (get-buffer-create "*Query Response*"))) + (with-current-buffer output-buffer + (delete-region (point-min) (point-max)) + (call-process qry-path nil t nil + (string-trim endpoint) "-X" (or method "GET")) + (unless (eq major-mode 'json-ts-mode) + (json-ts-mode)) + (goto-char (point-min))) + (display-buffer output-buffer)))) + +(keymap-set project-prefix-map "q" #'mm-humanwave-query) + + +;;; IMenu Support for Handlers + +(defvar mm-humanwave-handler--regexp + (rx bol + (* blank) + (or "if" "elif") + (* blank) + (or "topic" "schedule") + (* blank) + "==" + (* blank) + (or (seq ?\' (* (not ?\')) ?\') + (seq ?\" (* (not ?\")) ?\")) + (* blank) + ?: + (* blank) + eol)) + +(defun mm-humanwave-handler-topic-imenu-index () + (let ((tree-index (when (fboundp 'treesit-simple-imenu--generic-function) + (treesit-simple-imenu--generic-function + treesit-simple-imenu-settings))) + (topic-index '())) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward mm-humanwave-handler--regexp nil :noerror) + (let ((label (match-string 0)) + (pos (match-beginning 0))) + (push (cons (format "Topic: %s" (string-trim label)) pos) topic-index)))) + (append (nreverse topic-index) tree-index))) + +(defun mm-humanwave-handler-topic-imenu-setup () + "Setup custom imenu index for `python-ts-mode'." + (when (and (string-match-p "handlers?" (or (buffer-file-name) "")) + (derived-mode-p #'python-ts-mode)) + (setq-local imenu-create-index-function + #'mm-humanwave-handler-topic-imenu-index))) + +(add-hook 'after-change-major-mode-hook + #'mm-humanwave-handler-topic-imenu-setup) + + +;;; Insert Imports in Vue + +(defun mm-humanwave-insert-vue-import-path (base-directory target-file) + "Insert an import directive at POINT. +The import directive imports TARGET-FILE relative from BASE-DIRECTORY. +When called interactively BASE-DIRECTORY is the directory of the current +open Vue file and TARGET-FILE is a file in the current project that is +queried interactively. + +When called interactively the prefix argument can be used to emulate the +behaviour of the INCLUDE-ALL-P argument to `mm-project-read-file-name'." + (interactive + (list + default-directory + (mm-humanwave-project-read-file-name current-prefix-arg))) + (let ((path (file-name-sans-extension + (file-relative-name target-file base-directory)))) + (unless (string-match-p "/" path) + (setq path (concat "./" path))) + (insert "import ") + (save-excursion + (insert (thread-last + (file-name-base path) + (mm-string-split "-") + (mapconcat #'capitalize))) + (push-mark (point)) + (insert (format " from '%s';" path))))) + +(defun mm-humanwave-project-read-file-name (&optional include-all-p) + "Prompt for a project file. +This function is similar to `project-find-file', but it returns the path +to the selected file instead of opening it. + +When called interactively the selected file is printed to the minibuffer, +otherwise it is returned. + +The prefix argument INCLUDE-ALL-P is the same as the INCLUDE-ALL argument +to the `project-find-file' command." + (interactive "P") + (let* ((project (project-current :maybe-prompt)) + (root (project-root project)) + (files (if include-all-p + (let ((vc-ignores (mapcar + (lambda (dir) (concat dir "/")) + vc-directory-exclusion-list))) + (project--files-in-directory root vc-ignores)) + (project-files project))) + (canditates (mapcar (lambda (f) (file-relative-name f root)) + files)) + (table (lambda (string predicate action) + (if (eq action 'metadata) + '(metadata (category . file)) + (complete-with-action action canditates string predicate)))) + (default-directory root) + (choice (completing-read (format-prompt "Find project file" nil) + table nil :require-match))) + (let ((path (expand-file-name choice root))) + (if (called-interactively-p 'any) + (message "%s" path) + path)))) + +(provide 'mm-humanwave) diff --git a/.config/emacs/modules/mm-modeline.el b/.config/emacs/modules/mm-modeline.el index d1ec6f0..44fc115 100644 --- a/.config/emacs/modules/mm-modeline.el +++ b/.config/emacs/modules/mm-modeline.el @@ -90,7 +90,7 @@ the text it should be mapped to.") ((derived-mode-p 'comint-mode) "$ ") ((derived-mode-p 'conf-mode) "# ") ((derived-mode-p 'prog-mode) "λ ") - ((derived-mode-p 'special-mode) "❇ ") + ((derived-mode-p 'special-mode) "★ ") ((derived-mode-p 'text-mode) "§ ") (:default "")) 'face 'mm-modeline-major-mode-symbol-face)) diff --git a/.config/emacs/modules/mm-projects.el b/.config/emacs/modules/mm-projects.el index 00342f5..02d7af4 100644 --- a/.config/emacs/modules/mm-projects.el +++ b/.config/emacs/modules/mm-projects.el @@ -19,7 +19,7 @@ This is intended to be called interactively via (project-find-file "Find File" ?f) (mm-projects-project-magit-status "Git Status" ?s))) :config - (unless mm-darwin-p + (unless mm-humanwave-p (if-let ((repo-directory (getenv "REPODIR"))) (let* ((list-dir (lambda (path) @@ -47,9 +47,10 @@ This is intended to be called interactively via (use-package magit :ensure t - :bind ( :map magit-status-mode-map - ("[" . magit-section-backward-sibling) - ("]" . magit-section-forward-sibling)) + :bind (("C-c b" . magit-blame-addition) + :map magit-status-mode-map + ("[" . magit-section-backward-sibling) + ("]" . magit-section-forward-sibling)) :custom (git-commit-style-convention-checks '(non-empty-second-line overlong-summary-line)) @@ -70,11 +71,18 @@ This is intended to be called interactively via "push" "-v" args remote (format "refs/heads/%s:refs/heads/%s" branch branch))))) (transient-append-suffix #'magit-push '(1 -1) - '("a" "all remotes" mm-projects-magit-push-current-to-all-remotes))) + '("a" "all remotes" mm-projects-magit-push-current-to-all-remotes)) + (add-to-list 'magit-blame-styles + '(margin + (show-lines . t) + (margin-format . (" %C %a" " %s%f" " ")) + (margin-width . 73) + (margin-face . magit-blame-margin) + (margin-body-face . (magit-blame-dimmed))))) (use-package magit-repos :ensure nil ; Part of ‘magit’ - :if (not mm-darwin-p) + :if (not mm-humanwave-p) :commands (magit-list-repositories) :init (if-let ((directory (getenv "REPODIR"))) @@ -96,4 +104,11 @@ This is intended to be called interactively via (require 'ansi-color) (add-hook 'compilation-filter-hook #'ansi-color-compilation-filter)) -(provide 'mm-projects)
\ No newline at end of file + +;;; GitHub Pull Requests + +(require 'gh) +(keymap-global-set "C-c p c" #'gh-create-pr) +(keymap-global-set "C-c p o" #'gh-open-previous-pr) + +(provide 'mm-projects) diff --git a/.config/emacs/modules/mm-theme.el b/.config/emacs/modules/mm-theme.el index 95c96c9..cde214a 100644 --- a/.config/emacs/modules/mm-theme.el +++ b/.config/emacs/modules/mm-theme.el @@ -8,7 +8,7 @@ ;;; Fonts -(defvar mm-theme-monospace-font `(,(if mm-darwin-p +(defvar mm-theme-monospace-font `(,(if mm-humanwave-p "Iosevka Custom" "Iosevka Smooth") :weight regular @@ -16,7 +16,9 @@ "The default monospace font. This is a plist containing a font name, -weight, and -height.") -(defvar mm-theme-proportional-font '("OpenSans" :weight regular :height 162) +(defvar mm-theme-proportional-font + `(,(if mm-darwin-p "Microsoft Sans Serif" "OpenSans") + :weight regular :height 162) "The default proportional font. This is a plist containing a font name, -weight, and -height.") @@ -164,6 +166,16 @@ See also the `mm-theme-background-opacity' variable." (("C-c h l" . pulsar-highlight-dwim))) +;;; In-buffer highlighting + +(require 'hi-lock) ; For extra face definitions +(use-package highlighter + :bind (("C-c h m" . #'highlighter-mark) + ("C-c h u" . #'highlighter-unmark) + ("C-c h U" . #'highlighter-unmark-buffer)) + :commands (highlighter-mark highlighter-unmark highlighter-unmark-buffer)) + + ;;; Add Padding (use-package spacious-padding @@ -222,7 +234,7 @@ See also the `mm-theme-background-opacity' variable." ;;; Indent Guides -(when mm-darwin-p +(when mm-humanwave-p (use-package highlight-indent-guides :ensure t :hook ((jinja2-mode vue-ts-mode mhtml-mode) . highlight-indent-guides-mode) @@ -231,4 +243,4 @@ See also the `mm-theme-background-opacity' variable." (highlight-indent-guides-auto-even-face-perc 30) (highlight-indent-guides-auto-odd-face-perc 0))) -(provide 'mm-theme)
\ No newline at end of file +(provide 'mm-theme) diff --git a/.config/emacs/modules/mm-treesit.el b/.config/emacs/modules/mm-treesit.el index 4e052de..bd146ce 100644 --- a/.config/emacs/modules/mm-treesit.el +++ b/.config/emacs/modules/mm-treesit.el @@ -35,6 +35,8 @@ "https://github.com/tree-sitter/tree-sitter-java") (javascript "https://github.com/tree-sitter/tree-sitter-javascript") + (json + "https://github.com/tree-sitter/tree-sitter-json") (markdown "https://github.com/tree-sitter-grammars/tree-sitter-markdown" "split_parser" "tree-sitter-markdown/src") @@ -43,6 +45,9 @@ "split_parser" "tree-sitter-markdown-inline/src") (python "https://github.com/tree-sitter/tree-sitter-python") + (tsx + "https://github.com/tree-sitter/tree-sitter-typescript" + "master" "tsx/src") (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src") @@ -112,11 +117,13 @@ The parsers are taken from `treesit-language-source-alist'." ;; anyway. Same goes for ‘typescript-ts-mode’. (defvar mm-treesit-language-file-name-alist '((go . "\\.go\\'") - (go-mod . "/go\\.mod\\'") + (gomod . "/go\\.mod\\'") + (json . "\\.json\\'") + (tsx . "\\.tsx\\'") (typescript . "\\.ts\\'")) "Alist mapping languages to their associated file-names. This alist is a set of pairs of the form (LANG . REGEXP) where LANG is -the symbol corresponding to a major mode with the ‘-ts-mode’ suffix +the symbol corresponding to a major mode with the `-ts-mode' suffix removed. REGEXP is a regular expression matching filenames for which the associated language’s major-mode should be enabled. @@ -132,9 +139,9 @@ languages should be listed here.") (dolist (spec treesit-language-source-alist) (let* ((lang (car spec)) - (lang (alist-get lang mm-treesit-language-remap-alist lang)) - (name-mode (intern (format "%s-mode" lang))) - (name-ts-mode (intern (format "%s-ts-mode" lang)))) + (lang-remap (alist-get lang mm-treesit-language-remap-alist lang)) + (name-mode (intern (format "%s-mode" lang-remap))) + (name-ts-mode (intern (format "%s-ts-mode" lang-remap)))) ;; If ‘name-ts-mode’ is already in ‘auto-mode-alist’ then we don’t ;; need to do anything, however if that’s not the case then if ;; ‘name-ts-mode’ and ‘name-mode’ are both bound we do a simple @@ -155,6 +162,9 @@ languages should be listed here.") (add-to-list 'auto-mode-alist (cons file-regexp name-ts-mode)) (warn "Unable to determine the extension for `%s'." name-ts-mode)))))) +;; JavaScript being difficult as usual +(add-to-list 'major-mode-remap-alist '(javascript-mode . js-ts-mode)) + ;;; Hack For C23 @@ -212,4 +222,4 @@ back to regular `expreg-expand'." :commands (mm-expreg-expand mm-expreg-expand-dwim) :bind ("M-SPC" . mm-expreg-expand-dwim)) -(provide 'mm-treesit)
\ No newline at end of file +(provide 'mm-treesit) diff --git a/.config/emacs/site-lisp/gh.el b/.config/emacs/site-lisp/gh.el new file mode 100644 index 0000000..23086e5 --- /dev/null +++ b/.config/emacs/site-lisp/gh.el @@ -0,0 +1,59 @@ +;;; gh.el --- GitHub integration for Emacs -*- lexical-binding: t; -*- + +(defun gh-get-labels () + "Return a list of labels in the current GitHub repository." + (with-temp-buffer + (call-process "gh" nil t nil "label" "list" "--sort" "name" "--json" "name") + (goto-char (point-min)) + (seq-map (lambda (x) (gethash "name" x)) + (json-parse-buffer)))) + +;; TODO: Set title and body in a buffer like Magit +(defun gh-create-pr (title &optional labels draftp) + "Create a GitHub pull request. +If DRAFTP is non-nil, the PR will be created as a draft. + +LABELS is a list of labels. A list of available labels can be fetched +via `gh-get-labels'." + (interactive + (list + (read-string (format-prompt "PR Title" nil)) + (completing-read-multiple (format-prompt "PR Labels" nil) + (gh-get-labels)) + (y-or-n-p "Create PR as a draft? "))) + (let* ((project (project-name (project-current))) + (flags `("--fill-verbose" "--assignee" "@me")) + (label-string (mapconcat #'identity labels ","))) + ;; TODO: Remove this + (when (string= project "blixem") + (setq title (format "%s %s" (car (vc-git-branches)) title))) + (setq flags (append flags `("--title" ,title))) + (when draftp + (setq flags (append flags '("--draft")))) + (when labels + (setq flags (append flags `("--label" ,label-string)))) + (with-temp-buffer + (apply #'call-process "gh" nil t nil "pr" "create" flags) + (message (buffer-string))))) + +(defvar gh-pr-regexp + "\\`https://\\(?:www\\.\\)?github\\.com/[^/]+/[^/]+/pull/[[:digit:]]+\\'") + +(defun gh--pr-link-p (s) + (declare (pure t) (side-effect-free t)) + (string-match-p gh-pr-regexp s)) + +(defun gh-open-previous-pr () + "Open the previous GitHub pull request. +Opens the previous pull request created by `gh-create-pr' by searching +for the echoed URL in the `*Messages*' buffer." + (interactive) + (with-current-buffer "*Messages*" + (goto-char (point-max)) + (while (not (gh--pr-link-p (buffer-substring-no-properties + (pos-bol) (pos-eol)))) + (unless (line-move -1 :noerror) + (user-error "No previous pull request found."))) + (browse-url-at-point))) + +(provide 'gh) diff --git a/.config/emacs/site-lisp/highlighter.el b/.config/emacs/site-lisp/highlighter.el new file mode 100644 index 0000000..ce67ac8 --- /dev/null +++ b/.config/emacs/site-lisp/highlighter.el @@ -0,0 +1,128 @@ +;;; highlighter.el --- In-buffer highlighting commands -*- lexical-binding: t; -*- + +(require 'seq) + +(defgroup highlighter nil + "Customization group for `highlighter'." + :group 'convenience) + +(defcustom highlighter-default-face 'match + "The default face used by `highlighter-mark'." + :type 'face + :package-version '(highlighter . "1.0.0") + :group 'highlighter) + +(defun highlighter-mark (arg) + "Highlight text in the buffer. +Highlight the current line or region if it is active. Text is +highlighted using the face specified by `highlighter-default-face'. + +With ARG, interactively pick a face to highlight with." + (declare (interactive-only t)) + (interactive "P") + (let ((bounds (if (use-region-p) + (region-bounds) + `((,(pos-bol) . ,(pos-eol))))) + (face (when arg + (highlighter--read-face-name "Highlight with face" #'facep)))) + (highlighter-mark-region bounds face)) + (when (region-active-p) + (deactivate-mark))) + +(defun highlighter-unmark (arg) + "Remove highlights in the buffer. + +Remove highlights from the current line or region if it is active. + +With ARG, interactively pick a face. Only highlights using the chosen +face will be removed." + (declare (interactive-only t)) + (interactive "P") + (let ((bounds (if (use-region-p) + (region-bounds) + `((,(pos-bol) . ,(pos-eol))))) + (face (when arg + (highlighter--read-face-name + "Clear highlights using face" + #'highlighter--used-face-p)))) + (highlighter-unmark-region bounds face)) + (when (region-active-p) + (deactivate-mark))) + +(defun highlighter-mark-region (bounds &optional face) + "Highlight text in the buffer within BOUNDS. +BOUNDS uses the same format as returned by `region-bounds'. + +Text is highlighted using the face specified by `highlighter-default-face'. + +If FACE is nil or omitted, `highlighter-default-face' is used." + (dolist (x bounds) (highlighter--mark-region (car x) (cdr x) face))) + +(defun highlighter-unmark-region (bounds &optional face) + "Remove highlights in the buffer within BOUNDS. +BOUNDS uses the same format as returned by `region-bounds'. + +If FACE is non-nil, only remove highlights using FACE." + (dolist (x bounds) (highlighter--unmark-region (car x) (cdr x) face))) + +(defun highlighter--mark-region (beg end &optional face) + (let ((ov (make-overlay beg end nil :front-advance)) + (face (or face highlighter-default-face 'match))) + (overlay-put ov 'priority 1) + (overlay-put ov 'face face) + (overlay-put ov 'evaporate t) + (overlay-put ov 'highlighter--mark-p t) + (overlay-put ov 'highlighter--face face))) + +(defun highlighter--unmark-region (beg end &optional face) + (if face + (remove-overlays beg end 'highlighter--face face) + (remove-overlays beg end 'highlighter--mark-p t))) + +(defun highlighter-unmark-buffer (arg) + "Remove highlights in the buffer. + +With ARG, interactively pick a face. Only highlights using the chosen +face will be removed." + (declare (interactive-only t)) + (interactive "P") + (let ((face (when arg + (highlighter--read-face-name + "Clear highlights using face" + #'highlighter--used-face-p)))) + (highlighter--unmark-region (point-min) (point-max) face))) + +(defun highlighter--read-face-name (prompt face-predicate) + (let (default defaults) + (let ((prompt (format "%s: " prompt)) + (completion-extra-properties + `(:affixation-function + ,(lambda (faces) + (mapcar + (lambda (face) + (list face + (concat (propertize read-face-name-sample-text + 'face face) + "\t") + "")) + faces)))) + aliasfaces nonaliasfaces faces) + ;; Build up the completion tables. + (mapatoms (lambda (s) + (when (apply face-predicate s nil) + (if (get s 'face-alias) + (push (symbol-name s) aliasfaces) + (push (symbol-name s) nonaliasfaces))))) + (let ((face (completing-read + prompt + (completion-table-in-turn nonaliasfaces aliasfaces) + nil t nil 'face-name-history defaults))) + (when (facep face) (if (stringp face) + (intern face) + face)))))) + +(defun highlighter--used-face-p (face) + (seq-filter (lambda (ov) (eq face (overlay-get ov 'highlighter--face))) + (overlays-in (point-min) (point-max)))) + +(provide 'highlighter) diff --git a/.config/emacs/site-lisp/live-jq.el b/.config/emacs/site-lisp/live-jq.el new file mode 100644 index 0000000..f8a0a7f --- /dev/null +++ b/.config/emacs/site-lisp/live-jq.el @@ -0,0 +1,101 @@ +;; TODO: ‘defcustom’ this +(defvar live-jq-major-mode + (cond ((fboundp #'json-ts-mode) #'json-ts-mode) + ((fboundp #'json-mode) #'json-mode)) + "TODO") + +(defvar live-jq--input-buffer nil + "The buffer containing the original JSON data.") + +(defvar live-jq--preview-buffer "*JQ Preview*" + "The buffer showing the live jq results.") + +(defvar live-jq--last-query "") + +(defun live-jq--get-json-input () + "Return the contents of the input buffer as a string." + (with-current-buffer live-jq--input-buffer + (buffer-substring-no-properties (point-min) (point-max)))) + +(defun live-jq--run-jq (query) + "Run jq QUERY on the input buffer's content and return result string or nil on error." + (let ((json-input (live-jq--get-json-input))) + (with-temp-buffer + (insert json-input) + (let ((exit-code (call-process-region + (point-min) (point-max) + "jq" :delete t nil "--tab" query))) + (when (zerop exit-code) + (buffer-string)))))) + +(defun live-jq--render-jq-preview (query) + "Update preview buffer with the result or error of jq QUERY." + (let* ((preview-buffer (get-buffer-create live-jq--preview-buffer)) + (json-input (live-jq--get-json-input)) + (inhibit-read-only t)) + (with-current-buffer preview-buffer + (erase-buffer) + (condition-case err + (with-temp-buffer + (insert json-input) + (let ((exit-code (call-process-region + (point-min) (point-max) + "jq" nil preview-buffer nil "--tab" query))) + (when (not (zerop exit-code)) + (erase-buffer) + (insert "%s\n%s" + (propertize (format "jq error (exit %d): %s" exit-code query) + 'face 'error) + json-input)))) + (error + (insert "%s\n%s" + (propertize (format "Error: %s" err) 'face 'error) + input-json))) + (goto-char (point-min)) + (when live-jq-major-mode + (funcall live-jq-major-mode)))) + (display-buffer live-jq--preview-buffer)) + +(defun live-jq--minibuffer-update () + "Update preview as user types." + (let ((query (minibuffer-contents))) + (unless (equal query live-jq--last-query) + (setq live-jq--last-query query) + (live-jq--render-jq-preview query)))) + +;;;###autoload +(defun live-jq () + "Prompt for a jq query, show live results, and replace buffer on confirmation." + (interactive) + (unless (executable-find "jq") + (user-error "`jq' not found in PATH.")) + + (setq live-jq--input-buffer (current-buffer)) + (setq live-jq--last-query "") + + ;; Clean up preview buffer if user cancels with C-g + (let ((minibuffer-setup-hook + (list (lambda () + ;; Add post-command-hook for live preview + (add-hook 'post-command-hook #'live-jq--minibuffer-update nil t) + ;; Add abort cleanup + (add-hook 'minibuffer-exit-hook + (lambda () + (when (get-buffer live-jq--preview-buffer) + (kill-buffer live-jq--preview-buffer))) + nil t))))) + (let ((query (read-from-minibuffer (format-prompt "Query" nil)))) + (unwind-protect + (let ((result (live-jq--run-jq query))) + (if result + (with-current-buffer live-jq--input-buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (insert result)) + (message "jq applied.")) + (user-error "Invalid jq query: see *jq-preview* for details"))) + ;; Cleanup preview buffer after any outcome + (when (get-buffer live-jq--preview-buffer) + (kill-buffer live-jq--preview-buffer)))))) + +(provide 'live-jq) diff --git a/.config/emacs/templates b/.config/emacs/templates index 6749183..ad4e97a 100644 --- a/.config/emacs/templates +++ b/.config/emacs/templates @@ -27,4 +27,12 @@ emacs-lisp-mode (buffer-name))) " --- " p " -*- lexical-binding: t; -*-" n n q) -(section "\f" n ";;; " p n n q)
\ No newline at end of file +(section "\f" n ";;; " p n n q) + +vue-ts-mode + +(fcrow + > "<div class=\"fc-row\">" + n> "<label>{{ _('" p "') }}</label>" + n> q + n> "</div>")
\ No newline at end of file |