From 999b871b5e47718ddce4005119dfb622e9709950 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:34:46 +0100 Subject: emacs: Add grab.el for Grab support --- .config/emacs/site-lisp/grab.el | 211 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 .config/emacs/site-lisp/grab.el diff --git a/.config/emacs/site-lisp/grab.el b/.config/emacs/site-lisp/grab.el new file mode 100644 index 0000000..830d976 --- /dev/null +++ b/.config/emacs/site-lisp/grab.el @@ -0,0 +1,211 @@ +;;; grab.el --- Emacs integration for grab -*- lexical-binding: t; -*- + +;; Author: Thomas Voss +;; Description: TODO +;; Keywords: matching, tools + +;;; Commentary: +;; TODO + +;;; Code: + +(require 'ansi-color) +(require 'compile) +(require 'project) + +(defgroup grab nil + "Settings for `grab'." + :group 'tools) + +(defcustom grab-command "grab" + "The base executable for the grab tool." + :type 'string) + +(defcustom git-grab-command "git-grab" + "The base executable for the git-grab tool." + :type 'string) + +(defcustom grab-command-arguments '("-c" "-Halways") + "TODO" + :type '(repeat string)) + +(defcustom git-grab-command-arguments grab-command-arguments + "TODO" + :type '(repeat string)) + +(defcustom grab-default-pattern '("x// h//" . 3) + "TODO" + :type '(choice (cons string natnum) + (string))) + +(defvar grab--header-regexp + "^\\([^:\n]+\\):\\([0-9]+\\):") + +(defvar grab-results-mode-map + (let ((map (make-sparse-keymap))) + (keymap-set map "RET" #'grab-goto-match) + (keymap-set map "n" #'grab-next-match) + (keymap-set map "p" #'grab-prev-match) + map) + "Keymap for navigating grab matches.") + +(define-derived-mode grab-results-mode compilation-mode "Grab" + "TODO" + (setq-local compilation-error-regexp-alist nil + compilation-error-regexp-alist-alist nil + next-error-function #'grab-next-error) + (add-hook 'compilation-filter-hook #'ansi-color-compilation-filter nil :local) + (font-lock-add-keywords nil + `((,grab--header-regexp + (1 'compilation-info) + (2 'compilation-line-number))))) + + +;;; Process Management + +(defun grab--run-process (command-args buffer-name) + (let ((cmd-string (mapconcat #'shell-quote-argument command-args " "))) + (compilation-start cmd-string + #'grab-results-mode + (lambda (_) buffer-name)))) + + +;;; Navigation & Core Logic + +(defun grab--valid-match-p () + "Return non-nil if the current line is a match." + (save-excursion + (beginning-of-line) + (and (looking-at grab--header-regexp) + (not (string-prefix-p "Grab started" (match-string 1))) + (not (string-prefix-p "Grab finished" (match-string 1)))))) + +(defun grab-display-match (&optional select-window-p) + "Parse current line and display the match in the source buffer." + (interactive "P") + (save-excursion + (beginning-of-line) + (if (grab--valid-match-p) + (let* ((file (match-string-no-properties 1)) + (offset (string-to-number (match-string-no-properties 2))) + (full-path (expand-file-name file default-directory))) + (if (not (file-exists-p full-path)) + (error "File `%s' does not exist" full-path) + (let* ((buffer (find-file-noselect full-path)) + (window (display-buffer + buffer '(display-buffer-reuse-window + display-buffer-pop-up-window)))) + (with-selected-window window + (let ((pos (or (byte-to-position (1+ offset)) (1+ offset)))) + (goto-char pos) + (when (fboundp 'pulse-momentary-highlight-one-line) + (pulse-momentary-highlight-one-line pos)))) + (when select-window-p + (select-window window))))) + (user-error "No match found on the current line")))) + +(defun grab-goto-match () + "Go to match on current line and select its window." + (interactive) + (grab-display-match :select-window-p)) + +(defun grab--search-forward () + (catch 'found + (while (re-search-forward grab--header-regexp nil :noerror) + (when (grab--valid-match-p) + (throw 'found t))) + nil)) + +(defun grab--search-backward () + (catch 'found + (while (re-search-backward grab--header-regexp nil :noerror) + (when (grab--valid-match-p) + (throw 'found t))) + nil)) + +(defun grab-next-match () + "Move to the next match and display it." + (interactive) + (forward-line 1) + (if (grab--search-forward) + (progn + (beginning-of-line) + (grab-display-match)) + (forward-line -1) + (message "No more matches"))) + +(defun grab-prev-match () + "Move to the previous match and display it." + (interactive) + (forward-line -1) + (if (grab--search-backward) + (progn + (beginning-of-line) + (grab-display-match)) + (forward-line 1) + (message "No previous matches"))) + +(defun grab-next-error (&optional arg reset) + "TODO" + (interactive "p") + (when reset + (goto-char (point-min))) + (let ((direction (if (< arg 0) -1 +1)) + (count (abs arg))) + (dotimes (_ count) + (if (> direction 0) + (progn + (end-of-line) + (unless (grab--search-forward) + (error "No more matches"))) + (beginning-of-line) + (unless (grab--search-backward) + (error "No previous matches")))) + (beginning-of-line) + (grab-goto-match))) + + +;;; Interactive Commands + +;;;###autoload +(defun grab (pattern) + "Run grab with PATTERN in the current directory." + (interactive + (list (read-string (format-prompt "Grab Pattern" nil) grab-default-pattern))) + (grab--run-process + (flatten-tree (list grab-command grab-command-arguments pattern)) + "*grab*")) + +;;;###autoload +(defun git-grab (pattern) + "Run git grab with PATTERN in the current directory." + (interactive + (list (read-string (format-prompt "Grab Pattern" nil) grab-default-pattern))) + (grab--run-process + (flatten-tree (list git-grab-command git-grab-command-arguments pattern)) + "*grab*")) + +;;;###autoload +(defun project-grab (pattern) + "Run grab with PATTERN at the project root." + (interactive + (list (read-string (format-prompt "Grab Pattern" nil) grab-default-pattern))) + (let* ((project (project-current t)) + (default-directory (project-root project))) + (grab--run-process + (flatten-tree (list grab-command grab-command-arguments pattern)) + "*grab*"))) + +;;;###autoload +(defun project-git-grab (pattern) + "Run git grab with PATTERN at the project root." + (interactive + (list (read-string (format-prompt "Grab Pattern" nil) grab-default-pattern))) + (let* ((project (project-current t)) + (default-directory (project-root project))) + (grab--run-process + (flatten-tree (list git-grab-command git-grab-command-arguments pattern)) + "*grab*"))) + +(provide 'grab) +;;; grab.el ends here -- cgit v1.2.3 From 46e4626e4a9326fd32161459db1a22af7b791902 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:34:58 +0100 Subject: emacs: Map ‘C-x p G’ to ‘project-git-grab’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/emacs/modules/mm-keybindings.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/emacs/modules/mm-keybindings.el b/.config/emacs/modules/mm-keybindings.el index 0df92ce..929dd02 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 -- cgit v1.2.3 From cb3a583e6969262ecab14f026922492b4594a2f7 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:35:34 +0100 Subject: emacs: Add ‘mm-humanwave-insert-last-commit-message’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/emacs/modules/mm-humanwave.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.config/emacs/modules/mm-humanwave.el b/.config/emacs/modules/mm-humanwave.el index f9e59b4..2e58b13 100644 --- a/.config/emacs/modules/mm-humanwave.el +++ b/.config/emacs/modules/mm-humanwave.el @@ -158,4 +158,14 @@ 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))))) + (provide 'mm-humanwave) -- cgit v1.2.3 From 4052c02106550f7a33677d28103310fd46b791fd Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:36:40 +0100 Subject: emacs: Add Rust commenting support --- .config/emacs/modules/mm-editing.el | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) 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 -- cgit v1.2.3 From b64b74516c0e5dd3fa153770fe90c813baa6aa68 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:36:50 +0100 Subject: emacs: Map ‘C-c m’ to ‘mm-humanwave-insert-last-commit-message’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/emacs/modules/mm-keybindings.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.config/emacs/modules/mm-keybindings.el b/.config/emacs/modules/mm-keybindings.el index 929dd02..aae9b4d 100644 --- a/.config/emacs/modules/mm-keybindings.el +++ b/.config/emacs/modules/mm-keybindings.el @@ -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 -- cgit v1.2.3 From 5b19afdbedaaccd55e24888df804ea2122f702ac Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:37:17 +0100 Subject: emacs: Jira integration --- .config/emacs/modules/mm-humanwave.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.config/emacs/modules/mm-humanwave.el b/.config/emacs/modules/mm-humanwave.el index 2e58b13..38fb7f5 100644 --- a/.config/emacs/modules/mm-humanwave.el +++ b/.config/emacs/modules/mm-humanwave.el @@ -168,4 +168,17 @@ to the `project-find-file' command." (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)) + (provide 'mm-humanwave) -- cgit v1.2.3 From 079822d7974d0b17706799f2d01863be9a1e8341 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:37:24 +0100 Subject: emacs: Humanwave Icon completion --- .config/emacs/modules/mm-humanwave.el | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/.config/emacs/modules/mm-humanwave.el b/.config/emacs/modules/mm-humanwave.el index 38fb7f5..54e4a4b 100644 --- a/.config/emacs/modules/mm-humanwave.el +++ b/.config/emacs/modules/mm-humanwave.el @@ -181,4 +181,74 @@ to the `project-find-file' command." (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 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 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))) + (error "Unable to find ICON_MAP definitions"))) + (provide 'mm-humanwave) -- cgit v1.2.3 From 754fccfdc4820e098f6ec53cc532b1aea52ce1d0 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:37:35 +0100 Subject: emacs: Configure ‘grab’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/emacs/modules/mm-search.el | 6 ++++++ 1 file changed, 6 insertions(+) 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) -- cgit v1.2.3 From 2aae06f23408be081a451de38111dd32c1cf07e7 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:38:09 +0100 Subject: emacs: Use global-completion-previde-mode --- .config/emacs/modules/mm-completion.el | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.config/emacs/modules/mm-completion.el b/.config/emacs/modules/mm-completion.el index 79da064..74740b4 100644 --- a/.config/emacs/modules/mm-completion.el +++ b/.config/emacs/modules/mm-completion.el @@ -158,4 +158,12 @@ :custom (find-library-include-other-files nil)) -(provide 'mm-completion) \ No newline at end of file + +;;; 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) -- cgit v1.2.3 From 9049047797a619df4ba5383b73a4cf83988aa30e Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:38:36 +0100 Subject: emacs: Implement ‘mm-camel-to-lisp’ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/emacs/init.el | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.config/emacs/init.el b/.config/emacs/init.el index eb0f23e..0f917c7 100644 --- a/.config/emacs/init.el +++ b/.config/emacs/init.el @@ -73,6 +73,15 @@ This function is meant to be used in conjuction with `read-string' and (string-to-number string-or-number) string-or-number)) +(defun mm-camel-to-lisp (string) + "Convert STRING from camelCase to lisp-case." + (declare (ftype (function (string) string)) + (pure t) (side-effect-free t)) + (let ((case-fold-search nil)) + (downcase + (replace-regexp-in-string + (rx (group (or lower digit)) (group upper)) "\\1-\\2" string)))) + (defun mm-do-and-center (function &rest arguments) "Call FUNCTION with ARGUMENTS and then center the screen." (apply function arguments) -- cgit v1.2.3 From c5797f6a1e6803a5eeff5a251c5ec7bbe91dd298 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:38:50 +0100 Subject: emacs: Use cape for CAPF functions --- .config/emacs/modules/mm-completion.el | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.config/emacs/modules/mm-completion.el b/.config/emacs/modules/mm-completion.el index 74740b4..8ff2894 100644 --- a/.config/emacs/modules/mm-completion.el +++ b/.config/emacs/modules/mm-completion.el @@ -158,6 +158,23 @@ :custom (find-library-include-other-files nil)) + +;;; 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 -- cgit v1.2.3 From 4e0beba3438a9132eb4e049f53bd9a66d74645b2 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 20 Mar 2026 16:39:14 +0100 Subject: emacs: Support marginalia for Git branches --- .config/emacs/mango-theme.el | 6 +++ .config/emacs/modules/mm-completion.el | 72 +++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/.config/emacs/mango-theme.el b/.config/emacs/mango-theme.el index 5fec2e0..f18f4c0 100644 --- a/.config/emacs/mango-theme.el +++ b/.config/emacs/mango-theme.el @@ -175,6 +175,12 @@ graphically, so I shouldn’t need to have multiple specs per face. (marginalia-documentation :foreground ,(mango-theme--color 'disabled) :underline nil) + (mm-diffstat-counter-added + :foreground "green" + :weight bold) + (mm-diffstat-counter-removed + :foreground "red" + :weight bold) ;; Tempel (tempel-default diff --git a/.config/emacs/modules/mm-completion.el b/.config/emacs/modules/mm-completion.el index 8ff2894..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)) -- cgit v1.2.3 From 36de7356cfbf742a9ce72146f758f288b3f7880b Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Sun, 8 Mar 2026 01:39:47 +0100 Subject: bash: Fix config for Niri --- .bash_profile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.bash_profile b/.bash_profile index 79a3224..45e7036 100644 --- a/.bash_profile +++ b/.bash_profile @@ -1,3 +1,8 @@ +readonly tty="$(tty)" [[ -f ~/.bashrc ]] && source ~/.bashrc -[[ -z "$DISPLAY" && `tty` == /dev/tty1 ]] && exec start-hyprland -[[ -z "$DISPLAY" && `tty` == /dev/tty2 ]] && exec niri-session +if [[ -z "$DISPLAY" && -z "$NIRI_LOADED" && "$tty" = /dev/tty1 ]] +then + export NIRI_LOADED=1 + exec niri-session +fi +[[ -z "$DISPLAY" && "$tty" = /dev/tty2 ]] && exec start-hyprland -- cgit v1.2.3 From 9c1f5711ffd8aa1ca2556d28b35a7cc8eff1027c Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Sun, 8 Mar 2026 01:40:35 +0100 Subject: niri: Add Niri configurations --- .config/niri/config.kdl | 301 +++++++++++++++++++++++++++++++++++++++ .config/niri/dms/alttab.kdl | 10 ++ .config/niri/dms/binds.kdl | 0 .config/niri/dms/colors.kdl | 39 +++++ .config/niri/dms/cursor.kdl | 0 .config/niri/dms/layout.kdl | 22 +++ .config/niri/dms/outputs.kdl | 13 ++ .config/niri/dms/windowrules.kdl | 0 .config/niri/dms/wpblur.kdl | 9 ++ 9 files changed, 394 insertions(+) create mode 100644 .config/niri/config.kdl create mode 100644 .config/niri/dms/alttab.kdl create mode 100644 .config/niri/dms/binds.kdl create mode 100644 .config/niri/dms/colors.kdl create mode 100644 .config/niri/dms/cursor.kdl create mode 100644 .config/niri/dms/layout.kdl create mode 100644 .config/niri/dms/outputs.kdl create mode 100644 .config/niri/dms/windowrules.kdl create mode 100644 .config/niri/dms/wpblur.kdl diff --git a/.config/niri/config.kdl b/.config/niri/config.kdl new file mode 100644 index 0000000..a8f3d7b --- /dev/null +++ b/.config/niri/config.kdl @@ -0,0 +1,301 @@ +include "dms/alttab.kdl" +include "dms/binds.kdl" +include "dms/cursor.kdl" +include "dms/layout.kdl" +include "dms/outputs.kdl" +include "dms/windowrules.kdl" +include "dms/wpblur.kdl" + +// https://yalter.github.io/niri/Configuration:-Input +input { + keyboard { + xkb { + layout "mango,mango,us" + variant "basic,swedish,basic" + options "lv3:switch,compose:ralt" + } + + repeat-delay 360 + repeat-rate 35 + numlock + } + + touchpad { + tap + dwt + drag true + natural-scroll + accel-speed 0.2 + accel-profile "adaptive" + scroll-method "two-finger" + } + + mouse { + natural-scroll + accel-speed 0.2 + accel-profile "adaptive" + } + + focus-follows-mouse + workspace-auto-back-and-forth +} + +output "DP-2" { + focus-at-startup +} + +layout { + gaps 12 + background-color "#2B303B" + + always-center-single-column + center-focused-column "on-overflow" + + preset-column-widths { + proportion 0.33333 + proportion 0.5 + proportion 0.66667 + } + + default-column-width { + proportion 0.5 + } + + focus-ring { + off + } + + border { + width 2 + active-gradient from="#FAA14F" to="#B8F182" angle=150 relative-to="workspace-view" + inactive-color "#363C4A" + urgent-color "#841A11" + } + + shadow { + on + draw-behind-window true + + softness 30 + spread 25 + offset x=8 y=8 + color "#0007" + } + + default-column-display "tabbed" + tab-indicator { + hide-when-single-tab + width 4 + length total-proportion=0.9 + corner-radius 16 + active-gradient from="#FAA14F" to="#B8F182" angle=150 + inactive-color "#363C4A" + urgent-color "#841A11" + } +} + +hotkey-overlay { + skip-at-startup +} + +prefer-no-csd + +screenshot-path "~/media/gfx/screen/%Y-%m-%d_%H-%M-%S.png" + +// Animation settings. +// The wiki explains how to configure individual animations: +// https://yalter.github.io/niri/Configuration:-Animations +animations { + // Uncomment to turn off all animations. + // off + + // Slow down all animations by this factor. Values below 1 speed them up instead. + // slowdown 3.0 +} + +// Window rules let you adjust behavior for individual windows. +// Find more information on the wiki: +// https://yalter.github.io/niri/Configuration:-Window-Rules + +// Open the Firefox picture-in-picture player as floating by default. +window-rule { + // This app-id regular expression will work for both: + // - host Firefox (app-id is "firefox") + // - Flatpak Firefox (app-id is "org.mozilla.firefox") + match app-id=r#"firefox$"# title="^Picture-in-Picture$" + open-floating true +} + +// Example: block out two password managers from screen capture. +// (This example rule is commented out with a "/-" in front.) +/-window-rule { + match app-id=r#"^org\.keepassxc\.KeePassXC$"# + match app-id=r#"^org\.gnome\.World\.Secrets$"# + + block-out-from "screen-capture" + + // Use this instead if you want them visible on third-party screenshot tools. + // block-out-from "screencast" +} + +// Example: enable rounded corners for all windows. +// (This example rule is commented out with a "/-" in front.) +/-window-rule { + geometry-corner-radius 12 + clip-to-geometry true +} + +binds { + Mod+Shift+Slash { show-hotkey-overlay; } + XF86Tools { spawn "emacsclient" "-ca" "emacs" "/home/thomas/.config"; } + + /* Applikationer */ + // Mod+C hotkey-overlay-title="Calculator" { + // spawn "qalculate-gtk" + // } + // Mod+E hotkey-overlay-title="Editor" { + // spawn "emacsclient" "-ca" "emacs" + // } + // Mod+M hotkey-overlay-title="Email" { + // spawn "foot" "aerc" + // } + // Mod+W hotkey-overlay-title="Web Browser" { + // spawn "firefox" + // } + Mod+Return hotkey-overlay-title="Terminal" { + spawn "foot" + } + Mod+Space hotkey-overlay-title="App Launcher" { + spawn "fuzzel" "-I" "--log-level=warning" "--show-actions" + } + + /- Super+Alt+L hotkey-overlay-title="Screenlock" { + spawn "swaylock" + } + + /* Ljudkontroll */ + XF86AudioRaiseVolume allow-when-locked=true { + spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.1+" "-l" "1.0" + } + XF86AudioLowerVolume allow-when-locked=true { + spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.1-" + } + XF86AudioMute allow-when-locked=true { + spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle" + } + XF86AudioMicMute allow-when-locked=true { + spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle" + } + + /* Mediakontroll */ + XF86AudioPlay allow-when-locked=true { + spawn "playerctl" "play-pause" + } + XF86AudioStop allow-when-locked=true { + spawn "playerctl" "stop" + } + XF86AudioPrev allow-when-locked=true { + spawn "playerctl" "previous" + } + XF86AudioNext allow-when-locked=true { + spawn "playerctl" "next" + } + + XF86MonBrightnessUp allow-when-locked=true { + spawn "brightnessctl" "--class=backlight" "set" "+10%" + } + XF86MonBrightnessDown allow-when-locked=true { + spawn "brightnessctl" "--class=backlight" "set" "10%-" + } + + Mod+Q repeat=false { close-window; } + + Mod+Left { focus-column-left; } + Mod+Down { focus-window-down; } + Mod+Up { focus-window-up; } + Mod+Right { focus-column-right; } + Mod+F { focus-column-right; } + Mod+B { focus-column-left; } + Mod+N { focus-window-down-or-top; } + Mod+P { focus-window-up-or-bottom; } + + Mod+Ctrl+Left { move-column-left; } + Mod+Ctrl+Down { move-window-down; } + Mod+Ctrl+Up { move-window-up; } + Mod+Ctrl+Right { move-column-right; } + Mod+Ctrl+F { move-column-right; } + Mod+Ctrl+B { move-column-left; } + Mod+Ctrl+N { move-window-down; } + Mod+Ctrl+P { move-window-up; } + + Mod+Home { focus-column-first; } + Mod+End { focus-column-last; } + Mod+Ctrl+Home { move-column-to-first; } + Mod+Ctrl+End { move-column-to-last; } + Mod+A { focus-column-first; } + Mod+E { focus-column-last; } + Mod+Ctrl+A { move-column-to-first; } + Mod+Ctrl+E { move-column-to-last; } + + Mod+Shift+Left { focus-monitor-left; } + Mod+Shift+Down { focus-monitor-down; } + Mod+Shift+Up { focus-monitor-up; } + Mod+Shift+Right { focus-monitor-right; } + + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+6 { focus-workspace 6; } + Mod+7 { focus-workspace 7; } + Mod+8 { focus-workspace 8; } + Mod+9 { focus-workspace 9; } + Mod+Shift+1 { focus-workspace 1; } + Mod+Shift+2 { focus-workspace 2; } + Mod+Shift+3 { focus-workspace 3; } + Mod+Shift+4 { focus-workspace 4; } + Mod+Shift+5 { focus-workspace 5; } + Mod+Shift+6 { focus-workspace 6; } + Mod+Shift+7 { focus-workspace 7; } + Mod+Shift+8 { focus-workspace 8; } + Mod+Shift+9 { focus-workspace 9; } + + Mod+Ctrl+1 { move-column-to-workspace 1; } + Mod+Ctrl+2 { move-column-to-workspace 2; } + Mod+Ctrl+3 { move-column-to-workspace 3; } + Mod+Ctrl+4 { move-column-to-workspace 4; } + Mod+Ctrl+5 { move-column-to-workspace 5; } + Mod+Ctrl+6 { move-column-to-workspace 6; } + Mod+Ctrl+7 { move-column-to-workspace 7; } + Mod+Ctrl+8 { move-column-to-workspace 8; } + Mod+Ctrl+9 { move-column-to-workspace 9; } + Mod+Ctrl+Shift+1 { move-column-to-workspace 1; } + Mod+Ctrl+Shift+2 { move-column-to-workspace 2; } + Mod+Ctrl+Shift+3 { move-column-to-workspace 3; } + Mod+Ctrl+Shift+4 { move-column-to-workspace 4; } + Mod+Ctrl+Shift+5 { move-column-to-workspace 5; } + Mod+Ctrl+Shift+6 { move-column-to-workspace 6; } + Mod+Ctrl+Shift+7 { move-column-to-workspace 7; } + Mod+Ctrl+Shift+8 { move-column-to-workspace 8; } + Mod+Ctrl+Shift+9 { move-column-to-workspace 9; } + + Mod+Comma { consume-window-into-column; } + Mod+Period { expel-window-from-column; } + + Mod+Ctrl+M { maximize-column; } + Mod+Ctrl+Shift+M { fullscreen-window; } + Mod+L { center-window; } + Mod+R { switch-preset-column-width; } + Mod+W { toggle-column-tabbed-display; } + Mod+O { toggle-overview; } + + Print { screenshot; } + Ctrl+Print { screenshot-screen; } + Alt+Print { screenshot-window; } + + Mod+Shift+Comma { focus-workspace-up; } + Mod+Shift+Period { focus-workspace-down; } + + Mod+Ctrl+G allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; } +} diff --git a/.config/niri/dms/alttab.kdl b/.config/niri/dms/alttab.kdl new file mode 100644 index 0000000..091ea07 --- /dev/null +++ b/.config/niri/dms/alttab.kdl @@ -0,0 +1,10 @@ +// ! DO NOT EDIT ! + // ! AUTO-GENERATED BY DMS ! + // ! CHANGES WILL BE OVERWRITTEN ! + // ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE ! + + recent-windows { + highlight { + corner-radius 12 + } + } diff --git a/.config/niri/dms/binds.kdl b/.config/niri/dms/binds.kdl new file mode 100644 index 0000000..e69de29 diff --git a/.config/niri/dms/colors.kdl b/.config/niri/dms/colors.kdl new file mode 100644 index 0000000..7e6101c --- /dev/null +++ b/.config/niri/dms/colors.kdl @@ -0,0 +1,39 @@ +// ! Auto-generated file. Do not edit directly. +// Remove `include "dms/colors.kdl"` from your config to override. + +layout { + background-color "transparent" + + focus-ring { + active-color "#42a5f5" + inactive-color "#8c9199" + urgent-color "#f2b8b5" + } + + border { + active-color "#42a5f5" + inactive-color "#8c9199" + urgent-color "#f2b8b5" + } + + shadow { + color "#00000070" + } + + tab-indicator { + active-color "#42a5f5" + inactive-color "#8c9199" + urgent-color "#f2b8b5" + } + + insert-hint { + color "#42a5f580" + } +} + +recent-windows { + highlight { + active-color "#0d47a1" + urgent-color "#f2b8b5" + } +} diff --git a/.config/niri/dms/cursor.kdl b/.config/niri/dms/cursor.kdl new file mode 100644 index 0000000..e69de29 diff --git a/.config/niri/dms/layout.kdl b/.config/niri/dms/layout.kdl new file mode 100644 index 0000000..431afbf --- /dev/null +++ b/.config/niri/dms/layout.kdl @@ -0,0 +1,22 @@ +// ! DO NOT EDIT ! + // ! AUTO-GENERATED BY DMS ! + // ! CHANGES WILL BE OVERWRITTEN ! + // ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE ! + + layout { + gaps 4 + + border { + width 2 + } + + focus-ring { + width 2 + } + } + window-rule { + geometry-corner-radius 12 + clip-to-geometry true + tiled-state true + draw-border-with-background false + } diff --git a/.config/niri/dms/outputs.kdl b/.config/niri/dms/outputs.kdl new file mode 100644 index 0000000..8a92851 --- /dev/null +++ b/.config/niri/dms/outputs.kdl @@ -0,0 +1,13 @@ +// Auto-generated by DMS - do not edit manually + +output "DP-2" { + mode "5120x1440@144.000" + scale 1 + position x=0 y=0 +} + +output "eDP-1" { + mode "2256x1504@60.000" + scale 1 + position x=1432 y=1440 +} diff --git a/.config/niri/dms/windowrules.kdl b/.config/niri/dms/windowrules.kdl new file mode 100644 index 0000000..e69de29 diff --git a/.config/niri/dms/wpblur.kdl b/.config/niri/dms/wpblur.kdl new file mode 100644 index 0000000..3d58802 --- /dev/null +++ b/.config/niri/dms/wpblur.kdl @@ -0,0 +1,9 @@ +// ! DO NOT EDIT ! +// ! AUTO-GENERATED BY DMS ! +// ! CHANGES WILL BE OVERWRITTEN ! +// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE ! + +layer-rule { + match namespace="dms:blurwallpaper" + place-within-backdrop true +} -- cgit v1.2.3 From 25447e3f3c55150323180a74aa2ad4dc627b68c8 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Sun, 8 Mar 2026 06:52:54 +0100 Subject: iosevka: Add my Iosevka config --- .config/iosevka-smooth/private-build-plans.toml | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .config/iosevka-smooth/private-build-plans.toml diff --git a/.config/iosevka-smooth/private-build-plans.toml b/.config/iosevka-smooth/private-build-plans.toml new file mode 100644 index 0000000..2fca1fc --- /dev/null +++ b/.config/iosevka-smooth/private-build-plans.toml @@ -0,0 +1,93 @@ +[buildPlans.iosevka-smooth] +family = "Iosevka Smooth" +spacing = "normal" +serifs = "sans" +no-cv-ss = false +export-glyph-names = true + +[buildPlans.iosevka-smooth.variants.design] +capital-g = "toothless-corner-serifless-hooked" +capital-k = "symmetric-touching-serifless" +capital-q = "crossing" +a = "double-storey-toothless-corner" +b = "toothless-corner-serifless" +d = "toothless-corner-serifless" +g = "single-storey-earless-corner" +i = "tailed-serifed" +l = "tailed-serifed" +m = "earless-corner-double-arch-serifless" +n = "earless-corner-straight-serifless" +p = "earless-corner-serifless" +q = "earless-corner-straight-serifless" +u = "toothless-corner-serifless" +long-s = "bent-hook-middle-serifed-xh" +eszet = "sulzbacher-descending" +lower-delta = "flat-top" +lower-mu = "toothless-corner-serifless" +zero = "tall-slashed" +two = "straight-neck" +three = "flat-top" +brace = "straight" +ampersand = "et-toothless-corner" +percent = "rings-continuous-slash" +partial-derivative = "straight-bar" +lower-eth = "straight-bar" +micro-sign = "toothless-corner-serifless" +lig-neq = "more-slanted" +lig-equal-chain = "without-notch" +lig-hyphen-chain = "without-notch" + +[buildPlans.iosevka-smooth.variants.italic] +k = "straight-serifless" +long-s = "flat-hook-tailed" +eszet = "sulzbacher-tailed" + +[buildPlans.iosevka-smooth.ligations] +inherits = "dlig" + +[buildPlans.iosevka-smooth-term] +family = "Iosevka Smooth Term" +spacing = "term" +serifs = "sans" +no-cv-ss = false +export-glyph-names = true + +[buildPlans.iosevka-smooth-term.variants.design] +capital-g = "toothless-corner-serifless-hooked" +capital-k = "symmetric-touching-serifless" +capital-q = "crossing" +a = "double-storey-toothless-corner" +b = "toothless-corner-serifless" +d = "toothless-corner-serifless" +g = "single-storey-earless-corner" +i = "tailed-serifed" +l = "tailed-serifed" +m = "earless-corner-double-arch-serifless" +n = "earless-corner-straight-serifless" +p = "earless-corner-serifless" +q = "earless-corner-straight-serifless" +u = "toothless-corner-serifless" +long-s = "bent-hook-middle-serifed-xh" +eszet = "sulzbacher-descending" +lower-delta = "flat-top" +lower-mu = "toothless-corner-serifless" +zero = "tall-slashed" +two = "straight-neck" +three = "flat-top" +brace = "straight" +ampersand = "et-toothless-corner" +percent = "rings-continuous-slash" +partial-derivative = "straight-bar" +lower-eth = "straight-bar" +micro-sign = "toothless-corner-serifless" +lig-neq = "more-slanted" +lig-equal-chain = "without-notch" +lig-hyphen-chain = "without-notch" + +[buildPlans.iosevka-smooth-term.variants.italic] +k = "straight-serifless" +long-s = "flat-hook-tailed" +eszet = "sulzbacher-tailed" + +[buildPlans.iosevka-smooth-term.ligations] +inherits = "dlig" -- cgit v1.2.3