diff options
Diffstat (limited to '.config/emacs/modules')
| -rw-r--r-- | .config/emacs/modules/mm-completion.el | 99 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-editing.el | 17 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-humanwave.el | 93 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-keybindings.el | 7 | ||||
| -rw-r--r-- | .config/emacs/modules/mm-search.el | 6 |
5 files changed, 214 insertions, 8 deletions
diff --git a/.config/emacs/modules/mm-completion.el b/.config/emacs/modules/mm-completion.el index 79da064..3367aa1 100644 --- a/.config/emacs/modules/mm-completion.el +++ b/.config/emacs/modules/mm-completion.el @@ -20,10 +20,80 @@ ;;; Annotate Completions -;; TODO: Show git branch descriptions! (use-package marginalia :ensure t :hook after-init + :config + (with-eval-after-load 'magit + (defvar mm-marginalia--magit-cache nil) + (add-hook 'minibuffer-setup-hook + (lambda () (setq mm-marginalia--magit-cache nil))) + + (defvar-local mm-marginalia-magit-base-branch "master") + + (defface mm-diffstat-counter-added + '((t :inherit magit-diffstat-added)) + "TODO") + (defface mm-diffstat-counter-removed + '((t :inherit magit-diffstat-removed)) + "TODO") + + (defun mm-marginalia-populate-magit-cache () + "Batch-fetch all Git branch descriptions and stats into the cache." + (setq mm-marginalia--magit-cache (make-hash-table :test #'equal)) + (when-let ((default-directory (magit-toplevel))) + (dolist (line (magit-git-lines "config" "list")) + (when (string-match "^branch\\.\\(.*?\\)\\.description=\\(.*\\)$" line) + (puthash (match-string 1 line) + (list :desc (match-string 2 line) :stats "") + mm-marginalia--magit-cache))) + (dolist (line (magit-git-lines + "for-each-ref" + (format + "--format=%%(refname:short)\x1F%%(ahead-behind:%s)" + mm-marginalia-magit-base-branch) + "refs/heads/")) + (when (string-match (rx bol (group (1+ (not #x1F))) + #x1F (group (1+ digit)) + " " (group (1+ digit)) eol) + line) + (let* ((branch (match-string 1 line)) + (ahead (+ (string-to-number (match-string 2 line)))) + (behind (- (string-to-number (match-string 3 line)))) + (ahead-str (if (zerop ahead) + "" + (propertize (format "%+d" ahead) + 'face 'mm-diffstat-counter-added))) + (behind-str (if (zerop behind) + "" + (propertize (format "%+d" behind) + 'face 'mm-diffstat-counter-removed))) + (stats-str (format "%5s %5s" ahead-str behind-str)) + (existing (gethash branch mm-marginalia--magit-cache + (list :desc "" :stats "")))) + (puthash branch (plist-put existing :stats stats-str) + mm-marginalia--magit-cache)))))) + + (defun mm-marginalia-annotate-magit-branch (cand) + "Annotate Git branch CAND with ahead/behind stats and description." + (unless mm-marginalia--magit-cache + (mm-marginalia-populate-magit-cache)) + (let* ((data (gethash cand mm-marginalia--magit-cache '(:desc "" :stats ""))) + (desc (or (plist-get data :desc) "")) + (stats (or (plist-get data :stats) ""))) + (marginalia--fields + (stats :width 10) + (desc :truncate 1.0 :face 'marginalia-documentation)))) + + (add-to-list 'marginalia-annotators + '(magit-branch mm-marginalia-annotate-magit-branch builtin none)) + (dolist (cmd '(magit-branch-and-checkout + magit-branch-checkout + magit-branch-delete + magit-checkout + magit-merge + magit-rebase-branch)) + (add-to-list 'marginalia-command-categories (cons cmd 'magit-branch)))) :custom (marginalia-field-width 50) (marginalia-max-relative-age 0)) @@ -158,4 +228,29 @@ :custom (find-library-include-other-files nil)) -(provide 'mm-completion)
\ No newline at end of file + +;;; Completion at Point Functions + +(defun mm-cape-file--not-dot-path-p (cand) + (declare (ftype (function (string) boolean)) + (pure t) (side-effect-free t)) + (not (or (string= cand "./") + (string= cand "../")))) + +(use-package cape + :ensure t + :init + (add-hook 'completion-at-point-functions + (cape-capf-predicate #'cape-file #'mm-cape-file--not-dot-path-p)) + (add-hook 'completion-at-point-functions + (cape-capf-prefix-length #'cape-dabbrev 3))) + + +;;; Completion at Point Live Completions + +(use-package completion-preview + :hook (after-init . global-completion-preview-mode) + :custom + (completion-preview-minimum-symbol-length 1)) + +(provide 'mm-completion) diff --git a/.config/emacs/modules/mm-editing.el b/.config/emacs/modules/mm-editing.el index eb8b7be..91a290e 100644 --- a/.config/emacs/modules/mm-editing.el +++ b/.config/emacs/modules/mm-editing.el @@ -136,22 +136,29 @@ those should be listed in `mm-editing-indentation-settings'." ;;; Code Commenting -(defun mm-c-comment-no-continue () +(defun mm-newcomment-c-config () (setq-local comment-continue " ")) -(defun mm-mhtml-comment-no-continue () +(defun mm-newcomment-html-config () (setq-local comment-continue " ")) +(defun mm-newcomment-rust-config () + (setq-local comment-start "/* " + comment-end " */" + comment-continue " * " ; rustfmt doesn’t play nice + comment-quote-nested nil)) + (use-package newcomment :custom (comment-style 'multi-line) :config (dolist (mode '(c-mode c++-mode)) - (add-hook (mm-mode-to-hook mode) #'mm-c-comment-no-continue) + (add-hook (mm-mode-to-hook mode) #'mm-newcomment-c-config) (when-let ((ts-mode (mm-mode-to-ts-mode mode)) ((fboundp ts-mode))) - (add-hook (mm-mode-to-hook ts-mode) #'mm-c-comment-no-continue))) - (add-hook 'mhtml-mode #'mm-mhtml-comment-no-continue)) + (add-hook (mm-mode-to-hook ts-mode) #'mm-newcomment-c-config))) + (add-hook 'mhtml-mode #'mm-newcomment-html-config) + (add-hook 'rust-ts-mode #'mm-newcomment-rust-config)) ;;; Multiple Cursors diff --git a/.config/emacs/modules/mm-humanwave.el b/.config/emacs/modules/mm-humanwave.el index f9e59b4..54e4a4b 100644 --- a/.config/emacs/modules/mm-humanwave.el +++ b/.config/emacs/modules/mm-humanwave.el @@ -158,4 +158,97 @@ to the `project-find-file' command." (message "%s" path) path)))) +(defun mm-humanwave-insert-last-commit-message () + "TODO" + (interactive) + (insert + (with-temp-buffer + (call-process "git" nil t nil "log" "-1" "--pretty=%s") + (goto-char (point-min)) + (replace-regexp "\\`HW-[0-9]+ " "") + (string-trim (buffer-string))))) + + +;;; Jira Integration + +(use-package jira + :ensure t + :custom + (jira-api-version 3) + (jira-base-url "https://humanwave.atlassian.net") + (jira-detail-show-announcements nil) + (jira-issues-max-results 100) + (jira-issues-table-fields '(:key :status-name :assignee-name :summary)) + (jira-token-is-personal-access-token nil)) + + +;;; Icon Autocompletion + +(defvar mm-humanwave-icon-component-file "web/src/components/icon.vue" + "Path to the <icon /> component definition.") + +(defun mm-humanwave--find-icon-map () + (let* ((project (project-current :maybe-prompt)) + (path (expand-file-name mm-humanwave-icon-component-file + (project-root project)))) + (unless (file-exists-p path) + (user-error "File `%s' does not exist." path)) + (with-current-buffer (find-file-noselect path) + (let* ((parser (treesit-parser-create 'typescript)) + (root-node (treesit-parser-root-node parser)) + (query `((((lexical_declaration + (variable_declarator + name: (identifier) @name)) @the_catch) + (:equal @name "ICON_MAP")) + (((variable_declaration + (variable_declarator + name: (identifier) @name)) @the_catch) + (:equal @name "ICON_MAP")))) + (captures (treesit-query-capture root-node query)) + (found-node (alist-get 'the_catch captures))) + found-node)))) + +(defun mm-humanwave--icon-list (found-node) + (let ((captures (treesit-query-capture found-node '((pair) @the_pair))) + (pairs nil)) + (when captures + (dolist (capture captures) + (let* ((pair-node (cdr capture)) + (key-node (treesit-node-child-by-field-name pair-node "key")) + (val-node (treesit-node-child-by-field-name pair-node "value"))) + (when (and key-node val-node) + (push (cons (mm-camel-to-lisp + (treesit-node-text key-node :no-property)) + (treesit-node-text val-node :no-property)) + pairs)))) + (sort pairs :key #'car :lessp #'string<)))) + +(defun mm-humanwave-insert-icon-component () + "Insert an icon at point with completion. + +This command provides completion for the available props that can be +given to the <icon /> component. The parser searches for the `ICON_MAP' +definition in the file specified by `mm-humanwave-icon-component-file'." + (interactive "" vue-ts-mode) + (if-let* ((node (mm-humanwave--find-icon-map)) + (alist (mm-humanwave--icon-list node))) + (let* ((max-key-width + (thread-last + alist + (mapcar (lambda (pair) (length (car pair)))) + (apply #'max) + (+ 4))) + (completion-extra-properties + `(:annotation-function + ,(lambda (key) + (concat + (propertize " " + 'display `(space :align-to ,max-key-width)) + (propertize (cdr (assoc key alist)) + 'face 'font-lock-string-face))))) + (prompt (format-prompt "Icon" nil)) + (icon (completing-read prompt alist nil :require-match))) + (insert (format "<icon %s />" icon))) + (error "Unable to find ICON_MAP definitions"))) + (provide 'mm-humanwave) diff --git a/.config/emacs/modules/mm-keybindings.el b/.config/emacs/modules/mm-keybindings.el index 0df92ce..aae9b4d 100644 --- a/.config/emacs/modules/mm-keybindings.el +++ b/.config/emacs/modules/mm-keybindings.el @@ -151,7 +151,7 @@ the first command is remapped to the second command." (with-eval-after-load 'project (mm-keymap-set project-prefix-map "g" #'mm-project-find-regexp - "G" #'mm-project-or-external-find-regexp) + "G" #'project-git-grab) (when mm-humanwave-p (mm-keymap-set project-prefix-map @@ -162,6 +162,11 @@ the first command is remapped to the second command." (mm-keymap-set eat-semi-char-mode-map "M-o" #'ace-window))) +(with-eval-after-load 'minibuffer + (when mm-humanwave-p + (mm-keymap-set minibuffer-mode-map + "C-c m" #'mm-humanwave-insert-last-commit-message))) + ;;; Display Available Keybindings diff --git a/.config/emacs/modules/mm-search.el b/.config/emacs/modules/mm-search.el index 9b1c4c4..3afb77e 100644 --- a/.config/emacs/modules/mm-search.el +++ b/.config/emacs/modules/mm-search.el @@ -46,4 +46,10 @@ matching respectively." (interactive (list (project--read-regexp))) (mm--project-find-wrapper #'project-or-external-find-regexp regexp)) + +;;; Grab Integration + +(use-package grab + :commands (grab git-grab project-grab project-git-grab)) + (provide 'mm-search) |