| 1 | ;;; init-tools.el -*- lexical-binding: t; -*- |
| 2 | |
| 3 | (use-package magit |
| 4 | :defer t) |
| 5 | |
| 6 | (use-package diff-hl |
| 7 | :hook ((after-init . global-diff-hl-mode) |
| 8 | (dired-mode . diff-hl-dired-mode) |
| 9 | (magit-pre-refresh . diff-hl-magit-pre-refresh) |
| 10 | (magit-post-refresh . diff-hl-magit-post-refresh)) |
| 11 | :custom |
| 12 | (diff-hl-margin-symbols-alist |
| 13 | '((insert . "+") (delete . "-") (change . "~") |
| 14 | (unknown . "?") (ignored . "i") (reference . " "))) |
| 15 | (diff-hl-draw-borders nil) |
| 16 | :config |
| 17 | (diff-hl-flydiff-mode 1) |
| 18 | (diff-hl-margin-mode 1) |
| 19 | ;; base16-theme already defines these faces, so the theme file can't |
| 20 | ;; override them; set them here instead. |
| 21 | (custom-set-faces |
| 22 | '(diff-hl-insert ((t (:foreground "#fbcb97" :background "#fbcb97")))) |
| 23 | '(diff-hl-change ((t (:foreground "#e78a53" :background "#e78a53")))) |
| 24 | '(diff-hl-delete ((t (:foreground "#aa6c6c" :background "#aa6c6c")))) |
| 25 | ;; margin text: color the symbol only, no filled background |
| 26 | '(diff-hl-margin-insert ((t (:foreground "#fbcb97" :background unspecified :weight bold)))) |
| 27 | '(diff-hl-margin-change ((t (:foreground "#e78a53" :background unspecified :weight bold)))) |
| 28 | '(diff-hl-margin-delete ((t (:foreground "#aa6c6c" :background unspecified :weight bold)))))) |
| 29 | |
| 30 | (use-package elfeed |
| 31 | :defer t |
| 32 | :config |
| 33 | (setq-default elfeed-search-filter "@6-months-ago") |
| 34 | |
| 35 | (defvar my/elfeed-feeds-url "https://feeds.stevedylan.dev/feeds?format=json" |
| 36 | "Endpoint returning my subscriptions as JSON.") |
| 37 | |
| 38 | (defun my/elfeed-refresh-feeds () |
| 39 | "Replace `elfeed-feeds' with the list from `my/elfeed-feeds-url'. |
| 40 | Keeps the current list if the fetch fails." |
| 41 | (interactive) |
| 42 | (condition-case err |
| 43 | (let ((buf (url-retrieve-synchronously my/elfeed-feeds-url t t 10))) |
| 44 | (unless buf (error "no response")) |
| 45 | (with-current-buffer buf |
| 46 | (goto-char (point-min)) |
| 47 | (re-search-forward "\n\n") |
| 48 | (let* ((data (json-parse-buffer :object-type 'alist :array-type 'list)) |
| 49 | (urls (mapcar (lambda (s) (alist-get 'url s)) |
| 50 | (alist-get 'subscriptions data)))) |
| 51 | (kill-buffer buf) |
| 52 | (setq elfeed-feeds urls) |
| 53 | (message "elfeed: loaded %d feeds" (length urls))))) |
| 54 | (error (message "elfeed: feed refresh failed (%s), keeping existing list" |
| 55 | (error-message-string err))))) |
| 56 | |
| 57 | (advice-add 'elfeed-update :before #'my/elfeed-refresh-feeds) |
| 58 | |
| 59 | (defun my/elfeed-show-font () |
| 60 | (face-remap-add-relative 'variable-pitch :family "BerkeleyMono Nerd Font Mono" :height 140) |
| 61 | (face-remap-add-relative 'default :family "BerkeleyMono Nerd Font Mono" :height 140)) |
| 62 | (add-hook 'elfeed-show-mode-hook #'my/elfeed-show-font)) |
| 63 | |
| 64 | (use-package ob-go |
| 65 | :defer t) |
| 66 | |
| 67 | (use-package org |
| 68 | :ensure nil |
| 69 | :defer t |
| 70 | :config |
| 71 | (require 'ob-go) |
| 72 | (org-babel-do-load-languages |
| 73 | 'org-babel-load-languages |
| 74 | '((js . t) |
| 75 | (shell . t) |
| 76 | (go . t))) |
| 77 | |
| 78 | (setq org-directory "~/org/" |
| 79 | org-default-notes-file (expand-file-name "inbox.org" org-directory) |
| 80 | org-capture-templates |
| 81 | '(("t" "Todo" entry (file+headline "inbox.org" "Tasks") |
| 82 | "* TODO %?\n%i\n%a" :empty-lines 1) |
| 83 | ("n" "Note" entry (file+headline "inbox.org" "Notes") |
| 84 | "* %?\n%U\n%i" :empty-lines 1) |
| 85 | ("j" "Journal" entry (file+olp+datetree "journal.org") |
| 86 | "* %U %?\n%i" :empty-lines 1) |
| 87 | ("b" "Bookmark" entry (file "bookmarks.org") |
| 88 | "* [[%^{URL}][%^{Title}]] %^g\n:PROPERTIES:\n:ADDED: %U\n:END:\n%?" |
| 89 | :empty-lines 1)))) |
| 90 | |
| 91 | (use-package dired |
| 92 | :ensure nil |
| 93 | :custom |
| 94 | (dired-kill-when-opening-new-dired-buffer t) ; reuse one buffer |
| 95 | (dired-dwim-target t) |
| 96 | :hook (dired-mode . dired-omit-mode) ; hide dotfiles by default |
| 97 | :config |
| 98 | (require 'dired-x) |
| 99 | (setq dired-omit-files "\\`[.][^.]") ; dotfiles, but keep ".." |
| 100 | (evil-collection-define-key 'normal 'dired-mode-map |
| 101 | "." 'dired-omit-mode ; toggle dotfiles |
| 102 | "h" 'dired-up-directory |
| 103 | "l" 'dired-find-file |
| 104 | "q" 'quit-window)) |
| 105 | |
| 106 | (autoload 'posts-compose "posts" nil t) |
| 107 | (autoload 'uguu-upload-file "uguu" nil t) |
| 108 | (autoload 'uguu-upload-clipboard "uguu" nil t) |
| 109 | |
| 110 | (defgroup openrouter nil |
| 111 | "OpenRouter backend for gptel." |
| 112 | :group 'gptel) |
| 113 | |
| 114 | (defcustom openrouter-pass-entry "openrouter/openrouter-api-key" |
| 115 | "Name of the `pass' entry holding the API key." |
| 116 | :type 'string |
| 117 | :group 'openrouter) |
| 118 | |
| 119 | (defun openrouter--api-key () |
| 120 | "Return the OpenRouter API key from password-store." |
| 121 | (require 'auth-source-pass) ; autoloaded in Emacs 28+, but cheap to be explicit |
| 122 | (let ((key (auth-source-pass-get 'secret openrouter-pass-entry))) |
| 123 | (or (and key (string-trim key)) |
| 124 | (user-error "No pass entry `%s'" openrouter-pass-entry)))) |
| 125 | |
| 126 | (use-package gptel |
| 127 | :defer t |
| 128 | :config |
| 129 | (setq gptel-backend |
| 130 | (gptel-make-openai "OpenRouter" |
| 131 | :host "openrouter.ai" |
| 132 | :endpoint "/api/v1/chat/completions" |
| 133 | :stream t |
| 134 | :key #'openrouter--api-key |
| 135 | :request-params '(:tools [(:type "openrouter:web_search") |
| 136 | (:type "openrouter:web_fetch")]) |
| 137 | :models '(z-ai/glm-5.3 |
| 138 | z-ai/glm-5.3-flash |
| 139 | z-ai/glm-5.3-flashx)))) |
| 140 | |
| 141 | (defun gptel-model (model) |
| 142 | "Set the gptel model in the current buffer." |
| 143 | (interactive |
| 144 | (list (intern (completing-read "Model: " |
| 145 | '("z-ai/glm-5.3" |
| 146 | "z-ai/glm-5.3-flash" |
| 147 | "z-ai/glm-5.3-flashx"))))) |
| 148 | (setq-local gptel-model model)) |
| 149 | |
| 150 | (use-package markdown-mode |
| 151 | :defer t) |
| 152 | |
| 153 | |
| 154 | (provide 'init-tools) |