emacs: experiments with review workflow; update some packages

This commit is contained in:
Pavel Korytov 2025-01-23 12:15:30 +03:00
parent 327340a95c
commit 3e4efbae78
3 changed files with 643 additions and 453 deletions

View file

@ -8,7 +8,7 @@
"imagemagick" "imagemagick"
"font-gnu-freefont" "font-gnu-freefont"
"font-gnu-unifont" "font-gnu-unifont"
"tdlib-1.8.16" "emacs-telega-sever"
"yt-dlp" "yt-dlp"
"mpv" "mpv"
"python-youtube-transcript-api" "python-youtube-transcript-api"

View file

@ -1723,6 +1723,7 @@ targets."
(setq lsp-headerline-breadcrumb-enable nil) (setq lsp-headerline-breadcrumb-enable nil)
(setq lsp-modeline-code-actions-enable nil) (setq lsp-modeline-code-actions-enable nil)
(setq lsp-modeline-diagnostics-enable nil) (setq lsp-modeline-diagnostics-enable nil)
(setq lsp-volar-take-over-mode nil)
(add-to-list 'lsp-language-id-configuration '(svelte-mode . "svelte"))) (add-to-list 'lsp-language-id-configuration '(svelte-mode . "svelte")))
(use-package lsp-ui (use-package lsp-ui
@ -2166,7 +2167,7 @@ Returns (<buffer> . <workspace-index>) or nil."
(use-package copilot (use-package copilot
:straight (:host github :repo "copilot-emacs/copilot.el") :straight (:host github :repo "copilot-emacs/copilot.el")
:commands (copilot-mode) :commands (copilot-mode)
:if (not (or my/remote-server my/is-termux)) :disabled t
:init :init
(add-hook 'emacs-startup-hook (add-hook 'emacs-startup-hook
(lambda () (lambda ()
@ -2310,11 +2311,13 @@ Returns (<buffer> . <workspace-index>) or nil."
(add-hook 'web-mode-hook #'my/web-mode-lsp) (add-hook 'web-mode-hook #'my/web-mode-lsp)
(defun my/web-mode-vue-setup (&rest _) (defun my/web-mode-vue-setup (&rest _)
(when (string-match-p (rx ".vue" eos) (buffer-file-name)) (let ((filename (buffer-file-name)))
(setq-local web-mode-script-padding 0) (when (and (stringp filename)
(setq-local web-mode-style-padding 0) (string-match-p (rx ".vue" eos) filename))
(setq-local create-lockfiles nil) (setq-local web-mode-script-padding 0)
(setq-local web-mode-enable-auto-pairing nil))) (setq-local web-mode-style-padding 0)
(setq-local create-lockfiles nil)
(setq-local web-mode-enable-auto-pairing nil))))
(add-hook 'web-mode-hook 'my/web-mode-vue-setup) (add-hook 'web-mode-hook 'my/web-mode-vue-setup)
(add-hook 'editorconfig-after-apply-functions 'my/web-mode-vue-setup) (add-hook 'editorconfig-after-apply-functions 'my/web-mode-vue-setup)
@ -2783,7 +2786,7 @@ Returns (<buffer> . <workspace-index>) or nil."
:config :config
(setq langtool-language-tool-server-jar "/home/pavel/bin/LanguageTool-6.4/languagetool-server.jar") (setq langtool-language-tool-server-jar "/home/pavel/bin/LanguageTool-6.4/languagetool-server.jar")
(setq langtool-mother-tongue "ru") (setq langtool-mother-tongue "ru")
(setq langtool-default-language "en-US")) (setq langtool-default-language "ru-RU"))
(my-leader-def (my-leader-def
:infix "L" :infix "L"
@ -3557,6 +3560,7 @@ With ARG, repeats or can move backward if negative."
`((emacs-lisp . t) `((emacs-lisp . t)
(python . t) (python . t)
(sql . t) (sql . t)
(sqlite . t)
;; (typescript .t) ;; (typescript .t)
(hy . t) (hy . t)
(shell . t) (shell . t)
@ -3946,6 +3950,9 @@ With ARG, repeats or can move backward if negative."
(with-eval-after-load 'org (with-eval-after-load 'org
(my-leader-def "ol" #'org-clock-agg)) (my-leader-def "ol" #'org-clock-agg))
:config :config
(setq org-clock-agg-node-format
"%-%(+ title-width)t %20c %8z %s/%S")
(setq org-clock-agg-node-title-width-delta 47)
(push (push
(cons "Agenda+Archive" (cons "Agenda+Archive"
(append (append
@ -4063,6 +4070,21 @@ With ARG, repeats or can move backward if negative."
:infix "SPC" :infix "SPC"
"C" #'my/org-clock-recent)) "C" #'my/org-clock-recent))
(defun my/org-fix-task-kind ()
(interactive)
(let ((entries (org-ql-query
:select #'element-with-markers
:from (current-buffer)
:where '(and (olp "Tasks")
(not (property "TASK_KIND"))
(clocked)))))
(org-fold-show-all)
(dolist (entry entries)
(let ((marker (org-element-property :org-marker entry)))
(org-with-point-at marker
(let ((value (org-read-property-value "TASK_KIND")))
(org-set-property "TASK_KIND" value)))))))
(use-package org-super-agenda (use-package org-super-agenda
:straight t :straight t
:after (org) :after (org)
@ -4150,6 +4172,30 @@ TYPE may be `ts', `ts-active', `ts-inactive', `clocked', or
:sort '(priority todo deadline) :sort '(priority todo deadline)
:super-groups '((:auto-outline-path-file t))))) :super-groups '((:auto-outline-path-file t)))))
(defun my/org-ql-clocked-today ()
(interactive)
(let ((today (format-time-string
"%Y-%m-%d"
(days-to-time
(- (org-today) (time-to-days 0))))))
(org-ql-search (org-agenda-files) `(clocked :from ,today)
:title "Clocked today"
:sort '(todo priority date)
:super-groups '((:auto-outline-path-file t)
(:auto-todo t)))))
(defun my/org-ql-closed-today ()
(interactive)
(let ((today (format-time-string
"%Y-%m-%d"
(days-to-time
(- (org-today) (time-to-days 0))))))
(org-ql-search (org-agenda-files) `(closed :from ,today)
:title "Closed today"
:sort '(todo priority date)
:super-groups '((:auto-outline-path-file t)
(:auto-todo t)))))
(setq org-ql-views (setq org-ql-views
(list (list
(cons "Overview: All TODO" #'my/org-ql-all-todo) (cons "Overview: All TODO" #'my/org-ql-all-todo)
@ -4171,13 +4217,8 @@ TYPE may be `ts', `ts-active', `ts-inactive', `clocked', or
:sort '(todo priority date) :sort '(todo priority date)
:super-groups '((:auto-outline-path-file t)))) :super-groups '((:auto-outline-path-file t))))
(cons "Review: Recently timestamped" #'my/org-ql-view-recent-items) (cons "Review: Recently timestamped" #'my/org-ql-view-recent-items)
(cons "Review: Unlinked to meetings" (cons "Review: Clocked today" #'my/org-ql-clocked-today)
(list :buffers-files #'org-agenda-files (cons "Review: Closed today" #'my/org-ql-closed-today)
:query '(and (todo "DONE" "NO")
(not (property "MEETING"))
(ts :from -7))
:super-groups '((:auto-outline-path-file t))))
(cons "Review: Meeting" #'my/org-ql-meeting-tasks)
(cons "Fix: tasks without TASK_KIND" (cons "Fix: tasks without TASK_KIND"
(lambda () (lambda ()
(interactive) (interactive)
@ -4522,7 +4563,7 @@ KEYS is a list of cons cells like (<label> . <time>)."
(thread-last (thread-last
heading heading
(substring-no-properties) (substring-no-properties)
(replace-regexp-in-string (rx (| "(" "[") (+ alnum) (| "]" ")")) "") (replace-regexp-in-string (rx (| "(" "[") (+ nonl) (| "]" ")")) "")
(replace-regexp-in-string (rx " " (+ (or digit "."))) " ") (replace-regexp-in-string (rx " " (+ (or digit "."))) " ")
(replace-regexp-in-string (rx (+ " ")) " ") (replace-regexp-in-string (rx (+ " ")) " ")
(string-trim))) (string-trim)))
@ -4774,6 +4815,11 @@ KEYS is a list of cons cells like (<label> . <time>)."
(add-hook 'org-journal-after-entry-create-hook (add-hook 'org-journal-after-entry-create-hook
#'my/set-journal-header) #'my/set-journal-header)
(defun my/org-journal-decrypt ()
"Decrypt the current org journal file."
(interactive)
(org-journal-tags--ensure-decrypted))
(use-package citar (use-package citar
:straight t :straight t
:init :init
@ -4841,21 +4887,61 @@ KEYS is a list of cons cells like (<label> . <time>)."
(setq org-roam-capture-templates (setq org-roam-capture-templates
`(("d" "default" plain "%?" `(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t)
("f" "fleeting" plain "%?"
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :fleeting:\n")
:unnarrowed t) :unnarrowed t)
("e" "encrypted" plain "%?" ("e" "encrypted" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org.gpg" "#+title: ${title}\n") :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org.gpg" "#+title: ${title}\n")
:unnarrowed t))) :unnarrowed t)))
(use-package org-roam-ql
:straight t
:after (org-roam)
:config
(general-define-key
:states '(normal visual)
:keymaps '(org-roam-ql-mode-map)
"s" #'org-roam-ql-buffer-dispatch))
(defun my/org-roam-node-find-permanent (&optional other-window)
(interactive current-prefix-arg)
(org-roam-node-find
other-window
nil
(lambda (node)
(not
(seq-contains-p
"fleeting"
(org-roam-node-tags node))))))
(defun my/org-roam-node-insert-permanent ()
(interactive)
(org-roam-node-insert
(lambda (node)
(not
(seq-contains-p
(org-roam-node-tags node)
"fleeting")))))
(defun my/org-roam-ql-fleeting ()
(interactive)
(org-roam-ql-search
'(tags "fleeting")
"Fleeting notes"))
(with-eval-after-load 'org-roam (with-eval-after-load 'org-roam
(my-leader-def (my-leader-def
:infix "or" :infix "or"
"" '(:which-key "org-roam") "" '(:which-key "org-roam")
"i" #'org-roam-node-insert "i" #'my/org-roam-node-insert-permanent
"r" #'org-roam-node-find "r" #'my/org-roam-node-find-permanent
"g" #'org-roam-graph "g" #'org-roam-graph
"c" #'org-roam-capture "c" #'org-roam-capture
"b" #'org-roam-buffer-toggle) "b" #'org-roam-buffer-toggle
"q" #'org-roam-ql-search
"f" #'my/org-roam-ql-fleeting)
(general-define-key (general-define-key
:keymaps 'org-roam-mode-map :keymaps 'org-roam-mode-map
:states '(normal) :states '(normal)
@ -4876,7 +4962,8 @@ KEYS is a list of cons cells like (<label> . <time>)."
"a" #'org-roam-alias-add) "a" #'org-roam-alias-add)
(general-define-key (general-define-key
:keymap 'org-mode-map :keymap 'org-mode-map
"C-c i" 'org-roam-node-insert)) "C-c i" #'my/org-roam-node-insert-permanent
"C-c I" #'org-roam-node-insert))
(defface my/org-roam-count-overlay-face (defface my/org-roam-count-overlay-face
'((t :inherit tooltip)) '((t :inherit tooltip))
@ -5122,6 +5209,8 @@ Review checklist:
- [ ] Reconcile ledger - [ ] Reconcile ledger
- [ ] Clear [[file:~/Downloads][downloads]] and [[file:~/00-Scratch][scratch]] folders - [ ] Clear [[file:~/Downloads][downloads]] and [[file:~/00-Scratch][scratch]] folders
- [ ] Process [[file:~/30-39 Life/35 Photos/35.00 Inbox/][photo inbox]] - [ ] Process [[file:~/30-39 Life/35 Photos/35.00 Inbox/][photo inbox]]
- [ ] Process new [[elisp:(my/org-roam-ql-fleeting)][fleeting notes]] (skip if tired)
- [ ] Process new [[https://wallabag.sqrtminusone.xyz/tag/list/t:zk-inbox][zk-inbox]] (skip if tired)
- [ ] Process [[file:../inbox.org][inbox]] - [ ] Process [[file:../inbox.org][inbox]]
- [ ] Create [[file:../recurring.org][recurring tasks]] for next week - [ ] Create [[file:../recurring.org][recurring tasks]] for next week
- [ ] Check agenda (-1 / +2 weeks): priorities, deadlines - [ ] Check agenda (-1 / +2 weeks): priorities, deadlines
@ -5129,6 +5218,7 @@ Review checklist:
- [[org-ql-search:todo%3A?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: All TODOs]] - [[org-ql-search:todo%3A?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: All TODOs]]
- [[org-ql-search:(and (todo) (not (tags \"nots\")) (not (ts :from -14)))?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: Stale tasks]] - [[org-ql-search:(and (todo) (not (tags \"nots\")) (not (ts :from -14)))?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: Stale tasks]]
- [[org-ql-search:todo%3AWAIT?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: WAIT]] - [[org-ql-search:todo%3AWAIT?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: WAIT]]
- [[org-ql-search:todo%3AMAYBE?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: MAYBE]]
- [ ] Run auto-archiving - [ ] Run auto-archiving
- [ ] Review journal records - [ ] Review journal records
") ")
@ -5150,6 +5240,58 @@ TODO Write something, maybe? "))))
(with-eval-after-load 'org-journal (with-eval-after-load 'org-journal
(my-leader-def "ojw" #'my/org-review-weekly)) (my-leader-def "ojw" #'my/org-review-weekly))
(defun my/kill-messengers ()
(interactive)
(when (get-buffer telega-root-buffer-name)
(telega-kill t))
(call-process-shell-command "pkill -f rocketchat-desktop")
(call-process-shell-command "pkill -f 'bwrap --args 36 element'")
(call-process-shell-command "pkill -f element-desktop"))
(defun my/org-review-set-daily-record ()
(save-excursion
(org-journal-tags-prop-apply-delta :add '("review.daily"))
(insert "Daily Review")
(goto-char (point-max))
(insert "
Maintenance checklist (/delete this/):
- [ ] [[elisp:(my/kill-messengers)][Close all messengers]]
- [ ] Process [[file:../inbox.org][inbox]]
- [ ] Check if clocked tasks are properly annotated
- [[elisp:(my/org-ql-clocked-today)][Tasks clocked today]]
- [[elisp:(my/org-ql-closed-today)][Tasks closed today]]
- [ ] Check agenda for the current week
/Remember, all of the following headers are optional./
*** Happened today
Happened to me:
- /Anything interesting?/
Happened to the world:
- /Anything important?/
*** New ideas
/Write them down in org-roam with the \"fleeting\" tag; leave links here. Perhaps note what sparked that idea?/
*** Interactions today
/Any meaninginful interactions, conflicts or tensions?/
*** Emotions today
/How did I feel?/
")))
(defun my/org-review-daily ()
(interactive)
(let ((org-journal-after-entry-create-hook
`(,@org-journal-after-entry-create-hook
my/org-review-set-daily-record)))
(org-journal-new-entry nil)
(org-fold-show-subtree)))
(with-eval-after-load 'org-journal
(my-leader-def "ojd" #'my/org-review-daily))
(use-package org-contacts (use-package org-contacts
:straight (:type git :repo "https://repo.or.cz/org-contacts.git") :straight (:type git :repo "https://repo.or.cz/org-contacts.git")
:if (not my/remote-server) :if (not my/remote-server)
@ -5400,7 +5542,7 @@ TODO Write something, maybe? "))))
"M-r" #'wdired-change-to-wdired-mode "M-r" #'wdired-change-to-wdired-mode
"<left>" #'dired-up-directory "<left>" #'dired-up-directory
"<right>" #'dired-find-file "<right>" #'dired-find-file
"M-<return>" #'dired-open-xdg)) "M-<return>" #'my/dired-open-xdg))
(defun my/dired-home () (defun my/dired-home ()
"Open dired at $HOME" "Open dired at $HOME"
@ -5486,7 +5628,9 @@ TODO Write something, maybe? "))))
:hook (dired-mode . (lambda () :hook (dired-mode . (lambda ()
(unless (or (file-remote-p default-directory) (unless (or (file-remote-p default-directory)
(string-match-p "/gnu/store" default-directory)) (string-match-p "/gnu/store" default-directory))
(nerd-icons-dired-mode))))) (nerd-icons-dired-mode))))
:config
(advice-add #'dired-create-empty-file :around #'nerd-icons-dired--refresh-advice))
(use-package dired-open (use-package dired-open
:straight t :straight t
@ -5522,11 +5666,18 @@ TODO Write something, maybe? "))))
:init :init
(my-leader-def "aa" #'avy-dired-goto-line)) (my-leader-def "aa" #'avy-dired-goto-line))
(defun my/dired-rsync--refresh ()
(cl-loop for window being the windows
do (with-current-buffer (window-buffer window)
(when (derived-mode-p 'dired-mode)
(revert-buffer)))))
(use-package dired-rsync (use-package dired-rsync
:straight t :straight t
:after (dired) :after (dired)
:config :config
(add-to-list 'global-mode-string '(:eval dired-rsync-modeline-status)) (add-to-list 'global-mode-string '(:eval dired-rsync-modeline-status))
(add-hook 'dired-rsync-success-hook #'my/dired-rsync--refresh)
(general-define-key (general-define-key
:states '(normal) :states '(normal)
:keymaps '(dired-mode-map) :keymaps '(dired-mode-map)
@ -5572,6 +5723,14 @@ TODO Write something, maybe? "))))
:keymaps 'dired-mode-map :keymaps 'dired-mode-map
"H" #'my/dired-goto-project-root)) "H" #'my/dired-goto-project-root))
(defun my/dired-open-xdg ()
"Try to run `xdg-open' to open the file under point."
(interactive)
(when (executable-find "xdg-open")
(let ((file (ignore-errors (dired-get-file-for-visit))))
(start-process "dired-open" nil
"xdg-open" (file-truename file)))))
(defun my/dired-bookmark-open () (defun my/dired-bookmark-open ()
(interactive) (interactive)
(let ((bookmarks (let ((bookmarks
@ -6994,109 +7153,6 @@ by the `my/elfeed-youtube-subtitles' function."
(setq emms-volume-change-function #'my/set-volume) (setq emms-volume-change-function #'my/set-volume)
(setq emms-volume-change-amount 5) (setq emms-volume-change-amount 5)
(use-package ytel
:straight t
:commands (ytel)
:config
(setq ytel-invidious-api-url "https://invidio.xamh.de/")
(general-define-key
:states '(normal)
:keymaps 'ytel-mode-map
"q" #'ytel-quit
"s" #'ytel-search
"L" #'ytel-search-next-page
"H" #'ytel-search-previous-page
"RET" #'my/ytel-add-emms))
(with-eval-after-load 'emms
(define-emms-source ytel (video)
(let ((track (emms-track
'url (concat "https://www.youtube.com/watch?v="
(ytel-video-id video)))))
(emms-track-set track 'info-title (ytel-video-title video))
(emms-track-set track 'info-artist (ytel-video-author video))
(emms-playlist-insert-track track))))
(defun my/ytel-add-emms ()
(interactive)
(emms-add-ytel (ytel-get-current-video)))
(setq my/invidious-instances-url
"https://api.invidious.io/instances.json?pretty=1&sort_by=health")
(defun my/ytel-instances-fetch-json ()
"Fetch list of invidious instances as json, sorted by health."
(let
((url-request-method "GET")
(url-request-extra-headers
'(("Accept" . "application/json"))))
(with-current-buffer
(url-retrieve-synchronously my/invidious-instances-url)
(goto-char (point-min))
(re-search-forward "^$")
(let* ((json-object-type 'alist)
(json-array-type 'list)
(json-key-type 'string))
(json-read)))))
(defun my/ytel-instances-alist-from-json ()
"Make the json of invidious instances into an alist."
(let ((jsonlist (my/ytel-instances-fetch-json))
(inst ()))
(while jsonlist
(push (concat "https://" (caar jsonlist)) inst)
(setq jsonlist (cdr jsonlist)))
(nreverse inst)))
(defun my/ytel-choose-instance ()
"Prompt user to choose an invidious instance to use."
(interactive)
(setq ytel-invidious-api-url
(or (condition-case nil
(completing-read "Using instance: "
(cl-subseq (my/ytel-instances-alist-from-json) 0 11) nil "confirm" "https://")
(error nil))
"https://invidious.synopyta.org")))
(defun my/ytel-draw--buffer-nil-videos-fix ()
(let ((inhibit-read-only t)
(current-line (line-number-at-pos)))
(erase-buffer)
(setf header-line-format
(concat "Search results for "
(propertize ytel-search-term 'face 'ytel-video-published-face)
", page "
(number-to-string ytel-current-page)))
(seq-do
(lambda (v)
(ytel--insert-video v)
(insert "\n"))
(seq-filter
(lambda (v)
(ytel-video-title v))
ytel-videos))
(goto-char (point-min))))
(with-eval-after-load 'ytel
(advice-add #'ytel--draw-buffer :override #'my/ytel-draw--buffer-nil-videos-fix))
(defun my/ytel--format-unknown-fix (fun &rest args)
(if (car args)
(apply fun args)
"unknown "))
(with-eval-after-load 'ytel
(advice-add #'ytel--format-video-length :around #'my/ytel--format-unknown-fix)
(advice-add #'ytel--format-video-published :around #'my/ytel--format-unknown-fix)
(advice-add #'ytel--format-video-views :around #'my/ytel--format-unknown-fix))
(defun my/ytel-kill-url ()
(interactive)
(kill-new
(concat
"https://www.youtube.com/watch?v="
(ytel-video-id (ytel-get-current-video)))))
(defun my/toggle-shr-use-fonts () (defun my/toggle-shr-use-fonts ()
"Toggle the shr-use-fonts variable in buffer" "Toggle the shr-use-fonts variable in buffer"
(interactive) (interactive)
@ -7206,25 +7262,31 @@ by the `my/elfeed-youtube-subtitles' function."
(setq mastodon-active-user "sqrtminusone") (setq mastodon-active-user "sqrtminusone")
(my/persp-add-rule mastodon-mode 0 "mastodon") (my/persp-add-rule mastodon-mode 0 "mastodon")
;; Hide spoilers by default ;; Hide spoilers by default
(setq-default mastodon-toot--content-warning t) ;; (setq-default mastodon-toot--content-warning nil)
(setq mastodon-media--avatar-height 40) (setq mastodon-media--avatar-height 40)
(setq mastodon-tl--timeline-posts-count "40") (setq mastodon-tl--timeline-posts-count "40")
(setq mastodon-tl--show-avatars t) (setq mastodon-tl--show-avatars t)
(setq mastodon-tl--horiz-bar
(make-string shr-max-width
(if (char-displayable-p ?―) ?― ?-)))
;; The default emojis take two characters for me ;; The default emojis take two characters for me
(setq mastodon-tl--symbols (mapcar (lambda (item)
'((reply "" . "R") (setf (alist-get (car item) mastodon-tl--symbols)
(boost "" . "B") (cdr item)))
(favourite "" . "F") '((reply "" . "R")
(bookmark "" . "K") (boost "" . "B")
(media "" . "[media]") (favourite "" . "F")
(verified "" . "V") (bookmark "" . "K")
(locked "" . "[locked]") (media "" . "[media]")
(private "" . "[followers]") (verified "" . "V")
(direct "" . "[direct]") (locked "" . "[locked]")
(edited "" . "[edited]")))) (private "" . "[followers]")
(direct "" . "[direct]")
(edited "" . "[edited]"))))
(use-package mastodon-alt (use-package mastodon-alt
:straight (:host github :repo "rougier/mastodon-alt") :straight (:host github :repo "rougier/mastodon-alt")
:disabled t
:after (mastodon) :after (mastodon)
:config :config
(mastodon-alt-tl-activate)) (mastodon-alt-tl-activate))
@ -7362,12 +7424,15 @@ by the `my/elfeed-youtube-subtitles' function."
(lambda (toot) (lambda (toot)
(and (and
(or (not hide-replies) (or (not hide-replies)
;; Why is the original function inverted?? (not (mastodon-tl--is-reply toot)))
(mastodon-tl--is-reply toot))
(or (not hide-boosts) (or (not hide-boosts)
(not (alist-get 'reblog toot))))) (not (alist-get 'reblog toot)))))
toots))) toots))
(mapc #'mastodon-tl--toot toots)))) (start-pos (point)))
(mapc #'mastodon-tl--toot toots)
(when mastodon-tl--display-media-p
(save-excursion
(mastodon-media--inline-images start-pos (point)))))))
(defun my/mastodon-tl--get-home (hide-replies hide-boosts) (defun my/mastodon-tl--get-home (hide-replies hide-boosts)
(mastodon-tl--init (mastodon-tl--init
@ -7474,8 +7539,8 @@ base toot."
("o" "Thread" mastodon-tl--thread) ("o" "Thread" mastodon-tl--thread)
("w" "Browser" my/mastodon-toot--browse) ("w" "Browser" my/mastodon-toot--browse)
("le" "List edits" mastodon-toot--view-toot-edits) ("le" "List edits" mastodon-toot--view-toot-edits)
("lf" "List favouriters" mastodon-toot--list-toot-favouriters) ("lf" "List favouriters" mastodon-toot--list-favouriters)
("lb" "List boosters" mastodon-toot--list-toot-boosters)] ("lb" "List boosters" mastodon-toot--list-boosters)]
["Toot Actions" ["Toot Actions"
:class transient-row :class transient-row
("r" "Reply" mastodon-toot--reply) ("r" "Reply" mastodon-toot--reply)
@ -7677,6 +7742,8 @@ base toot."
(message "Scrolled %s" scrolled))))) (message "Scrolled %s" scrolled)))))
(use-package telega (use-package telega
;; :straight (:type built-in)
;; For now emacs-telega-server is compatible with the latest telega.el
:straight t :straight t
:if (not (or my/remote-server)) :if (not (or my/remote-server))
:commands (telega) :commands (telega)
@ -7688,6 +7755,10 @@ base toot."
(telega-webpage-chat-link :foreground (my/color-value 'base0) (telega-webpage-chat-link :foreground (my/color-value 'base0)
:background (my/color-value 'fg))) :background (my/color-value 'fg)))
:config :config
(when (file-directory-p "~/.guix-extra-profiles/emacs/")
(setq telega-server-command
(expand-file-name
"~/.guix-extra-profiles/emacs/emacs/bin/telega-server")))
(setq telega-emoji-use-images nil) (setq telega-emoji-use-images nil)
(setq telega-chat-fill-column 80) (setq telega-chat-fill-column 80)
(setq telega-completing-read-function #'completing-read) (setq telega-completing-read-function #'completing-read)
@ -7744,7 +7815,7 @@ base toot."
(company-mode 1) (company-mode 1)
(setopt visual-fill-column-width (setopt visual-fill-column-width
(+ telega-chat-fill-column (+ telega-chat-fill-column
(if (display-graphic-p) 4 5))) (if (display-graphic-p) 5 6)))
(setq-local split-width-threshold 1)) (setq-local split-width-threshold 1))
(add-hook 'telega-chat-mode-hook #'my/telega-chat-setup) (add-hook 'telega-chat-mode-hook #'my/telega-chat-setup)
@ -7839,7 +7910,7 @@ base toot."
(setq biome-api-try-parse-error-as-response t)) (setq biome-api-try-parse-error-as-response t))
:config :config
(add-to-list 'biome-query-coords (add-to-list 'biome-query-coords
'("Saint-Petersburg, Russia" 59.93863 30.31413)) '("Saint-Petersburg, Russia" 59.942651 30.229930))
(add-to-list 'biome-query-coords (add-to-list 'biome-query-coords
'("Tyumen, Russia" 57.15222 65.52722))) '("Tyumen, Russia" 57.15222 65.52722)))
@ -7971,20 +8042,31 @@ base toot."
(setq gptel-backend (gptel-make-ollama "Ollama" (setq gptel-backend (gptel-make-ollama "Ollama"
:host "localhost:11434" :host "localhost:11434"
:stream t :stream t
:models '("llama3.1:latest" "llama3.1:instruct"))) :models '("llama3.1:8b" "deepseek-r1:32b"
"qwen2.5:32b" "qwen2.5-coder:32b"
"eva-qwen2.5-q4_k_l-32b:latest"
"t-pro-1.0-q4_k_m:latest")))
(gptel-make-openai "OpenRouter"
:host "openrouter.ai/api"
:key (lambda () (my/password-store-get-field
"My_Online/Accounts/openrouter" "api-key"))
:stream t
:models '("anthropic/claude-3.5-haiku"))
(setq gptel--known-backends
(seq-filter
(lambda (cell)
(not (equal (car cell) "ChatGPT")))
gptel--known-backends))
(setq gptel-response-prefix-alist
'((markdown-mode . "[Response] ")
(org-mode . "*** Response: ")
(text-mode . "[Response]")))
;; (my/gptel-switch-backend "llama3.1:latest")
(general-define-key (general-define-key
:keymaps '(gptel-mode-map) :keymaps '(gptel-mode-map)
:states '(insert normal) :states '(insert normal)
"C-<return>" 'gptel-send) "C-<return>" 'gptel-send
(general-define-key "M-o" #'gptel-menu))
:keymaps '(gptel-mode-map)
:states '(normal)
"?" #'gptel-menu)
(gptel-make-gemini "Gemini"
:key (my/password-store-get-field "My_Online/Accounts/google-gemini" "api")
:stream t))
(use-package ellama (use-package ellama
:straight t :straight t
@ -7995,19 +8077,26 @@ base toot."
(require 'llm-ollama) (require 'llm-ollama)
;; I've looked for this option for 1.5 hours ;; I've looked for this option for 1.5 hours
(setq ellama-long-lines-length 100000) (setq ellama-long-lines-length 100000)
(my-leader-def
"aie" '(:wk "ellama" :keymap ellama-command-map))
(setq ellama-provider (make-llm-ollama (setq ellama-provider (make-llm-ollama
:chat-model "llama3.1:instruct" :chat-model "qwen2.5:32b"
:embedding-model "llama3.1:instruct")) :embedding-model "qwen2.5:32b"))
(setq ellama-coding-provider (make-llm-ollama
:chat-model "qwen2.5-coder:32b"
:embedding-model "qwen2.5-coder:32b"))
(setq ellama-providers (setq ellama-providers
`(("llama3.1:8b" . ,(make-llm-ollama `(("llama3.1:8b" . ,(make-llm-ollama
:chat-model "llama3.1:latest" :chat-model "llama3.1:latest"
:embedding-model "llama3.1:latest")) :embedding-model "llama3.1:latest"))
("llama3.1:instruct" . ,(make-llm-ollama ("phi4:latest" . ,(make-llm-ollama
:chat-model "llama3.1:instruct" :chat-model "phi4:latest"
:embedding-model "llama3.1:instruct"))))) :embedding-model "phi4:latest"))
("qwen2.5:32b" . ,(make-llm-ollama
:chat-model "qwen2.5:32b"
:embedding-model "qwen2.5:32b"))
("qwen2.5-coder:32b" . ,(make-llm-ollama
:chat-model "qwen2.5-coder:32b"
:embedding-model "qwen2.5-coder:32b")))))
(with-eval-after-load 'ellama (with-eval-after-load 'ellama
(transient-define-prefix my/ellama-transient () (transient-define-prefix my/ellama-transient ()
@ -8024,9 +8113,7 @@ base toot."
("ci" "Improve" ellama-code-improve)] ("ci" "Improve" ellama-code-improve)]
["Natural Language" ["Natural Language"
:class transient-row :class transient-row
("np" "Proof-read" my/ellama-proof-read) ("np" "Proof-read" my/ellama-proof-read)]
("nw" "Improve wording" my/ellama-improve-wording)
("nc" "Improve conciseness" my/ellama-improve-concise)]
["Formatting" ["Formatting"
:class transient-row :class transient-row
("ff" "Format" ellama-make-format) ("ff" "Format" ellama-make-format)
@ -8051,7 +8138,6 @@ base toot."
("sr" "Rename ression" ellama-session-rename) ("sr" "Rename ression" ellama-session-rename)
("sd" "Delete session" ellama-session-remove)])) ("sd" "Delete session" ellama-session-remove)]))
(defun my/ellama () (defun my/ellama ()
(interactive) (interactive)
(require 'ellama) (require 'ellama)
@ -8076,27 +8162,46 @@ base toot."
(delete-file file1) (delete-file file1)
(delete-file file2)))) (delete-file file2))))
(defun my/ellama-text-with-diff (text is-org-mode prompt) (defun my/ellama-proof-read--display (text is-org-mode prompt)
(require 'ellama)
(llm-chat-async (llm-chat-async
ellama-provider ellama-provider
(llm-make-chat-prompt (llm-make-chat-prompt
(format prompt text)) (format prompt text))
(lambda (changed-text) (lambda (response)
(when is-org-mode (let* ((parts (split-string response "-FIXED TEXT ENDS-"))
(setq changed-text (ellama--translate-markdown-to-org-filter changed-text))) (changed-text (nth 0 parts))
(let ((buffer (generate-new-buffer "*ellama-diff*"))) (comments (nth 1 parts))
(buffer (generate-new-buffer "*ellama-diff*")))
(when is-org-mode
(setq changed-text (ellama--translate-markdown-to-org-filter changed-text)))
(with-current-buffer buffer (with-current-buffer buffer
(text-mode) (text-mode)
(insert changed-text) (insert
(insert "\n\n") (propertize "Changed text:\n" 'face 'transient-heading)
(insert (my/diff-strings text changed-text))) (string-trim changed-text)
"\n\n"
(propertize "Comments:\n" 'face 'transient-heading)
(string-trim comments)
"\n\n"
(propertize "Diff:\n" 'face 'transient-heading)
(my/diff-strings text changed-text)))
(display-buffer buffer))) (display-buffer buffer)))
(lambda (&rest err) (lambda (&rest err)
(message "Error: %s" err)))) (message "Error: %s" err))))
(setq my/ellama-proof-read-prompt (setq my/ellama-proof-read-prompt
"Proof-read the following text. Fix any errors but keep the original style and punctuation, including linebreaks. Print the changed text and nothing else, not even \"Here's the proof-read text\".\n\n %s") "Proof-read the following text. Follow these rules:
- Fix all grammar errors
- Keep the original style and punctuation, including linebreaks.
- Use British spelling
- Do not replace ' with , and do not touch other such symbols
Output the following and nothing else:
- The fixed text
- The string -FIXED TEXT ENDS-
- List of found errors
- List of style suggestions
%s")
(defun my/ellama--text () (defun my/ellama--text ()
(if (region-active-p) (if (region-active-p)
@ -8105,28 +8210,17 @@ base toot."
(defun my/ellama-proof-read (text is-org-mode) (defun my/ellama-proof-read (text is-org-mode)
(interactive (list (my/ellama--text) (derived-mode-p 'org-mode))) (interactive (list (my/ellama--text) (derived-mode-p 'org-mode)))
(my/ellama-text-with-diff text is-org-mode my/ellama-proof-read-prompt)) (require 'ellama)
(my/ellama-proof-read--display text is-org-mode my/ellama-proof-read-prompt))
(setq my/ellama-improve-wording-prompt
"Proof-read the following text. Fix any errors and improve wording. Print the changed text and nothing else, not even \"Here's the improved text\".\n\n %s")
(defun my/ellama-improve-wording (text is-org-mode)
(interactive (list (my/ellama--text) (derived-mode-p 'org-mode)))
(my/ellama-text-with-diff text is-org-mode my/ellama-improve-wording-prompt))
(setq my/ellama-improve-concise-prompt
"Make the following text more concise. Print the changed text and nothing else, not even \"Here's the improved text\".\n\n %s")
(defun my/ellama-improve-concise (text is-org-mode)
(interactive (list (my/ellama--text) (derived-mode-p 'org-mode)))
(my/ellama-text-with-diff text is-org-mode my/ellama-improve-concise-prompt))
(defun my/whisper--format-vtt-seconds (seconds) (defun my/whisper--format-vtt-seconds (seconds)
(let* ((hours (/ (floor seconds) (* 60 60))) (if (numberp seconds)
(minutes (/ (- (floor seconds) (* hours 60 60)) 60)) (let* ((hours (/ (floor seconds) (* 60 60)))
(sec (% (floor seconds) 60)) (minutes (/ (- (floor seconds) (* hours 60 60)) 60))
(ms (floor (* 1000 (- seconds (floor seconds)))))) (sec (% (floor seconds) 60))
(format "%.2d:%.2d:%.2d.%.3d" hours minutes sec ms))) (ms (floor (* 1000 (- seconds (floor seconds))))))
(format "%.2d:%.2d:%.2d.%.3d" hours minutes sec ms))
""))
(defun my/whisper--save-chucks-vtt (path data) (defun my/whisper--save-chucks-vtt (path data)
(with-temp-file path (with-temp-file path

602
Emacs.org
View file

@ -44,6 +44,7 @@ I decided not to keep configs for features that I do not use anymore because thi
| Feature | Last commit | | Feature | Last commit |
|--------------------------+------------------------------------------| |--------------------------+------------------------------------------|
| ytel | 327340a95c4ff9cffd171f6bd937c6041f63add7 |
| org-roam dailies | d2648918fcc338bd5c1cd6d5c0aa60a65077ccf7 | | org-roam dailies | d2648918fcc338bd5c1cd6d5c0aa60a65077ccf7 |
| org-roam projects | 025278a1e180e86f3aade20242e4ac1cdc1a2f13 | | org-roam projects | 025278a1e180e86f3aade20242e4ac1cdc1a2f13 |
| treemacs | 3d87852745caacc0863c747f1fa9871d367240d2 | | treemacs | 3d87852745caacc0863c747f1fa9871d367240d2 |
@ -2639,6 +2640,7 @@ References:
(setq lsp-headerline-breadcrumb-enable nil) (setq lsp-headerline-breadcrumb-enable nil)
(setq lsp-modeline-code-actions-enable nil) (setq lsp-modeline-code-actions-enable nil)
(setq lsp-modeline-diagnostics-enable nil) (setq lsp-modeline-diagnostics-enable nil)
(setq lsp-volar-take-over-mode nil)
(add-to-list 'lsp-language-id-configuration '(svelte-mode . "svelte"))) (add-to-list 'lsp-language-id-configuration '(svelte-mode . "svelte")))
(use-package lsp-ui (use-package lsp-ui
@ -3210,7 +3212,7 @@ A general-purpose package to run formatters on files. While the most popular for
(use-package copilot (use-package copilot
:straight (:host github :repo "copilot-emacs/copilot.el") :straight (:host github :repo "copilot-emacs/copilot.el")
:commands (copilot-mode) :commands (copilot-mode)
:if (not (or my/remote-server my/is-termux)) :disabled t
:init :init
(add-hook 'emacs-startup-hook (add-hook 'emacs-startup-hook
(lambda () (lambda ()
@ -3392,11 +3394,13 @@ Hooking this up with lsp.
Vue settings Vue settings
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun my/web-mode-vue-setup (&rest _) (defun my/web-mode-vue-setup (&rest _)
(when (string-match-p (rx ".vue" eos) (buffer-file-name)) (let ((filename (buffer-file-name)))
(setq-local web-mode-script-padding 0) (when (and (stringp filename)
(setq-local web-mode-style-padding 0) (string-match-p (rx ".vue" eos) filename))
(setq-local create-lockfiles nil) (setq-local web-mode-script-padding 0)
(setq-local web-mode-enable-auto-pairing nil))) (setq-local web-mode-style-padding 0)
(setq-local create-lockfiles nil)
(setq-local web-mode-enable-auto-pairing nil))))
(add-hook 'web-mode-hook 'my/web-mode-vue-setup) (add-hook 'web-mode-hook 'my/web-mode-vue-setup)
(add-hook 'editorconfig-after-apply-functions 'my/web-mode-vue-setup) (add-hook 'editorconfig-after-apply-functions 'my/web-mode-vue-setup)
@ -3813,7 +3817,7 @@ References:
:config :config
(setq langtool-language-tool-server-jar "/home/pavel/bin/LanguageTool-6.4/languagetool-server.jar") (setq langtool-language-tool-server-jar "/home/pavel/bin/LanguageTool-6.4/languagetool-server.jar")
(setq langtool-mother-tongue "ru") (setq langtool-mother-tongue "ru")
(setq langtool-default-language "en-US")) (setq langtool-default-language "ru-RU"))
(my-leader-def (my-leader-def
:infix "L" :infix "L"
@ -4950,6 +4954,7 @@ Enable languages
`((emacs-lisp . t) `((emacs-lisp . t)
(python . t) (python . t)
(sql . t) (sql . t)
(sqlite . t)
;; (typescript .t) ;; (typescript .t)
(hy . t) (hy . t)
(shell . t) (shell . t)
@ -5510,6 +5515,9 @@ It's been somewhat complicated to integrate into my workflow, but I think it's b
(with-eval-after-load 'org (with-eval-after-load 'org
(my-leader-def "ol" #'org-clock-agg)) (my-leader-def "ol" #'org-clock-agg))
:config :config
(setq org-clock-agg-node-format
"%-%(+ title-width)t %20c %8z %s/%S")
(setq org-clock-agg-node-title-width-delta 47)
(push (push
(cons "Agenda+Archive" (cons "Agenda+Archive"
(append (append
@ -5658,6 +5666,23 @@ And use the function to set the total clocked time.
:infix "SPC" :infix "SPC"
"C" #'my/org-clock-recent)) "C" #'my/org-clock-recent))
#+end_src #+end_src
***** Fix tasks without TASK_KIND
#+begin_src emacs-lisp
(defun my/org-fix-task-kind ()
(interactive)
(let ((entries (org-ql-query
:select #'element-with-markers
:from (current-buffer)
:where '(and (olp "Tasks")
(not (property "TASK_KIND"))
(clocked)))))
(org-fold-show-all)
(dolist (entry entries)
(let ((marker (org-element-property :org-marker entry)))
(org-with-point-at marker
(let ((value (org-read-property-value "TASK_KIND")))
(org-set-property "TASK_KIND" value)))))))
#+end_src
**** org-super-agenda **** org-super-agenda
[[https://github.com/alphapapa/org-super-agenda][org-super-agenda]] is alphapapa's extension to group items in org-agenda. I don't use it instead of the standard agenda, but =org-ql= uses it for some of its views. [[https://github.com/alphapapa/org-super-agenda][org-super-agenda]] is alphapapa's extension to group items in org-agenda. I don't use it instead of the standard agenda, but =org-ql= uses it for some of its views.
@ -5766,6 +5791,37 @@ A view to return all TODOs in a category.
:super-groups '((:auto-outline-path-file t))))) :super-groups '((:auto-outline-path-file t)))))
#+end_src #+end_src
***** Items clocked or closed today
Some custom functions to account for =org-extend-today-until=. Needed because sometimes my daily reviews cross 00:00.
#+begin_src emacs-lisp
(defun my/org-ql-clocked-today ()
(interactive)
(let ((today (format-time-string
"%Y-%m-%d"
(days-to-time
(- (org-today) (time-to-days 0))))))
(org-ql-search (org-agenda-files) `(clocked :from ,today)
:title "Clocked today"
:sort '(todo priority date)
:super-groups '((:auto-outline-path-file t)
(:auto-todo t)))))
#+end_src
#+begin_src emacs-lisp
(defun my/org-ql-closed-today ()
(interactive)
(let ((today (format-time-string
"%Y-%m-%d"
(days-to-time
(- (org-today) (time-to-days 0))))))
(org-ql-search (org-agenda-files) `(closed :from ,today)
:title "Closed today"
:sort '(todo priority date)
:super-groups '((:auto-outline-path-file t)
(:auto-todo t)))))
#+end_src
***** Configuring views ***** Configuring views
Putting all the above in =org-ql-views=. Putting all the above in =org-ql-views=.
@ -5791,13 +5847,8 @@ Putting all the above in =org-ql-views=.
:sort '(todo priority date) :sort '(todo priority date)
:super-groups '((:auto-outline-path-file t)))) :super-groups '((:auto-outline-path-file t))))
(cons "Review: Recently timestamped" #'my/org-ql-view-recent-items) (cons "Review: Recently timestamped" #'my/org-ql-view-recent-items)
(cons "Review: Unlinked to meetings" (cons "Review: Clocked today" #'my/org-ql-clocked-today)
(list :buffers-files #'org-agenda-files (cons "Review: Closed today" #'my/org-ql-closed-today)
:query '(and (todo "DONE" "NO")
(not (property "MEETING"))
(ts :from -7))
:super-groups '((:auto-outline-path-file t))))
(cons "Review: Meeting" #'my/org-ql-meeting-tasks)
(cons "Fix: tasks without TASK_KIND" (cons "Fix: tasks without TASK_KIND"
(lambda () (lambda ()
(interactive) (interactive)
@ -6221,7 +6272,7 @@ First, I need to find and group and such headers. =org-ql= can help with that:
(thread-last (thread-last
heading heading
(substring-no-properties) (substring-no-properties)
(replace-regexp-in-string (rx (| "(" "[") (+ alnum) (| "]" ")")) "") (replace-regexp-in-string (rx (| "(" "[") (+ nonl) (| "]" ")")) "")
(replace-regexp-in-string (rx " " (+ (or digit "."))) " ") (replace-regexp-in-string (rx " " (+ (or digit "."))) " ")
(replace-regexp-in-string (rx (+ " ")) " ") (replace-regexp-in-string (rx (+ " ")) " ")
(string-trim))) (string-trim)))
@ -6542,6 +6593,15 @@ And here's the function that creates a drawer with such information. At the mome
(add-hook 'org-journal-after-entry-create-hook (add-hook 'org-journal-after-entry-create-hook
#'my/set-journal-header) #'my/set-journal-header)
#+end_src #+end_src
Also, a function to decrypt the current file:
#+begin_src emacs-lisp
(defun my/org-journal-decrypt ()
"Decrypt the current org journal file."
(interactive)
(org-journal-tags--ensure-decrypted))
#+end_src
*** Bibliography *** Bibliography
I use [[https://www.zotero.org/][Zotero]] to manage my bibliograhy. I use [[https://www.zotero.org/][Zotero]] to manage my bibliograhy.
@ -6668,12 +6728,63 @@ Capture templates for =org-roam-capture=. As for now, nothing too complicated he
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq org-roam-capture-templates (setq org-roam-capture-templates
`(("d" "default" plain "%?" `(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t)
("f" "fleeting" plain "%?"
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :fleeting:\n")
:unnarrowed t) :unnarrowed t)
("e" "encrypted" plain "%?" ("e" "encrypted" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org.gpg" "#+title: ${title}\n") :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org.gpg" "#+title: ${title}\n")
:unnarrowed t))) :unnarrowed t)))
#+end_src #+end_src
**** org-roam-ql
[[https://github.com/ahmed-shariff/org-roam-ql][org-roam-ql]] is a package to query =org-roam= files like =org-ql=.
#+begin_src emacs-lisp
(use-package org-roam-ql
:straight t
:after (org-roam)
:config
(general-define-key
:states '(normal visual)
:keymaps '(org-roam-ql-mode-map)
"s" #'org-roam-ql-buffer-dispatch))
#+end_src
**** Finding nodes
Find and insert permanent nodes:
#+begin_src emacs-lisp
(defun my/org-roam-node-find-permanent (&optional other-window)
(interactive current-prefix-arg)
(org-roam-node-find
other-window
nil
(lambda (node)
(not
(seq-contains-p
"fleeting"
(org-roam-node-tags node))))))
(defun my/org-roam-node-insert-permanent ()
(interactive)
(org-roam-node-insert
(lambda (node)
(not
(seq-contains-p
(org-roam-node-tags node)
"fleeting")))))
#+end_src
List unprocessed fleeting notes:
#+begin_src emacs-lisp
(defun my/org-roam-ql-fleeting ()
(interactive)
(org-roam-ql-search
'(tags "fleeting")
"Fleeting notes"))
#+end_src
**** Keybindings **** Keybindings
A set of keybindings to quickly access things in Org Roam. A set of keybindings to quickly access things in Org Roam.
@ -6682,11 +6793,13 @@ A set of keybindings to quickly access things in Org Roam.
(my-leader-def (my-leader-def
:infix "or" :infix "or"
"" '(:which-key "org-roam") "" '(:which-key "org-roam")
"i" #'org-roam-node-insert "i" #'my/org-roam-node-insert-permanent
"r" #'org-roam-node-find "r" #'my/org-roam-node-find-permanent
"g" #'org-roam-graph "g" #'org-roam-graph
"c" #'org-roam-capture "c" #'org-roam-capture
"b" #'org-roam-buffer-toggle) "b" #'org-roam-buffer-toggle
"q" #'org-roam-ql-search
"f" #'my/org-roam-ql-fleeting)
(general-define-key (general-define-key
:keymaps 'org-roam-mode-map :keymaps 'org-roam-mode-map
:states '(normal) :states '(normal)
@ -6707,7 +6820,8 @@ A set of keybindings to quickly access things in Org Roam.
"a" #'org-roam-alias-add) "a" #'org-roam-alias-add)
(general-define-key (general-define-key
:keymap 'org-mode-map :keymap 'org-mode-map
"C-c i" 'org-roam-node-insert)) "C-c i" #'my/org-roam-node-insert-permanent
"C-c I" #'org-roam-node-insert))
#+end_src #+end_src
**** Backlinks count display **** Backlinks count display
Occasionally I want to see how many backlinks a particular page has. Occasionally I want to see how many backlinks a particular page has.
@ -7007,7 +7121,7 @@ I'll use data from git to get the list of what I've been working on. The directo
(org-roam-node-title (cdr c))))) (org-roam-node-title (cdr c)))))
(apply #'concat))))) (apply #'concat)))))
#+end_src #+end_src
**** Org Journal integration **** General review logic
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun my/org-review-get-last-review-date (kind) (defun my/org-review-get-last-review-date (kind)
(let* ((start-of-day (- (time-convert nil #'integer) (let* ((start-of-day (- (time-convert nil #'integer)
@ -7027,9 +7141,7 @@ I'll use data from git to get the list of what I've been working on. The directo
(pcase kind (pcase kind
('weekly (- start-of-day (* 7 24 60 60))))))) ('weekly (- start-of-day (* 7 24 60 60)))))))
#+end_src #+end_src
**** Weekly review
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun my/org-review-set-weekly-record () (defun my/org-review-set-weekly-record ()
(save-excursion (save-excursion
@ -7049,6 +7161,8 @@ Review checklist:
- [ ] Reconcile ledger - [ ] Reconcile ledger
- [ ] Clear [[file:~/Downloads][downloads]] and [[file:~/00-Scratch][scratch]] folders - [ ] Clear [[file:~/Downloads][downloads]] and [[file:~/00-Scratch][scratch]] folders
- [ ] Process [[file:~/30-39 Life/35 Photos/35.00 Inbox/][photo inbox]] - [ ] Process [[file:~/30-39 Life/35 Photos/35.00 Inbox/][photo inbox]]
- [ ] Process new [[elisp:(my/org-roam-ql-fleeting)][fleeting notes]] (skip if tired)
- [ ] Process new [[https://wallabag.sqrtminusone.xyz/tag/list/t:zk-inbox][zk-inbox]] (skip if tired)
- [ ] Process [[file:../inbox.org][inbox]] - [ ] Process [[file:../inbox.org][inbox]]
- [ ] Create [[file:../recurring.org][recurring tasks]] for next week - [ ] Create [[file:../recurring.org][recurring tasks]] for next week
- [ ] Check agenda (-1 / +2 weeks): priorities, deadlines - [ ] Check agenda (-1 / +2 weeks): priorities, deadlines
@ -7056,6 +7170,7 @@ Review checklist:
- [[org-ql-search:todo%3A?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: All TODOs]] - [[org-ql-search:todo%3A?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: All TODOs]]
- [[org-ql-search:(and (todo) (not (tags \"nots\")) (not (ts :from -14)))?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: Stale tasks]] - [[org-ql-search:(and (todo) (not (tags \"nots\")) (not (ts :from -14)))?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: Stale tasks]]
- [[org-ql-search:todo%3AWAIT?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: WAIT]] - [[org-ql-search:todo%3AWAIT?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: WAIT]]
- [[org-ql-search:todo%3AMAYBE?buffers-files=%22org-agenda-files%22&super-groups=%28%28%3Aauto-outline-path-file%20t%29%29&sort=%28priority%20todo%20deadline%29][org-ql-search: MAYBE]]
- [ ] Run auto-archiving - [ ] Run auto-archiving
- [ ] Review journal records - [ ] Review journal records
") ")
@ -7079,6 +7194,70 @@ TODO Write something, maybe? "))))
(with-eval-after-load 'org-journal (with-eval-after-load 'org-journal
(my-leader-def "ojw" #'my/org-review-weekly)) (my-leader-def "ojw" #'my/org-review-weekly))
#+end_src #+end_src
**** Daily review
My attempt at a daily review, or an end-of-day routine.
I try to keep it under 10-15 minutes.
#+begin_src emacs-lisp
(defun my/kill-messengers ()
(interactive)
(when (get-buffer telega-root-buffer-name)
(telega-kill t))
(call-process-shell-command "pkill -f rocketchat-desktop")
(call-process-shell-command "pkill -f 'bwrap --args 36 element'")
(call-process-shell-command "pkill -f element-desktop"))
#+end_src
#+begin_src emacs-lisp
(defun my/org-review-set-daily-record ()
(save-excursion
(org-journal-tags-prop-apply-delta :add '("review.daily"))
(insert "Daily Review")
(goto-char (point-max))
(insert "
Maintenance checklist (/delete this/):
- [ ] [[elisp:(my/kill-messengers)][Close all messengers]]
- [ ] Process [[file:../inbox.org][inbox]]
- [ ] Check if clocked tasks are properly annotated
- [[elisp:(my/org-ql-clocked-today)][Tasks clocked today]]
- [[elisp:(my/org-ql-closed-today)][Tasks closed today]]
- [ ] Check agenda for the current week
/Remember, all of the following headers are optional./
,*** Happened today
Happened to me:
- /Anything interesting?/
Happened to the world:
- /Anything important?/
,*** New ideas
/Write them down in org-roam with the \"fleeting\" tag; leave links here. Perhaps note what sparked that idea?/
,*** Interactions today
/Any meaninginful interactions, conflicts or tensions?/
,*** Emotions today
/How did I feel?/
")))
#+end_src
#+begin_src emacs-lisp
(defun my/org-review-daily ()
(interactive)
(let ((org-journal-after-entry-create-hook
`(,@org-journal-after-entry-create-hook
my/org-review-set-daily-record)))
(org-journal-new-entry nil)
(org-fold-show-subtree)))
#+end_src
#+begin_src emacs-lisp
(with-eval-after-load 'org-journal
(my-leader-def "ojd" #'my/org-review-daily))
#+end_src
*** Contacts *** Contacts
=org-contacts= is a package to store contacts in an org file. =org-contacts= is a package to store contacts in an org file.
@ -7521,7 +7700,7 @@ My config mostly follows ranger's and vifm's keybindings which I'm used to.
"M-r" #'wdired-change-to-wdired-mode "M-r" #'wdired-change-to-wdired-mode
"<left>" #'dired-up-directory "<left>" #'dired-up-directory
"<right>" #'dired-find-file "<right>" #'dired-find-file
"M-<return>" #'dired-open-xdg)) "M-<return>" #'my/dired-open-xdg))
(defun my/dired-home () (defun my/dired-home ()
"Open dired at $HOME" "Open dired at $HOME"
@ -7630,7 +7809,9 @@ Display icons for files.
:hook (dired-mode . (lambda () :hook (dired-mode . (lambda ()
(unless (or (file-remote-p default-directory) (unless (or (file-remote-p default-directory)
(string-match-p "/gnu/store" default-directory)) (string-match-p "/gnu/store" default-directory))
(nerd-icons-dired-mode))))) (nerd-icons-dired-mode))))
:config
(advice-add #'dired-create-empty-file :around #'nerd-icons-dired--refresh-advice))
#+end_src #+end_src
Provides stuff like =dired-open-xdg= Provides stuff like =dired-open-xdg=
@ -7682,13 +7863,20 @@ Display git info, such as the last commit for file and stuff. It's pretty useful
(my-leader-def "aa" #'avy-dired-goto-line)) (my-leader-def "aa" #'avy-dired-goto-line))
#+end_src #+end_src
[[https://github.com/stsquad/dired-rsync][dired-rsync]] allows using =rsync= instead of the default synchronous copy operation. [[https://github.com/stsquad/dired-rsync][dired-rsync]] allows using =rsync= instead of the default synchronous copy operation. The only trouble is that it doesn't replace =dired-do-copy= completely, so...
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun my/dired-rsync--refresh ()
(cl-loop for window being the windows
do (with-current-buffer (window-buffer window)
(when (derived-mode-p 'dired-mode)
(revert-buffer)))))
(use-package dired-rsync (use-package dired-rsync
:straight t :straight t
:after (dired) :after (dired)
:config :config
(add-to-list 'global-mode-string '(:eval dired-rsync-modeline-status)) (add-to-list 'global-mode-string '(:eval dired-rsync-modeline-status))
(add-hook 'dired-rsync-success-hook #'my/dired-rsync--refresh)
(general-define-key (general-define-key
:states '(normal) :states '(normal)
:keymaps '(dired-mode-map) :keymaps '(dired-mode-map)
@ -7744,6 +7932,19 @@ Goto project root.
:keymaps 'dired-mode-map :keymaps 'dired-mode-map
"H" #'my/dired-goto-project-root)) "H" #'my/dired-goto-project-root))
#+end_src #+end_src
Open a file with =xdg-open=. I used =dired-open= for this before, but I've had to abandon the package because it switched from =start-process= to =call-process=, which blocks my EXWM.
#+begin_src emacs-lisp
(defun my/dired-open-xdg ()
"Try to run `xdg-open' to open the file under point."
(interactive)
(when (executable-find "xdg-open")
(let ((file (ignore-errors (dired-get-file-for-visit))))
(start-process "dired-open" nil
"xdg-open" (file-truename file)))))
#+end_src
*** Bookmarks *** Bookmarks
A simple bookmark list for Dired, mainly to use with TRAMP. I may look into a proper bookmarking system later. A simple bookmark list for Dired, mainly to use with TRAMP. I may look into a proper bookmarking system later.
@ -9736,142 +9937,6 @@ The list will be in reverse order."
(setq alists (cons alist alists))) (setq alists (cons alist alists)))
alists))) alists)))
#+end_src #+end_src
*** ytel
[[https://github.com/gRastello/ytel][ytel]] is a YouTube (actually Invidious) frontend, which lets one search YouTube (whereas the setup with elfeed just lets one view the pre-defined subscriptions).
**** Package config
The package doesn't provide evil bindings, so I define my own.
#+begin_src emacs-lisp
(use-package ytel
:straight t
:commands (ytel)
:config
(setq ytel-invidious-api-url "https://invidio.xamh.de/")
(general-define-key
:states '(normal)
:keymaps 'ytel-mode-map
"q" #'ytel-quit
"s" #'ytel-search
"L" #'ytel-search-next-page
"H" #'ytel-search-previous-page
"RET" #'my/ytel-add-emms))
#+end_src
**** EMMS integration
And here is the same kind of integration with EMMS as in the elfeed setup:
#+begin_src emacs-lisp
(with-eval-after-load 'emms
(define-emms-source ytel (video)
(let ((track (emms-track
'url (concat "https://www.youtube.com/watch?v="
(ytel-video-id video)))))
(emms-track-set track 'info-title (ytel-video-title video))
(emms-track-set track 'info-artist (ytel-video-author video))
(emms-playlist-insert-track track))))
(defun my/ytel-add-emms ()
(interactive)
(emms-add-ytel (ytel-get-current-video)))
#+end_src
**** Choosing instances
Invidious instances aren't particularly reliable, but there plenty of them, and there's an API at =invidious.io= that returns the available instances and their health, so we can use that.
Inspired by [[https://github.com/grastello/ytel/issues/17#issuecomment-801745429][this comment]].
#+begin_src emacs-lisp
(setq my/invidious-instances-url
"https://api.invidious.io/instances.json?pretty=1&sort_by=health")
#+end_src
#+begin_src emacs-lisp
(defun my/ytel-instances-fetch-json ()
"Fetch list of invidious instances as json, sorted by health."
(let
((url-request-method "GET")
(url-request-extra-headers
'(("Accept" . "application/json"))))
(with-current-buffer
(url-retrieve-synchronously my/invidious-instances-url)
(goto-char (point-min))
(re-search-forward "^$")
(let* ((json-object-type 'alist)
(json-array-type 'list)
(json-key-type 'string))
(json-read)))))
(defun my/ytel-instances-alist-from-json ()
"Make the json of invidious instances into an alist."
(let ((jsonlist (my/ytel-instances-fetch-json))
(inst ()))
(while jsonlist
(push (concat "https://" (caar jsonlist)) inst)
(setq jsonlist (cdr jsonlist)))
(nreverse inst)))
(defun my/ytel-choose-instance ()
"Prompt user to choose an invidious instance to use."
(interactive)
(setq ytel-invidious-api-url
(or (condition-case nil
(completing-read "Using instance: "
(cl-subseq (my/ytel-instances-alist-from-json) 0 11) nil "confirm" "https://")
(error nil))
"https://invidious.synopyta.org")))
#+end_src
**** Some fixes
At some point in the last 2 years, Invidious started to return videos with =null= fields. I have no idea what causes that, but I suspect it's related to YouTube Music.
=ytel= hasn't been updated in these two years, so it doesn't account for that change.
So, let's skip videos with null titles.
#+begin_src emacs-lisp
(defun my/ytel-draw--buffer-nil-videos-fix ()
(let ((inhibit-read-only t)
(current-line (line-number-at-pos)))
(erase-buffer)
(setf header-line-format
(concat "Search results for "
(propertize ytel-search-term 'face 'ytel-video-published-face)
", page "
(number-to-string ytel-current-page)))
(seq-do
(lambda (v)
(ytel--insert-video v)
(insert "\n"))
(seq-filter
(lambda (v)
(ytel-video-title v))
ytel-videos))
(goto-char (point-min))))
(with-eval-after-load 'ytel
(advice-add #'ytel--draw-buffer :override #'my/ytel-draw--buffer-nil-videos-fix))
#+end_src
And render other potentially =null= fields as "unknown".
#+begin_src emacs-lisp
(defun my/ytel--format-unknown-fix (fun &rest args)
(if (car args)
(apply fun args)
"unknown "))
(with-eval-after-load 'ytel
(advice-add #'ytel--format-video-length :around #'my/ytel--format-unknown-fix)
(advice-add #'ytel--format-video-published :around #'my/ytel--format-unknown-fix)
(advice-add #'ytel--format-video-views :around #'my/ytel--format-unknown-fix))
#+end_src
**** Some functions
Also, a function to copy a URL to the video under cursor.
#+begin_src emacs-lisp
(defun my/ytel-kill-url ()
(interactive)
(kill-new
(concat
"https://www.youtube.com/watch?v="
(ytel-video-id (ytel-get-current-video)))))
#+end_src
*** EWW *** EWW
Emacs built-in web browser. +I wonder if anyone actually uses it.+ Emacs built-in web browser. +I wonder if anyone actually uses it.+
@ -10019,25 +10084,31 @@ The default UI is rather rough, but Nicolas Rougier's [[https://github.com/rougi
(setq mastodon-active-user "sqrtminusone") (setq mastodon-active-user "sqrtminusone")
(my/persp-add-rule mastodon-mode 0 "mastodon") (my/persp-add-rule mastodon-mode 0 "mastodon")
;; Hide spoilers by default ;; Hide spoilers by default
(setq-default mastodon-toot--content-warning t) ;; (setq-default mastodon-toot--content-warning nil)
(setq mastodon-media--avatar-height 40) (setq mastodon-media--avatar-height 40)
(setq mastodon-tl--timeline-posts-count "40") (setq mastodon-tl--timeline-posts-count "40")
(setq mastodon-tl--show-avatars t) (setq mastodon-tl--show-avatars t)
(setq mastodon-tl--horiz-bar
(make-string shr-max-width
(if (char-displayable-p ?―) ?― ?-)))
;; The default emojis take two characters for me ;; The default emojis take two characters for me
(setq mastodon-tl--symbols (mapcar (lambda (item)
'((reply "" . "R") (setf (alist-get (car item) mastodon-tl--symbols)
(boost "" . "B") (cdr item)))
(favourite "" . "F") '((reply "" . "R")
(bookmark "" . "K") (boost "" . "B")
(media "" . "[media]") (favourite "" . "F")
(verified "" . "V") (bookmark "" . "K")
(locked "" . "[locked]") (media "" . "[media]")
(private "" . "[followers]") (verified "" . "V")
(direct "" . "[direct]") (locked "" . "[locked]")
(edited "" . "[edited]")))) (private "" . "[followers]")
(direct "" . "[direct]")
(edited "" . "[edited]"))))
(use-package mastodon-alt (use-package mastodon-alt
:straight (:host github :repo "rougier/mastodon-alt") :straight (:host github :repo "rougier/mastodon-alt")
:disabled t
:after (mastodon) :after (mastodon)
:config :config
(mastodon-alt-tl-activate)) (mastodon-alt-tl-activate))
@ -10201,12 +10272,15 @@ So here's a custom update function:
(lambda (toot) (lambda (toot)
(and (and
(or (not hide-replies) (or (not hide-replies)
;; Why is the original function inverted?? (not (mastodon-tl--is-reply toot)))
(mastodon-tl--is-reply toot))
(or (not hide-boosts) (or (not hide-boosts)
(not (alist-get 'reblog toot))))) (not (alist-get 'reblog toot)))))
toots))) toots))
(mapc #'mastodon-tl--toot toots)))) (start-pos (point)))
(mapc #'mastodon-tl--toot toots)
(when mastodon-tl--display-media-p
(save-excursion
(mastodon-media--inline-images start-pos (point)))))))
#+end_src #+end_src
In order to use it, the function has to be passed to =mastodon-tl--init=: In order to use it, the function has to be passed to =mastodon-tl--init=:
@ -10336,8 +10410,8 @@ And the prefix itself:
("o" "Thread" mastodon-tl--thread) ("o" "Thread" mastodon-tl--thread)
("w" "Browser" my/mastodon-toot--browse) ("w" "Browser" my/mastodon-toot--browse)
("le" "List edits" mastodon-toot--view-toot-edits) ("le" "List edits" mastodon-toot--view-toot-edits)
("lf" "List favouriters" mastodon-toot--list-toot-favouriters) ("lf" "List favouriters" mastodon-toot--list-favouriters)
("lb" "List boosters" mastodon-toot--list-toot-boosters)] ("lb" "List boosters" mastodon-toot--list-boosters)]
["Toot Actions" ["Toot Actions"
:class transient-row :class transient-row
("r" "Reply" mastodon-toot--reply) ("r" "Reply" mastodon-toot--reply)
@ -10577,14 +10651,16 @@ Or you can load up Element for a moment to see what the mention was, if that's e
*** Telega *** Telega
[[https://github.com/zevlg/telega.el/][telega.el]] is a Telegam client for Emacs. [[https://github.com/zevlg/telega.el/][telega.el]] is a Telegam client for Emacs.
| Guix dependency | | Guix dependency |
|-------------------| |--------------------|
| tdlib-1.8.16 | | emacs-telega-sever |
| font-gnu-unifont | | font-gnu-unifont |
| font-gnu-freefont | | font-gnu-freefont |
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package telega (use-package telega
;; :straight (:type built-in)
;; For now emacs-telega-server is compatible with the latest telega.el
:straight t :straight t
:if (not (or my/remote-server)) :if (not (or my/remote-server))
:commands (telega) :commands (telega)
@ -10596,6 +10672,10 @@ Or you can load up Element for a moment to see what the mention was, if that's e
(telega-webpage-chat-link :foreground (my/color-value 'base0) (telega-webpage-chat-link :foreground (my/color-value 'base0)
:background (my/color-value 'fg))) :background (my/color-value 'fg)))
:config :config
(when (file-directory-p "~/.guix-extra-profiles/emacs/")
(setq telega-server-command
(expand-file-name
"~/.guix-extra-profiles/emacs/emacs/bin/telega-server")))
(setq telega-emoji-use-images nil) (setq telega-emoji-use-images nil)
(setq telega-chat-fill-column 80) (setq telega-chat-fill-column 80)
(setq telega-completing-read-function #'completing-read) (setq telega-completing-read-function #'completing-read)
@ -10661,7 +10741,7 @@ Configuring company backends for the chat buffer, as recommended in the manual:
(company-mode 1) (company-mode 1)
(setopt visual-fill-column-width (setopt visual-fill-column-width
(+ telega-chat-fill-column (+ telega-chat-fill-column
(if (display-graphic-p) 4 5))) (if (display-graphic-p) 5 6)))
(setq-local split-width-threshold 1)) (setq-local split-width-threshold 1))
(add-hook 'telega-chat-mode-hook #'my/telega-chat-setup) (add-hook 'telega-chat-mode-hook #'my/telega-chat-setup)
#+end_src #+end_src
@ -10777,10 +10857,9 @@ References:
(setq biome-api-try-parse-error-as-response t)) (setq biome-api-try-parse-error-as-response t))
:config :config
(add-to-list 'biome-query-coords (add-to-list 'biome-query-coords
'("Saint-Petersburg, Russia" 59.93863 30.31413)) '("Saint-Petersburg, Russia" 59.942651 30.229930))
(add-to-list 'biome-query-coords (add-to-list 'biome-query-coords
'("Tyumen, Russia" 57.15222 65.52722))) '("Tyumen, Russia" 57.15222 65.52722)))
#+end_src #+end_src
** Reading documentation ** Reading documentation
*** tldr *** tldr
@ -10914,7 +10993,7 @@ There is a package called =devdocs= that does more or less the same, but I like
(add-hook 'sx-question-list-mode-hook #'doom-modeline-mode)) (add-hook 'sx-question-list-mode-hook #'doom-modeline-mode))
#+end_src #+end_src
** Not-an-AI ** Not-an-AI
Workflows, which are sometimes referred as "AI", go in here. Workflows, which are sometimes referred to as "AI", go in here.
I'm technically writing a PhD on a related topic, so I'm a bit more receptive towards the whole thing than most of the community. But I'm still not calling it AI. I'm technically writing a PhD on a related topic, so I'm a bit more receptive towards the whole thing than most of the community. But I'm still not calling it AI.
@ -10939,20 +11018,31 @@ I don't have access to any proprietary APIs, but LLaMA 3.1 8b with [[https://oll
(setq gptel-backend (gptel-make-ollama "Ollama" (setq gptel-backend (gptel-make-ollama "Ollama"
:host "localhost:11434" :host "localhost:11434"
:stream t :stream t
:models '("llama3.1:latest" "llama3.1:instruct"))) :models '("llama3.1:8b" "deepseek-r1:32b"
"qwen2.5:32b" "qwen2.5-coder:32b"
"eva-qwen2.5-q4_k_l-32b:latest"
"t-pro-1.0-q4_k_m:latest")))
(gptel-make-openai "OpenRouter"
:host "openrouter.ai/api"
:key (lambda () (my/password-store-get-field
"My_Online/Accounts/openrouter" "api-key"))
:stream t
:models '("anthropic/claude-3.5-haiku"))
(setq gptel--known-backends
(seq-filter
(lambda (cell)
(not (equal (car cell) "ChatGPT")))
gptel--known-backends))
(setq gptel-response-prefix-alist
'((markdown-mode . "[Response] ")
(org-mode . "*** Response: ")
(text-mode . "[Response]")))
;; (my/gptel-switch-backend "llama3.1:latest")
(general-define-key (general-define-key
:keymaps '(gptel-mode-map) :keymaps '(gptel-mode-map)
:states '(insert normal) :states '(insert normal)
"C-<return>" 'gptel-send) "C-<return>" 'gptel-send
(general-define-key "M-o" #'gptel-menu))
:keymaps '(gptel-mode-map)
:states '(normal)
"?" #'gptel-menu)
(gptel-make-gemini "Gemini"
:key (my/password-store-get-field "My_Online/Accounts/google-gemini" "api")
:stream t))
#+end_src #+end_src
**** ellama **** ellama
@ -10968,19 +11058,26 @@ I don't have access to any proprietary APIs, but LLaMA 3.1 8b with [[https://oll
(require 'llm-ollama) (require 'llm-ollama)
;; I've looked for this option for 1.5 hours ;; I've looked for this option for 1.5 hours
(setq ellama-long-lines-length 100000) (setq ellama-long-lines-length 100000)
(my-leader-def
"aie" '(:wk "ellama" :keymap ellama-command-map))
(setq ellama-provider (make-llm-ollama (setq ellama-provider (make-llm-ollama
:chat-model "llama3.1:instruct" :chat-model "qwen2.5:32b"
:embedding-model "llama3.1:instruct")) :embedding-model "qwen2.5:32b"))
(setq ellama-coding-provider (make-llm-ollama
:chat-model "qwen2.5-coder:32b"
:embedding-model "qwen2.5-coder:32b"))
(setq ellama-providers (setq ellama-providers
`(("llama3.1:8b" . ,(make-llm-ollama `(("llama3.1:8b" . ,(make-llm-ollama
:chat-model "llama3.1:latest" :chat-model "llama3.1:latest"
:embedding-model "llama3.1:latest")) :embedding-model "llama3.1:latest"))
("llama3.1:instruct" . ,(make-llm-ollama ("phi4:latest" . ,(make-llm-ollama
:chat-model "llama3.1:instruct" :chat-model "phi4:latest"
:embedding-model "llama3.1:instruct"))))) :embedding-model "phi4:latest"))
("qwen2.5:32b" . ,(make-llm-ollama
:chat-model "qwen2.5:32b"
:embedding-model "qwen2.5:32b"))
("qwen2.5-coder:32b" . ,(make-llm-ollama
:chat-model "qwen2.5-coder:32b"
:embedding-model "qwen2.5-coder:32b")))))
#+end_src #+end_src
The keybindings are a bit crazy to use even with =which-key=, so here goes transient.el. The keybindings are a bit crazy to use even with =which-key=, so here goes transient.el.
@ -11000,9 +11097,7 @@ The keybindings are a bit crazy to use even with =which-key=, so here goes trans
("ci" "Improve" ellama-code-improve)] ("ci" "Improve" ellama-code-improve)]
["Natural Language" ["Natural Language"
:class transient-row :class transient-row
("np" "Proof-read" my/ellama-proof-read) ("np" "Proof-read" my/ellama-proof-read)]
("nw" "Improve wording" my/ellama-improve-wording)
("nc" "Improve conciseness" my/ellama-improve-concise)]
["Formatting" ["Formatting"
:class transient-row :class transient-row
("ff" "Format" ellama-make-format) ("ff" "Format" ellama-make-format)
@ -11027,7 +11122,6 @@ The keybindings are a bit crazy to use even with =which-key=, so here goes trans
("sr" "Rename ression" ellama-session-rename) ("sr" "Rename ression" ellama-session-rename)
("sd" "Delete session" ellama-session-remove)])) ("sd" "Delete session" ellama-session-remove)]))
(defun my/ellama () (defun my/ellama ()
(interactive) (interactive)
(require 'ellama) (require 'ellama)
@ -11036,13 +11130,13 @@ The keybindings are a bit crazy to use even with =which-key=, so here goes trans
(my-leader-def "aie" #'my/ellama) (my-leader-def "aie" #'my/ellama)
#+end_src #+end_src
**** Change natural text & diff against the results **** Change natural-language text & diff against the results
One pattern I often want is to change the given text and compare it to the old version. One pattern I often want is to change the given text and compare it to the old version.
LLMs aren't perfectly good at saying what changes they have done, so the pattern here is to query the model and show the changed text together with the diff. LLMs aren't perfectly good at saying what changes they have done, so the pattern here is to query the model and show the changed text together with the diff.
So first, I need to diff two strings. So first, I need to diff two strings.
#+begin_src emacs-lisp :noweb yes #+begin_src emacs-lisp
(defun my/diff-strings (str1 str2) (defun my/diff-strings (str1 str2)
(let ((file1 (make-temp-file "diff1")) (let ((file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))) (file2 (make-temp-file "diff2")))
@ -11062,22 +11156,30 @@ So first, I need to diff two strings.
#+end_src #+end_src
And the function to do the prompting iself. Llama tends to output in Markdown, so I use a function from Ellama to convert the output back to Org-mode, if necessary. And the function to do the prompting iself. Llama tends to output in Markdown, so I use a function from Ellama to convert the output back to Org-mode, if necessary.
#+begin_src emacs-lisp :noweb yes #+begin_src emacs-lisp
(defun my/ellama-text-with-diff (text is-org-mode prompt) (defun my/ellama-proof-read--display (text is-org-mode prompt)
(require 'ellama)
(llm-chat-async (llm-chat-async
ellama-provider ellama-provider
(llm-make-chat-prompt (llm-make-chat-prompt
(format prompt text)) (format prompt text))
(lambda (changed-text) (lambda (response)
(when is-org-mode (let* ((parts (split-string response "-FIXED TEXT ENDS-"))
(setq changed-text (ellama--translate-markdown-to-org-filter changed-text))) (changed-text (nth 0 parts))
(let ((buffer (generate-new-buffer "*ellama-diff*"))) (comments (nth 1 parts))
(buffer (generate-new-buffer "*ellama-diff*")))
(when is-org-mode
(setq changed-text (ellama--translate-markdown-to-org-filter changed-text)))
(with-current-buffer buffer (with-current-buffer buffer
(text-mode) (text-mode)
(insert changed-text) (insert
(insert "\n\n") (propertize "Changed text:\n" 'face 'transient-heading)
(insert (my/diff-strings text changed-text))) (string-trim changed-text)
"\n\n"
(propertize "Comments:\n" 'face 'transient-heading)
(string-trim comments)
"\n\n"
(propertize "Diff:\n" 'face 'transient-heading)
(my/diff-strings text changed-text)))
(display-buffer buffer))) (display-buffer buffer)))
(lambda (&rest err) (lambda (&rest err)
(message "Error: %s" err)))) (message "Error: %s" err))))
@ -11086,7 +11188,18 @@ And the function to do the prompting iself. Llama tends to output in Markdown, s
As for prompts, I like the following prompt to proof-read text. It's pretty conservative, but good for fixing typos, missing commas, articles, etc. As for prompts, I like the following prompt to proof-read text. It's pretty conservative, but good for fixing typos, missing commas, articles, etc.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq my/ellama-proof-read-prompt (setq my/ellama-proof-read-prompt
"Proof-read the following text. Fix any errors but keep the original style and punctuation, including linebreaks. Print the changed text and nothing else, not even \"Here's the proof-read text\".\n\n %s") "Proof-read the following text. Follow these rules:
- Fix all grammar errors
- Keep the original style and punctuation, including linebreaks.
- Use British spelling
- Do not replace ' with , and do not touch other such symbols
Output the following and nothing else:
- The fixed text
- The string -FIXED TEXT ENDS-
- List of found errors
- List of style suggestions
%s")
(defun my/ellama--text () (defun my/ellama--text ()
(if (region-active-p) (if (region-active-p)
@ -11095,27 +11208,8 @@ As for prompts, I like the following prompt to proof-read text. It's pretty cons
(defun my/ellama-proof-read (text is-org-mode) (defun my/ellama-proof-read (text is-org-mode)
(interactive (list (my/ellama--text) (derived-mode-p 'org-mode))) (interactive (list (my/ellama--text) (derived-mode-p 'org-mode)))
(my/ellama-text-with-diff text is-org-mode my/ellama-proof-read-prompt)) (require 'ellama)
#+end_src (my/ellama-proof-read--display text is-org-mode my/ellama-proof-read-prompt))
The following is more expansive, but preserves less of the original text. For instance, it tends to replace my /id est/ and /exempli gratia/. But sometimes it has good ideas.
#+begin_src emacs-lisp
(setq my/ellama-improve-wording-prompt
"Proof-read the following text. Fix any errors and improve wording. Print the changed text and nothing else, not even \"Here's the improved text\".\n\n %s")
(defun my/ellama-improve-wording (text is-org-mode)
(interactive (list (my/ellama--text) (derived-mode-p 'org-mode)))
(my/ellama-text-with-diff text is-org-mode my/ellama-improve-wording-prompt))
#+end_src
Also, a prompt to make a text more concise.
#+begin_src emacs-lisp
(setq my/ellama-improve-concise-prompt
"Make the following text more concise. Print the changed text and nothing else, not even \"Here's the improved text\".\n\n %s")
(defun my/ellama-improve-concise (text is-org-mode)
(interactive (list (my/ellama--text) (derived-mode-p 'org-mode)))
(my/ellama-text-with-diff text is-org-mode my/ellama-improve-concise-prompt))
#+end_src #+end_src
*** Podcast transcripts *** Podcast transcripts
@ -11148,11 +11242,13 @@ First, some functions to process the output. These take a JSON formed by =insane
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun my/whisper--format-vtt-seconds (seconds) (defun my/whisper--format-vtt-seconds (seconds)
(let* ((hours (/ (floor seconds) (* 60 60))) (if (numberp seconds)
(minutes (/ (- (floor seconds) (* hours 60 60)) 60)) (let* ((hours (/ (floor seconds) (* 60 60)))
(sec (% (floor seconds) 60)) (minutes (/ (- (floor seconds) (* hours 60 60)) 60))
(ms (floor (* 1000 (- seconds (floor seconds)))))) (sec (% (floor seconds) 60))
(format "%.2d:%.2d:%.2d.%.3d" hours minutes sec ms))) (ms (floor (* 1000 (- seconds (floor seconds))))))
(format "%.2d:%.2d:%.2d.%.3d" hours minutes sec ms))
""))
(defun my/whisper--save-chucks-vtt (path data) (defun my/whisper--save-chucks-vtt (path data)
(with-temp-file path (with-temp-file path