diff options
Diffstat (limited to 'emacs/.config/emacs/init.el')
-rw-r--r-- | emacs/.config/emacs/init.el | 963 |
1 files changed, 947 insertions, 16 deletions
diff --git a/emacs/.config/emacs/init.el b/emacs/.config/emacs/init.el index ddc40f1..007f7b6 100644 --- a/emacs/.config/emacs/init.el +++ b/emacs/.config/emacs/init.el @@ -1,9 +1,13 @@ ;;; -*- lexical-binding: t; -*- +;;;; Elpaca package setup +;; ;; NixOS emacs doesn't include a build time. Assume a recent version. +;; (setq elpaca-core-date 20240101) +;; (setq elpaca-directory "~/.cache/emacs/elpaca") +;; (load-file (expand-file-name "elpaca-installer.el" user-emacs-directory)) -;; NixOS emacs doesn't include a build time. Assume a recent version. -(setq elpaca-core-date 20240101) -(setq elpaca-directory "~/.cache/emacs/elpaca") -(load-file (expand-file-name "elpaca-installer.el" user-emacs-directory)) +(with-eval-after-load 'package + (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)) +(setopt use-package-always-ensure t) (add-hook 'emacs-startup-hook (lambda () @@ -13,15 +17,19 @@ (time-subtract after-init-time before-init-time))) gcs-done))) -(add-hook 'elpaca-after-init-hook 'kj/restore-file-name-handler-alist) +;;;; Workaround for module loading order. +(let ((file (expand-file-name "lisp/kj-google.el" user-emacs-directory))) + (when (file-exists-p file) + (load-file file))) -(elpaca elpaca-use-package - ;; Enable :elpaca use-package keyword. - (elpaca-use-package-mode) - ;; Assume :elpaca t unless otherwise specified. - (setq elpaca-use-package-by-default t)) -(elpaca-wait) +;; (elpaca elpaca-use-package +;; ;; Enable :ensure use-package keyword. +;; (elpaca-use-package-mode) +;; ;; Assume :ensure t unless otherwise specified. +;; (setq elpaca-use-package-by-default t)) +;; (elpaca-wait) +;;;; GC and performance (use-package gcmh :hook (elpaca-after-init . kj/enable-gcmh) @@ -30,11 +38,934 @@ (message "Enabling gcmh") (gcmh-mode 1))) -;; Include user configuration. +(setopt inhibit-compacting-font-caches t) +(add-hook 'elpaca-after-init-hook 'kj/restore-file-name-handler-alist) + +;;;; Utility functions +(defmacro define-repeating-key (keymap key cmd &optional desc) + `(let ((def + ,(if desc + `'(,desc . ,cmd) + `',cmd))) + (define-key ,keymap ,key def) + (put ',cmd 'repeat-map ',keymap))) + +(defun kj/default-directory-remote-p () + (file-remote-p default-directory)) +;;;; Basic settings and customizations +(defvar kj/cache-dir "~/.cache/emacs") + +(setopt user-full-name "KJ Orbekk" + user-mail-address "kj@orbekk.com" + + initial-scratch-message nil + + switch-to-buffer-obey-display-actions t + switch-to-buffer-in-dedicated-window 'pop + even-window-sizes 'width-only + custom-file (expand-file-name "emacs-custom.el" user-emacs-directory) + bookmark-file (expand-file-name "boomarks" kj/cache-dir) + use-short-answers t + outline-minor-mode-cycle t + outline-minor-mode-highlight 'override + fast-but-imprecise-scrolling t + visible-bell t + auto-save-list-file-prefix (expand-file-name + "auto-save-list/.saves-" kj/cache-dir) + alert-default-style 'libnotify + + use-dialog-box nil + display-time-24hr-format t + + next-screen-context-lines 10) + +(load custom-file :noerror t) + +;; This should be on by default, but it doesn't work until reloaded for some reason. +(auto-compression-mode 1) + +;; Disable annoying warnings. +(setq warning-minimum-level :error) + +(use-package transient + :config + (setq transient-levels-file (expand-file-name "transient/levels.el" kj/cache-dir) + transient-values-file (expand-file-name "transient/values.el" kj/cache-dir) + transient-history-file (expand-file-name "transient/history.el" kj/cache-dir))) + +;;;;; Modes +(use-package markdown-mode + :ensure t + :mode ("README\\.md\\'" . gfm-mode) + :init (setq markdown-command "multimarkdown")) + +(use-package emacs + :ensure nil + :custom + (auto-revert-avoid-polling t) + (auto-revert-interval 5) + (auto-revert-check-vc-info t) + :init + ;; (dolist (hook '(markdown-mode-hook + ;; org-mode-hook)) + ;; (add-to-list hook #'visual-line-mode)) + (global-hl-line-mode 1) + (global-auto-revert-mode 1)) + +;;;; Navigation + +(use-package avy + :bind + (("C-c l" . avy-goto-char) + ("C-c n" . avy-goto-line)) + :config + (setq avy-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n ?s)) + (defun avy-action-embark (pt) + (unwind-protect + (save-excursion + (goto-char pt) + (embark-act)) + (select-window + (cdr (ring-ref avy-ring 0)))) + t) + (setf (alist-get ?. avy-dispatch-alist) 'avy-action-embark) + (define-key isearch-mode-map (kbd "M-j") 'avy-isearch)) + +(winner-mode 1) + +(use-package ace-window + :bind + (("C-c e" . ace-window)) + :config + (setq aw-dispatch-always t) + (setq aw-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n ?s)) + ;; Make ace window more visible + ;; (custom-set-faces! + ;; '(aw-leading-char-face + ;; :foreground "white" :background "red" + ;; :weight bold :height 2.5 :box (:line-width 10 :color "red"))) + ) + +(defun kj/balance-main-window () + (balance-windows (window-main-window))) + +;; Prefer horizontal splits. +(setq split-height-threshold 100) + +(defun kj/split-window-below () + (interactive) + (split-window-below) + (kj/balance-main-window) + (other-window 1)) + +(defun kj/split-window-right () + (interactive) + (split-window-right) + (kj/balance-main-window) + (other-window 1)) + +(global-set-key (kbd "C-x 3") #'kj/split-window-right) +(global-set-key (kbd "C-x 2") #'kj/split-window-below) + +;;;; Completion +(use-package embark + :ensure t + + :bind + (("C-." . embark-act) + ("C-;" . embark-dwim) + ("C-h C-b" . embark-bindings)) + + :init + (setq prefix-help-command #'embark-prefix-help-command) + + :config + (defun find-file-keeping-default-directory (filename) + (let ((dir default-directory)) + (with-current-buffer (find-file filename) + (setq-local default-directory dir)))) + (define-key embark-file-map "@" 'find-file-keeping-default-directory) + + ;; Hide the mode line of the Embark live/completions buffers + (add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + . + (window-parameters (mode-line-format . none))))) + +(use-package vertico + :init + (vertico-mode)) + +(use-package marginalia + :bind + (:map minibuffer-local-map + ("M-n" . marginalia-cycle)) + :init + (marginalia-mode)) + +(use-package embark-consult + :ensure t + :after (embark consult) + :demand t + :hook (embark-mode . consult-preview-at-point-mode)) + +(defun kj/consult-ripgrep-here (&optional dir initial) + (interactive "P") + (unless dir + (setq dir default-directory)) + (consult-ripgrep dir initial)) + +;; Example configuration for Consult +(use-package consult + ;; Replace bindings. Lazily loaded due by `use-package'. + :bind (;; C-c bindings (mode-specific-map) + ("C-c h" . kj/consult-history) + ;;("C-c m" . consult-mode-command) + ("C-c k" . consult-kmacro) + ;; C-x bindings (ctl-x-map) + ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command + ("C-x b" . consult-buffer) ;; orig. switch-to-buffer + ("C-x C-b" . consult-buffer) ;; orig. switch-to-buffer + ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window + ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame + ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump + ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer + ("C-c f r" . consult-recent-file) + ;; Custom M-# bindings for fast register access + ("M-#" . consult-register-load) + ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) + ("C-M-#" . consult-register) + ;; Other custom bindings + ("M-y" . consult-yank-pop) ;; orig. yank-pop + ("<help> a" . consult-apropos) ;; orig. apropos-command + ;; M-g bindings (goto-map) + ("M-g e" . consult-compile-error) + ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck + ("M-g g" . consult-goto-line) ;; orig. goto-line + ("M-g M-g" . consult-goto-line) ;; orig. goto-line + ("M-g o" . consult-outline) ;; Alternative: consult-org-heading + ("M-g m" . consult-mark) + ("M-g k" . consult-global-mark) + ("M-g i" . consult-imenu) + ("M-g I" . consult-imenu-multi) + ;; M-s bindings (search-map) + ("M-s d" . consult-find) + ("M-s D" . consult-locate) + ;; ("M-s g" . consult-grep) + ("M-s G" . consult-git-grep) + ("M-s r" . consult-ripgrep) + ("M-s R" . kj/consult-ripgrep-here) + ("M-s l" . consult-line) + ("M-s L" . consult-line-multi) + ("M-s m" . consult-multi-occur) + ("M-s k" . consult-keep-lines) + ("M-s u" . consult-focus-lines) + ;; Isearch integration + ("M-s e" . consult-isearch-history) + :map isearch-mode-map + ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s l" . consult-line) ;; needed by consult-line to detect isearch + ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch + ;; Minibuffer history + :map minibuffer-local-map + ("M-s" . kj/consult-history) ;; orig. next-matching-history-element + ("M-r" . kj/consult-history)) ;; orig. previous-matching-history-element + + ;; Enable automatic preview at point in the *Completions* buffer. This is + ;; relevant when you use the default completion UI. + ;; :hook (completion-list-mode . consult-preview-at-point-mode) + + ;; The :init configuration is always executed (Not lazy) + :init + + ;; Optionally configure the register formatting. This improves the register + ;; preview for `consult-register', `consult-register-load', + ;; `consult-register-store' and the Emacs built-ins. + (setq register-preview-delay 0.5 + register-preview-function #'consult-register-format) + + ;; Optionally tweak the register preview window. + ;; This adds thin lines, sorting and hides the mode line of the window. + (advice-add #'register-preview :override #'consult-register-window) + + ;; Use Consult to select xref locations with preview + (setq xref-show-xrefs-function #'consult-xref + xref-show-definitions-function #'consult-xref) + + ;; Configure other variables and modes in the :config section, + ;; after lazily loading the package. + :config + + ;; Use consult for completion. + (setq completion-in-region-function #'consult-completion-in-region) + + ;; Optionally configure preview. The default value + ;; is 'any, such that any key triggers the preview. + (setq consult-preview-key '(:debounce 0.3 any)) + ;; (setq consult-preview-key (kbd "M-.")) + ;; (setq consult-preview-key (list (kbd "<S-down>") (kbd "<S-up>"))) + ;; For some commands and buffer sources it is useful to configure the + ;; :preview-key on a per-command basis using the `consult-customize' macro. + ;; (consult-customize + ;; consult-theme + ;; :preview-key '(:debounce 0.2 any) + ;; consult-ripgrep consult-git-grep consult-grep + ;; consult-bookmark consult-recent-file consult-xref + ;; consult--source-bookmark consult--source-recent-file + ;; consult--source-project-recent-file + ;; :preview-key (kbd "M-.")) + + ;; Optionally configure the narrowing key. + ;; Both < and C-+ work reasonably well. + (setq consult-narrow-key "<") ;; (kbd "C-+") + + (setq consult-ripgrep-args + "rg --null --line-buffered --color=never --max-columns=1000 --path-separator / --smart-case --no-heading --with-filename --line-number --search-zip --hidden") + + ;; Optionally make narrowing help available in the minibuffer. + ;; You may want to use `embark-prefix-help-command' or which-key instead. + ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) + + ;; By default `consult-project-function' uses `project-root' from project.el. + ;; Optionally configure a different project root function. + ;; There are multiple reasonable alternatives to chose from. + ;;;; 1. project.el (the default) + ;;(setq consult-project-function #'consult--default-project-function) + ;;;; 2. projectile.el (projectile-project-root) + (autoload 'projectile-project-root "projectile") + (setq consult-project-function (lambda (_) (projectile-project-root))) + ;;;; 3. vc.el (vc-root-dir) + ;; (setq consult-project-function (lambda (_) (vc-root-dir))) + ;;;; 4. locate-dominating-file + ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git"))) + ) + +(use-package orderless + :init + (setq completion-styles '(orderless basic) + completion-category-defaults nil + completion-category-overrides '((file (styles partial-completion))))) + + +;;;; Appearance + +(use-package emacs + :ensure nil + :init + (column-number-mode) + (set-fringe-mode 10)) + +(use-package rainbow-delimiters + :defer t + :hook (prog-mode . rainbow-delimiters-mode)) + +(use-package emacs + :ensure nil + :init + (setq-default show-trailing-whitespace t) + (dolist (mode '(calendar-mode-hook + eshell-mode-hook + term-mode-hook + comint-mode-hook + completion-list-mode)) + (add-hook mode + (defun kj/disable-trailing-whitespace () + (setq show-trailing-whitespace nil))))) + +(use-package ws-butler + :hook (prog-mode . ws-butler-mode)) + +(use-package all-the-icons + :if (display-graphic-p)) + +(use-package emojify + :bind + (("C-c C-i C-e" . emojify-insert-emoji)) + :hook (after-init . global-emojify-mode) + :config + (emojify-set-emoji-styles '(unicode))) + +;;;;; Mode line +(use-package emacs + :ensure nil + :defer 5 + :init + (require 'battery) + (when (and battery-status-function + (not (string-match-p "unknown" + (battery-format "%B" + (funcall battery-status-function))))) + (display-battery-mode 1))) +(setopt display-time-default-load-average nil) + +(display-time-mode 1) + +(use-package doom-modeline + :ensure t + :disabled + :init (doom-modeline-mode 1) + :config + (setq doom-modeline-buffer-encoding 'nondefault)) + +(use-package emacs + :ensure nil + :defer 1 + :init + (defvar kj/minor-mode-alist-overrides + '((ws-butler "") + (gcmh-mode "") + (editorconfig-mode "") + (yas-minor-mode "") + (selected-minor-mode "") + (anzu-mode "") + (projectile-mode "") + (eldoc-mode "") + (mixed-pitch-mode "") + (which-key-mode ""))) + (defun kj/clean-mode-line () + (dolist (override kj/minor-mode-alist-overrides) + (if-let ((e (assoc (car override) minor-mode-alist))) + (setf (nth 1 e) (nth 1 override))))) + (add-to-list 'change-major-mode-hook 'kj/clean-mode-line)) + + +;;;;; Theme + +(use-package wombat-theme :ensure nil + :disabled + :init + (load-theme 'wombat) + :config + (custom-theme-set-faces + 'wombat + '(default ((t :background "#111111"))) + '(ansi-color-black ((t :background "#000000"))) + '(hl-line ((t :inherit () :background "#242424"))))) + +(use-package modus-themes + :init + :disabled + (load-theme 'modus-operandi-tinted) + (load-theme 'modus-vivendi-tinted t t) + :config + (custom-theme-set-faces + 'modus-vivendi-tinted + '(avy-lead-face ((t :inherit (bold modus-themes-subtle-blue)))) + '(avy-lead-face-0 ((t :inherit (bold modus-themes-subtle-cyan)))) + '(avy-lead-face-1 ((t :inherit (bold modus-themes-subtle-green)))) + '(avy-lead-face-2 ((t :inherit (bold modus-themes-subtle-magenta)))))) + +(use-package emacs :ensure nil + :init + (load-theme 'modus-operandi)) + +(use-package doom-themes) + +;;;; Shell and terminal +(setq shell-prompt-pattern "^[^#$%>\n]*[#$%>❯] *" + explicit-shell-file-name "zsh" + explicit-zsh-args '("--login" "--interactive") + comint-use-prompt-regexp t) +(defun kj/shell-buffer-name () + (concat "*shell:" default-directory "*")) + +(defun kj/shell-here () + (interactive) + (let ((buffer + (shell (kj/shell-buffer-name)))) + (with-current-buffer buffer + (let* ((proc (get-buffer-process (current-buffer))) + (sentinel (process-sentinel proc))) + (set-process-sentinel + proc + `(lambda (proc signal) + (funcall ',sentinel proc signal) + (and (memq (process-status proc) '(exit signal)) + (buffer-live-p (process-buffer proc)) + (message "Shell died, killing buffer %s" + (process-buffer proc)) + (kill-buffer (process-buffer proc))))))))) + +(use-package coterm + :config + (coterm-mode 1)) +(add-to-list 'comint-output-filter-functions + 'comint-osc-process-output) + +;; Update buffer name to reflect directory. +(advice-add 'cd :after + (defun kj/maybe-rename-shell (&rest args) + (and (derived-mode-p 'shell-mode) + (rename-buffer + (kj/shell-buffer-name))))) + +;;;; My Utilities +;;;;; Pass integration +(defun kj/pass-buffer () + (get-buffer-create "*pass*")) + +(defun kj/pass-echo (message) + (with-current-buffer (kj/pass-buffer) + (goto-char (point-max)) + (insert (propertize (concat message "\n") + 'face 'bold)))) + +(defun kj/pass-process (command &rest args) + (kj/pass-echo (format "$ %s" (string-join (cons command args) " "))) + (apply #'start-process "pass" (kj/pass-buffer) command args)) + +(defun kj/list-passwords () + (let* ((path (expand-file-name "~/.password-store")) + (pred (lambda (p) + (not (string-search (concat path "/.git") p)))) + (files (directory-files-recursively path "\\.gpg$" t pred t)) + (extract (lambda (p) + (string-remove-prefix (concat path "/") + (string-remove-suffix ".gpg" p)))) + (passwords (mapcar extract files))) + passwords)) + +(defun kj/pass-to-clipboard () + (interactive) + (let* ((password (completing-read "Password: " (kj/list-passwords))) + (process (kj/pass-process "pass" "show" "-c" password)) + (filter (lambda (proc m) (message "%s" (string-trim m))))) + (set-process-filter process filter))) + +(defun kj/pass-show () + (interactive) + (let* ((password (completing-read "Password: " (kj/list-passwords)))) + (kj/pass-echo "Showing password") + (kj/pass-process "pass" "show" password) + (display-buffer (kj/pass-buffer)))) + +(use-package with-editor) +(defun kj/pass-edit () + (interactive) + (let ((password (completing-read "Edit password: " (kj/list-passwords)))) + (with-editor + (kj/pass-process "pass" "edit" password)))) + +(defun kj/pass-generate (&optional args) + (interactive + (list (transient-args 'kj/pass-generate-transient))) + (let ((password (completing-read "Password name: " (kj/list-passwords) nil nil))) + (apply #'kj/pass-process "pass" "generate" `(,@args ,password)))) + +(keymap-global-set "C-c p" #'kj/pass) + +(use-package transient :ensure nil + :defer 1 + :init + (transient-define-prefix kj/pass-generate-transient () + "Generate" + [("-n" "No symbols" "--no-symbols") + ("-c" "Clipboard" "--clip")] + ["" + ("g" "generate" kj/pass-generate)]) + (transient-define-prefix + kj/transient () + "KJ Transient" + [;; "Arguments" + ;; ("-s" "Switch" "--switch") + ;; ("-a" "Another switch" "--another") + ;; ("-m" "Argument test" "--message=") + ] + ["Pass" + ("c" "Clipboard" kj/pass-to-clipboard) + ("s" "Show" kj/pass-show) + ("e" "Edit" kj/pass-edit) + ("g" "Generate" kj/pass-generate-transient)] + ["Repositories" + ("l" "List Repositories" magit-list-repositories) + ("C" "Clone my project" kj/clone-project)])) + +(keymap-global-set "C-c m" #'kj/transient) + +;;;; Applications +;;;;; Tramp +(use-package tramp :ensure nil) + +;;;;; Magit +;; (use-package magit :ensure nil) +;; (use-package transient) +(use-package magit + :after transient + :bind (:map magit-repolist-mode-map + ("c" . magit-clone) + ("i" . magit-init) + ("C" . kj/clone-project)) + :custom + (magit-repository-directories `((,kj/git-directory . 1)) "My git repositories") + (magit-repolist-columns '(("Name" 25 magit-repolist-column-ident nil) + ("Version" 25 magit-repolist-column-version + ((:sort magit-repolist-version<))) + ("B<U" 3 magit-repolist-column-unpulled-from-upstream + ((:right-align t) (:sort <))) + ("B>U" 3 magit-repolist-column-unpushed-to-upstream + ((:right-align t) (:sort <))) + ("F" 3 magit-repolist-column-flag + ((:right-align t) (:sort <))) + ("Path" 99 magit-repolist-column-path nil))) + :config + (defun kj/clone-project () + (interactive) + (let ((magit-clone-set-remote.pushDefault t)) + (magit-clone-regular (kj/read-repository) kj/git-directory nil)))) + +;;;;; Anki +;; (package-vc-install +;; '(anki-editor . (:url "https://github.com/anki-editor/anki-editor"))) +(use-package anki-editor :ensure nil + :after org + :bind (:map org-mode-map + ("<f12>" . anki-editor-cloze-region-auto-incr) + ("<f11>" . anki-editor-cloze-region-dont-incr) + ("<f6>" . anki-editor-push-tree)) + :hook (org-capture-after-finalize . anki-editor-reset-cloze-number) ; Reset cloze-number after each capture. + :config + (setq anki-editor-create-decks t ;; Allow anki-editor to create a new deck if it doesn't exist + anki-editor-org-tags-as-anki-tags t) + + (defun anki-editor-cloze-region-auto-incr (&optional arg) + "Cloze region without hint and increase card number." + (interactive) + (anki-editor-cloze-region my-anki-editor-cloze-number "") + (setq my-anki-editor-cloze-number (1+ my-anki-editor-cloze-number)) + (forward-sexp)) + (defun anki-editor-cloze-region-dont-incr (&optional arg) + "Cloze region without hint using the previous card number." + (interactive) + (anki-editor-cloze-region (1- my-anki-editor-cloze-number) "") + (forward-sexp)) + (defun anki-editor-reset-cloze-number (&optional arg) + "Reset cloze number to ARG or 1" + (interactive) + (setq my-anki-editor-cloze-number (or arg 1))) + (defun anki-editor-push-tree () + "Push all notes under a tree." + (interactive) + (anki-editor-push-notes '(4)) + (anki-editor-reset-cloze-number)) + ;; Initialize + (anki-editor-reset-cloze-number)) + +;;;; Org mode +(use-package org + :config + (org-babel-do-load-languages 'org-babel-load-languages + '((emacs-lisp . t) + (sql . t) + (shell . t))) + (setq org-confirm-babel-evaluate nil + org-return-follows-link t + ) + + (setopt org-capture-templates + '(("s" "Sailing entry" plain (file+datetree "~/org/roam/sailing.org") + "%^{LOCATION|Brooklyn}p\n%^{VESSEL|J80}p%^{SKIPPER}p%?") + ("a" "Anki templates") + ("ai" "Inbox basic" entry + (file+headline "~/org/anki/inbox.org" "Inbox") + "* %T %^g\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Basic\n:END:\n** Front\n%?\n** Back\n%x\n") + ("as" "Sailing basic" entry + (file+headline "~/org/anki/sailing.org" "Sailing") + "* %T\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Basic\n:END:\n** Front\n%?\n** Back\n%x\n") + ("az" "Sailing cloze" entry + (file+headline "~/org/anki/sailing.org" "Sailing") + "* %T\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Cloze\n:END:\n** Text\n%x%?\n** Extra\n"))) + + ;; Install the link type + (org-add-link-type "notmuch" 'org-notmuch-open) + (add-hook 'org-store-link-functions 'org-notmuch-store-link) + + (add-hook 'org-mode-hook + (defun kj/org-setup-media-handler () + (yank-media-handler "image/.*" #'kj/org--image-yank-media-handler))) + + (defun kj/org--image-yank-media-handler (mimetype data) + "Save image DATA of mime-type MIMETYPE and insert link at point. + It is saved as per `org-yank-image-save-type'. The name for the + image is prompted and the extension is automatically added to the + end." + (let* ((ext (symbol-name (mailcap-mime-type-to-extension mimetype))) + (iname (format-time-string "clipboard-%Y-%m-%d-%H:%M")) + (filename (file-name-with-extension iname ext)) + (absname (expand-file-name + filename + default-directory)) + link) + (with-temp-file absname + (insert data)) + (setq link (org-link-make-string + (concat "./" filename))) + (insert link))) + + (defun kj/org-screenshot () + (interactive) + (let* ((filename (format-time-string "screenshot-%Y-%m-%d-%H:%M:%S.png")) + (path (expand-file-name filename default-directory)) + (link (org-link-make-string (concat "file:" filename))) + (async-shell-command-display-buffer nil)) + (async-shell-command (format "grim -g \"$(slurp)\" %S" path)) + (insert link))) + + (defun org-notmuch-store-link () + "Store a link to a notmuch search or message." + (when (eq major-mode 'notmuch-show-mode) + (let* ((message-id (notmuch-show-get-prop :id)) + (subject (notmuch-show-get-subject)) + (to (notmuch-show-get-to)) + (from (notmuch-show-get-from)) + desc link) + (org-store-link-props :type "notmuch" :from from :to to + :subject subject :message-id message-id) + (setq desc (org-email-link-description)) + (setq link (concat "notmuch:" "id:" message-id)) + (org-add-link-props :link link :description desc) + link))) + + (defun org-notmuch-open (path) + "Follow a notmuch message link specified by PATH." + (org-notmuch-follow-link path)) + + (defun org-notmuch-follow-link (search) + "Follow a notmuch link to SEARCH. + +Can link to more than one message, if so all matching messages are shown." + (require 'notmuch) + (notmuch-show (org-link-unescape search))) + + (org-add-link-type "notmuch-search" 'org-notmuch-search-open) + (add-hook 'org-store-link-functions 'org-notmuch-search-store-link) + + (defun org-notmuch-search-store-link () + "Store a link to a notmuch search or message." + (when (eq major-mode 'notmuch-search-mode) + (let ((link (concat "notmuch-search:" + (org-link-escape notmuch-search-query-string))) + (desc (concat "Notmuch search: " notmuch-search-query-string))) + (org-store-link-props :type "notmuch-search" + :link link + :description desc) + link))) + + (defun org-notmuch-search-open (path) + "Follow a notmuch message link specified by PATH." + (message path) + (org-notmuch-search-follow-link path)) + + (defun org-notmuch-search-follow-link (search) + "Follow a notmuch link by displaying SEARCH in notmuch-search mode." + (require 'notmuch) + (notmuch-search (org-link-unescape search))) + + ;; If you use `org' and don't want your org files in the default location below, + ;; change `org-directory'. It must be set before org loads! + (setq org-directory "~/org/") + (setq org-todo-keywords + '((sequence + "TODO(t)" "ACTIVE(a!)" "WAIT(w@/!)" "|" "DONE(d!)" "CANCELED(c@)"))) + (setq org-refile-use-outline-path nil) + (setq org-refile-targets '((nil . (:maxlevel . 2)))) + (setq org-log-into-drawer t) + (setq org-agenda-log-mode-items '(closed clock state)) + (setq org-agenda-start-day nil) + (setq org-agenda-block-separator nil) + (setq org-agenda-compact-blocks t) + (setq org-agenda-start-with-log-mode nil) + (setq org-habit-show-all-today t) + (setq org-agenda-time-grid '((daily today require-timed) (800 1600) "......" "----------------------")) + (setq org-roam-directory (concat org-directory "roam/")) + (setq-default org-agenda-files '()) + (dolist (file '("tasks.org" + "running-2022.org" + "sailing.org")) + (add-to-list 'org-agenda-files (concat org-roam-directory file))) + (setq org-roam-db-location (concat org-roam-directory "/org-roam.db")) + (setq org-export-with-toc nil) + (setq deft-directory org-directory) + (setq deft-recursive t) + ;; Org html export + (setq org-html-htmlize-output-type 'css) + ;; Website publish settings. + (defvar kj/publish-tag "publish") + (defvar kj/publish-directory "/ssh:orbekk@dragon.orbekk.com:/storage/srv/kj.orbekk.com") + (add-to-list 'org-modules 'org-habit) + + (defvar org-publish-project-alist) + (defun kj/find-agenda-files-containing-tag (tag) + (setq org-agenda-archives-mode nil) + (let* ((org-agenda-archives-mode nil) + (candidates (org-agenda-files nil 'ifmode)) + (matcher (cdr (org-make-tags-matcher tag))) + (files)) + ;; (message "Results:") + (dolist (file candidates files) + (org-check-agenda-file file) + (with-current-buffer (org-get-agenda-file-buffer file) + ;; (message "%S" (org-scan-tags 'agenda matcher nil)) + (when (org-scan-tags 'agenda matcher nil) + (push file files)))))) + + (defun kj/org-publish (&optional project force) + (interactive) + (setq project (or project "all")) + (setq force (or force current-prefix-arg)) + (setq org-agenda-files (list org-roam-directory)) + (let* ((static-files-re (string-join '("css" "txt" "jpg" "png" "gif" "svg") "\\|")) + (files-to-include (kj/find-agenda-files-containing-tag kj/publish-tag)) + ;; Disable org babel exports during publish entirely to speed up publish. + ;; This messes up babel output handling. + ;; (org-export-use-babel nil) + (org-babel-default-header-args + (kj/assq-replace '((:exports . "both") + (:eval . "never-export")) + org-babel-default-header-args)) + (org-publish-project-alist + `( + ("static" + :base-directory ,(concat org-roam-directory "/static") + :base-extension ,static-files-re + :recursive t + :publishing-directory ,(concat kj/publish-directory "/static") + :publishing-function org-publish-attachment) + ("source" + :base-directory ,org-roam-directory + :base-extension "org" + :exclude ".*" + :include ,files-to-include + :recursive t + :publishing-directory ,kj/publish-directory + :publishing-function org-publish-attachment) + ("html" + :base-directory ,org-roam-directory + :base-extension "org" + :recursive t + :exclude ".*" + :include ,files-to-include + :publishing-directory ,kj/publish-directory + :publishing-function org-html-publish-to-html + + :with-broken-links t + :with-toc nil + :with-latex t + :with-drawers t + :with-title t + :section-numbers nil + + ;; HTML options + :html-toplevel-hlevel 2 + :html-preamble "" + :html-postamble "" + :html-html5-fancy t + :html-doctype "html5" + :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"https://gongzhitaao.org/orgcss/org.css\"/>" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-container article) + + ("all" :components ("static" "source" "html"))))) + + (org-publish project force))) + ; (setq org-appear-autolinks t) + ; (use-package! org-appear + ; :hook (org-mode . org-appear-mode)) + + (setq org-log-done 'time) + (setq org-todo-keywords + '((sequence + "TODO(t)" ; A task that needs doing & is ready to do + "PROJ(p)" ; A project, which usually contains other tasks + "LOOP(r)" ; A recurring task + "STRT(s)" ; A task that is in progress + "WAIT(w)" ; Something external is holding up this task + "HOLD(h)" ; This task is paused/on hold because of me + "IDEA(i)" ; An unconfirmed and unapproved task or notion + "|" + "DONE(d!)" ; Task successfully completed + "KILL(k)") ; Task was cancelled, aborted or is no longer applicable + (sequence + "[ ](T)" ; A task that needs doing + "[-](S)" ; Task is in progress + "[?](W)" ; Task is being held up or paused + "|" + "[X](D)") ; Task was completed + (sequence + "|" + "OKAY(o)" + "YES(y)" + "NO(n)"))) + + (setq org-journal-file-type 'weekly) + (setq org-journal-dir org-roam-directory) + (setq org-journal-file-format "journal-%Y-%m-%d.org") + (setq org-journal-enable-agenda-integration t) + (defun kj/org-journal-init () + (interactive) + (save-excursion + (goto-char (point-min)) + (org-id-get-create) + (org-set-property "category" "journal"))) + + (defun kj/org-journal-file-header (time) + (format-time-string "#+title: Week of %F (%W)\n" + (org-journal--convert-time-to-file-type-time time))) + (setq org-journal-file-header #'kj/org-journal-file-header) + (add-hook 'org-journal-after-header-create-hook 'kj/org-journal-init) + + ;; Org + (add-to-list 'org-export-backends 'md) + + (require 'org-tempo)) + +(defun org-archive-done-tasks () + (interactive) + (org-map-entries + (lambda () + (org-archive-subtree) + (setq org-map-continue-from (org-element-property :begin (org-element-at-point)))) + "/DONE" 'file)) + +(use-package org-pomodoro + :ensure t + :after org + :commands (org-pomodoro) + :config + (setq + org-pomodoro-manual-break t + org-pomodoro-time-format "%m" + org-pomodoro-format "🍅%s" + org-pomodoro-play-sounds nil + alert-user-configuration (quote ((((:category . "org-pomodoro")) notifications nil)))) + :bind + (("C-c o p" . org-pomodoro))) + +(use-package dslide) + +;;;; Include modules in lisp/ (defvar kj/module-directory (expand-file-name "lisp" user-emacs-directory)) (push kj/module-directory load-path) -(require 'kj-lib) (require 'kj-init) -(cl-loop for path in (directory-files kj/module-directory) - if (string-match "\\([^/]*\\).el" path) - do (require (intern (match-string-no-properties 1 path)))) +(use-package emacs :ensure nil + :defer 1 + :init + (cl-loop for path in (directory-files kj/module-directory) + if (string-match "\\([^/]*\\).el" path) + do (require (intern (match-string-no-properties 1 path))))) + +;;;; Enable commands +(put 'narrow-to-region 'disabled nil) +(put 'downcase-region 'disabled nil) +(put 'upcase-region 'disabled nil) +(put 'list-timers 'disabled nil) +(put 'set-goal-column 'disabled nil) +(put 'scroll-left 'disabled nil) + +;;;; Local variables +;; Local Variables: +;; eval: (outline-minor-mode 1) +;; eval: (outline-hide-sublevels 2) +;; outline-regexp: ";;;;*" +;; End: |