changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / emacs/default.el

changeset 638: 6c0e4a44c082
parent: 849f72b72b41
child: f901de70a80e
author: Richard Westhaver <ellis@rwest.io>
date: Sat, 07 Sep 2024 22:34:12 -0400
permissions: -rw-r--r--
description: dblocks
1 ;;; default.el --- default config -*- lexical-binding: t -*-
2 
3 ;;; Code:
4 ;;; Settings
5 (require 'util)
6 (put 'upcase-region 'disabled nil)
7 (put 'list-threads 'disabled nil)
8 (put 'list-timers 'disabled nil)
9 (setq show-paren-context-when-offscreen 'overlay)
10 (setopt
11  org-safe-remote-resources '("\\`https://cdn\\.compiler\\.company/org/clean\\.theme\\'")
12  ;; tabs = bad (unless in makefile..)
13  indent-tabs-mode nil
14  make-backup-files nil
15  auto-save-list-file-prefix (expand-file-name "auto-save/." user-emacs-directory)
16  tramp-auto-save-directory (expand-file-name "auto-save/tramp/" user-emacs-directory)
17  dired-free-space nil
18  mml-attach-file-at-the-end t
19  dired-mouse-drag-files t
20  confirm-kill-emacs nil
21  confirm-kill-processes nil
22  use-short-answers t
23  display-time-format "%Y-%m-%d %H:%M"
24  ring-bell-function 'ignore
25  completion-ignore-case t
26  ;; NOTE 2023-11-04: you need to add the following lines to ~/.gnupg/gpg-agent.conf:
27  ;; allow-emacs-pinentry
28  ;; allow-loopback-pinentry
29  epg-pinentry-mode 'loopback
30  shr-use-colors nil
31  shr-use-fonts nil
32  shr-max-image-proportion 0.6
33  shr-image-animate nil
34  shr-discard-aria-hidden t
35  bookmark-default-file (expand-file-name "bookmarks" user-emacs-directory)
36  tempo-interactive t
37  emms-directory (expand-file-name "emms" user-emacs-directory)
38  gnus-cache-directory (expand-file-name "gnus" user-emacs-directory)
39  url-cache-directory (expand-file-name "url" user-emacs-directory)
40  tab-always-indent 'complete
41  shr-cookie-policy nil
42  ;; NOTE 2023-11-04: EXPERIMENTAL
43  ediff-floating-control-frame t
44  register-use-preview nil
45  shr-use-xwidgets-for-media t
46  which-key-mode t
47  view-read-only t)
48 
49 ;;; Treesitter
50 
51 ;;(add-to-list 'treesit-extra-load-path "/usr/local/lib/")
52 
53 ;; (let ((grammar-dir "/usr/local/share/tree-sitter/"))
54 ;; (when (file-exists-p grammar-dir)
55 ;; (setq treesit-extra-load-path
56 ;; (append
57 ;; (flatten
58 ;; (mapcar
59 ;; (lambda (f)
60 ;; (unless (or (string= "." f) (string= ".." f))
61 ;; (concat grammar-dir f)))
62 ;; (directory-files "/usr/local/share/tree-sitter")))
63 ;; treesit-extra-load-path))))
64 
65 ;;; Variables
66 (defvar user-emacs-lib-directory (expand-file-name (join-paths user-emacs-directory "lib")))
67 (defvar user-custom-file (expand-file-name (format "%s.el" user-login-name) user-emacs-directory))
68 (defvar user-home-directory (expand-file-name "~"))
69 (defvar user-lab-directory (expand-file-name "lab" user-home-directory))
70 (defvar user-stash-directory (expand-file-name ".stash" user-home-directory))
71 (defvar user-store-directory (expand-file-name ".store" user-home-directory))
72 (defvar user-mail-directory (expand-file-name "mail" user-home-directory))
73 (defvar user-org-stash-directory (expand-file-name "org" user-stash-directory))
74 (defvar default-theme 'leuven-dark)
75 (defvar company-source-directory (join-paths user-home-directory "comp"))
76 (defvar company-org-directory (join-paths company-source-directory "org"))
77 (defvar company-domain "compiler.company")
78 (defvar company-name "The Compiler Company, LLC")
79 (defvar company-vc-domain "vc.compiler.company")
80 (defvar company-home "the.compiler.company")
81 (defvar company-cdn-url "https://cdn.compiler.company")
82 
83 ;;; Theme
84 (defun load-default-theme (&optional theme)
85  (interactive)
86  (when theme (setq default-theme theme))
87  (load-theme default-theme))
88 
89 ;;; Packages
90 (with-eval-after-load 'package
91  (setq package-archives
92  '(("gnu" . "https://elpa.gnu.org/packages/")
93  ("nongnu" . "https://elpa.nongnu.org/nongnu/")
94  ("melpa" . "https://melpa.org/packages/")))
95  (setopt
96  use-package-always-ensure t
97  use-package-expand-minimally t)
98  (add-packages
99  eglot-x ;; LSP extensions
100  org-web-tools ;; web parsing
101  citeproc ;; citations
102  htmlize ;; html export
103  all-the-icons all-the-icons-dired all-the-icons-ibuffer ;; icons
104  hide-mode-line) ;; ui
105  ;; bbdb
106  (package-install-selected-packages t))
107 
108 ;;; Env
109 (require 'exec-path-from-shell)
110 (exec-path-from-shell-copy-envs (list "SSH_AGENT_PID"
111  "SSH_AUTH_SOCK"
112  "PATH"
113  "CARGO_HOME"
114  "CC"
115  "LD"
116  "LD_LIBRARY_PATH"
117  "RUSTUP_HOME"
118  "QUICKLISP_HOME"
119  "DEV" "DEV_ID" "DEV_HOME"
120  "WORKER" "WORKER_ID" "WORKER_HOME"
121  "SBCL_HOME"
122  "STASH"
123  "STORE"
124  "LISP_HOME"))
125 
126 (add-to-list 'exec-path (expand-file-name "~/.cargo/bin/"))
127 (add-to-list 'exec-path (expand-file-name "~/.local/bin/"))
128 (add-to-list 'exec-path "/bin/")
129 (add-to-list 'exec-path "/usr/local/sbin/")
130 (add-to-list 'exec-path "/usr/local/bin/")
131 (add-to-list 'exec-path "/usr/local/share/lisp/bin/")
132 
133 ;;; Completions
134 (use-package cape :ensure t)
135 (use-package orderless
136  :ensure t
137  :init
138  (setq completion-styles '(orderless partial-completion basic)
139  completion-category-overrides '((file (styles basic partial-completion))
140  (eglot (styles orderless))
141  (eglot-capf (styles orderless)))))
142 
143 (use-package corfu
144  :ensure t
145  :config
146  (global-corfu-mode)
147  (corfu-popupinfo-mode)
148  (corfu-echo-mode)
149  (dolist (c (list (cons "SPC" " ")
150  (cons "." ".")
151  (cons "," ",")
152  (cons ":" ":")
153  (cons ")" ")")
154  (cons "}" "}")
155  (cons "]" "]")))
156  (define-key corfu-map (kbd (car c)) `(lambda ()
157  (interactive)
158  (corfu-insert)
159  (insert ,(cdr c)))))
160  (add-to-list 'completion-at-point-functions #'cape-dabbrev t)
161  (add-to-list 'completion-at-point-functions #'cape-abbrev t)
162  (add-to-list 'completion-at-point-functions #'cape-file)
163  (defun corfu-move-to-minibuffer ()
164  (interactive)
165  (pcase completion-in-region--data
166  (`(,beg ,end ,table ,pred ,extras)
167  (let ((completion-extra-properties extras)
168  completion-cycle-threshold completion-cycling)
169  (consult-completion-in-region beg end table pred)))))
170  (keymap-set corfu-map "M-m" #'corfu-move-to-minibuffer)
171  (add-to-list 'corfu-continue-commands #'corfu-move-to-minibuffer)
172  (unless (display-graphic-p)
173  (require 'corfu-terminal)
174  (corfu-terminal-mode 1)))
175 
176 (use-package kind-icon
177  :ensure t
178  :after corfu
179  ;:custom
180  ; (kind-icon-blend-background t)
181  ; (kind-icon-default-face 'corfu-default) ; only needed with blend-background
182  :config
183  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
184 
185 
186 (use-package vertico
187  :ensure t
188  :config (vertico-mode)
189  (keymap-set vertico-map "M-q" #'vertico-quick-insert)
190  (keymap-set vertico-map "C-q" #'vertico-quick-exit))
191 
192 (use-package marginalia :ensure t
193  :config (marginalia-mode))
194 (use-package embark
195  :ensure t)
196 (use-package embark-consult :ensure t)
197 (use-package consult :ensure t)
198 
199 ;;; Desktop
200 (setopt desktop-dirname (expand-file-name "sessions" user-emacs-directory))
201 
202 ;;; Multisession
203 (setq multisession-storage 'sqlite)
204 
205 ;;; Kill Ring
206 (kill-ring-deindent-mode)
207 
208 ;;; VC
209 ;; use rhg, fallback to hg. see hgrc
210 (if (file-exists-p "~/.local/bin/rhg")
211  (setq hg-binary "~/.local/bin/rhg"))
212 
213 ;;; Dired
214 ;;; Projects
215 (setopt project-list-file (expand-file-name "projects" user-emacs-directory)
216  project-mode-line t
217  project-file-history-behavior 'relativize)
218 
219 ;;; Tabs
220 (add-hook 'tab-bar-mode-hook #'tab-bar-history-mode)
221 
222 ;;; Lisp
223 (use-package company :ensure t)
224 (use-package slime-repl-ansi-color :ensure t)
225 
226 (use-package slime
227  :ensure t
228  :after (slime-cape slime-repl-ansi-color)
229  :init
230  (require 'slime-cape)
231  (setq slime-contribs '(slime-fancy
232  slime-quicklisp
233  slime-hyperdoc
234  ;; slime-listener-hooks
235  ;; slime-enclosing-context
236  ;; slime-media
237  ;; slime-mrepl
238  slime-sbcl-exts
239  slime-cape ;; ext
240  slime-repl-ansi-color
241  ;; slime-cl-indent
242  ;; slime-snapshot
243  slime-sprof
244  slime-tramp
245  ;; slime-typeout-frame
246  slime-xref-browser
247  slime-repl-ansi-color
248  ;; slime-highlight-edits
249  slime-asdf))
250  (put 'make-instance 'common-lisp-indent-function 1)
251  (put 'reinitialize-instance 'common-lisp-indent-function 1)
252  (slime-setup)
253  (defvar slime-toggle nil)
254  (defun slime-toggle ()
255  "toggle between lisp file and slime-repl"
256  (interactive)
257  (cond
258  ((eq major-mode 'slime-repl-mode)
259  (setq slime-toggle (pop-to-buffer (or slime-toggle (read-buffer "lisp buffer: ")))))
260  ((not (eq major-mode 'slime-repl-mode))
261  (if (slime-connected-p)
262  (progn
263  (setq slime-toggle (current-buffer))
264  (slime-switch-to-output-buffer))
265  (setq slime-toggle (current-buffer))
266  (slime)))))
267 
268  ;; X11-only (mcclim requires clx)
269  (defun clouseau-inspect (string)
270  "Inspect a lisp value with Clouseau. make sure to load clouseau
271 with a custom core or in your init file before using this
272 function: '(ql:quickload :clouseau)'."
273  (interactive
274  (list (slime-read-from-minibuffer
275  "Inspect value (evaluated): "
276  (slime-sexp-at-point))))
277  (let ((inspector 'cl-user::*clouseau-inspector*))
278  (slime-eval-async
279  `(cl:progn
280  (cl:defvar ,inspector nil)
281  ;; (Re)start the inspector if necessary.
282  (cl:unless (cl:and (clim:application-frame-p ,inspector)
283  (clim-internals::frame-process ,inspector))
284  (cl:setf ,inspector (cl:nth-value 1 (clouseau:inspect nil :new-process t))))
285  ;; Tell the inspector to visualize the correct datum.
286  (cl:setf (clouseau:root-object ,inspector :run-hook-p t)
287  (cl:eval (cl:read-from-string ,string)))
288  ;; Return nothing.
289  (cl:values)))))
290 
291  (define-common-lisp-style "core" "Core Common Lisp Indentation Style"
292  (:inherit "sbcl")
293  (:indentation
294  (defpkg (as defpackage))
295  (define-package (as defpackage))))
296 
297  ;; lisp font-lock defaults: https://www.n16f.net/blog/custom-font-lock-configuration-in-emacs/
298  ;; (defface cl-character-face
299  ;; '((default :inherit font-lock-constant-face))
300  ;; "The face used to highlight Common Lisp character literals.")
301 
302  ;; (defface cl-standard-function-face
303  ;; '((default :inherit font-lock-keyword-face))
304  ;; "The face used to highlight standard Common Lisp function symbols.")
305 
306  ;; (defface cl-standard-value-face
307  ;; '((default :inherit font-lock-variable-name-face))
308  ;; "The face used to highlight standard Common Lisp value symbols.")
309 
310  ;; (defvar cl-font-lock-keywords
311  ;; (let* ((character-re (concat "#\\\\" lisp-mode-symbol-regexp "\\_>"))
312  ;; (function-re (concat "(" (regexp-opt cl-function-names t) "\\_>"))
313  ;; (value-re (regexp-opt cl-value-names 'symbols)))
314  ;; `((,character-re . 'cl-character-face)
315  ;; (,function-re
316  ;; (1 'cl-standard-function-face))
317  ;; (,value-re . 'cl-standard-value-face))))
318 
319  (setq common-lisp-style-default "core")
320  ;; (define-key slime-prefix-map (kbd "i") 'clouseau-inspect)
321  (setq slime-threads-update-interval 1)
322  (add-hook 'slime-mode-hook #'slime-cape-maybe-enable 100)
323  (add-hook 'slime-repl-mode-hook #'slime-cape-maybe-enable 100))
324 
325 (use-package lisp-mode
326  :ensure nil
327  :after slime
328  :custom
329  inferior-lisp-program "sbcl --dynamic-space-size=8G"
330  scheme-program-name "gsi"
331  guile-program "guile"
332  cmulisp-program "lisp"
333  scsh-program "scsh")
334 
335 ;;; Eglot
336 (with-eval-after-load 'eglot
337  (unless (package-installed-p 'eglot-x)
338  (package-vc-install '(eglot-x :url "https://vc.compiler.company/packy/eglot-x.git")))
339  (require 'eglot-x)
340  (with-eval-after-load 'eglot-x
341  (add-to-list 'eglot-server-programs
342  '((rust-ts-mode rust-mode) .
343  ("rust-analyzer" :initializationOptions (:check (:command "clippy")))))
344  (eglot-x-setup)))
345 
346 ;;; Rust
347 (add-hook 'rust-mode-hook 'eglot-ensure)
348 
349 (setq rust-rustfmt-switches nil
350  rust-indent-offset 2)
351 
352 ;;; Python
353 (setq python-indent-offset 2)
354 (add-hook 'python-mode-hook 'eglot-ensure)
355 
356 ;;; Javascript
357 (setq js-indent-level 2)
358 
359 ;;; Bash
360 (setq sh-basic-offset 2)
361 
362 ;;; Graphviz
363 (use-package graphviz-dot-mode
364  :config
365  (setq graphviz-dot-indent-width 2))
366 ;;; Comments
367 (defcustom prog-comment-keywords
368  '("TODO" "REVIEW" "FIX" "HACK" "RESEARCH")
369  "List of strings with comment keywords."
370  :group 'default)
371 
372 (defcustom prog-comment-timestamp-format-concise "%F"
373  "Specifier for date in `prog-comment-timestamp-keyword'.
374 Refer to the doc string of `format-time-string' for the available
375 options."
376  :group 'default)
377 
378 (defcustom prog-comment-timestamp-format-verbose "%F %T %z"
379  "Like `prog-comment-timestamp-format-concise', but longer."
380  :group 'default)
381 
382 ;;;###autoload
383 (defun prog-comment-dwim (arg)
384  "Flexible, do-what-I-mean commenting.
385 
386 If region is active and ARG is either a numeric argument greater
387 than one or a universal prefix (\\[universal-argument]), then
388 apply `comment-kill' on all comments in the region.
389 
390 If the region is active and no ARG is supplied, or is equal to a
391 numeric prefix of 1, then toggle the comment status of the region.
392 
393 Else toggle the comment status of the line at point. With a
394 numeric prefix ARG, do so for ARGth lines (negative prefix
395 operates on the lines before point)."
396  (interactive "p")
397  (cond
398  ((and (> arg 1) (use-region-p))
399  (let* ((beg (region-beginning))
400  (end (region-end))
401  (num (count-lines beg end)))
402  (save-excursion
403  (goto-char beg)
404  (comment-kill num))))
405  ((use-region-p)
406  (comment-or-uncomment-region (region-beginning) (region-end)))
407  (t
408  (save-excursion (comment-line (or arg 1))))))
409 
410 (defvar prog-comment--keyword-hist '()
411  "Input history of selected comment keywords.")
412 
413 (defun prog-comment--keyword-prompt (keywords)
414  "Prompt for candidate among KEYWORDS."
415  (let ((def (car prog-comment--keyword-hist)))
416  (completing-read
417  (format "Select keyword [%s]: " def)
418  keywords nil nil nil 'prog-comment--keyword-hist def)))
419 
420 
421 ;;;###autoload
422 (defun prog-comment-timestamp-keyword (keyword &optional verbose)
423  "Add timestamped comment with KEYWORD.
424 
425 When called interactively, the list of possible keywords is that
426 of `prog-comment-keywords', though it is possible to
427 input arbitrary text.
428 
429 If point is at the beginning of the line or if line is empty (no
430 characters at all or just indentation), the comment is started
431 there in accordance with `comment-style'. Any existing text
432 after the point will be pushed to a new line and will not be
433 turned into a comment.
434 
435 If point is anywhere else on the line, the comment is indented
436 with `comment-indent'.
437 
438 The comment is always formatted as 'DELIMITER KEYWORD DATE:',
439 with the date format being controlled by the variable
440 `prog-comment-timestamp-format-concise'.
441 
442 With optional VERBOSE argument (such as a prefix argument
443 `\\[universal-argument]'), use an alternative date format, as
444 specified by `prog-comment-timestamp-format-verbose'."
445  (interactive
446  (list
447  (prog-comment--keyword-prompt prog-comment-keywords)
448  current-prefix-arg))
449  (let* ((date (if verbose
450  comment-timestamp-format-verbose
451  prog-comment-timestamp-format-concise))
452  (string (format "%s %s: " keyword (format-time-string date)))
453  (beg (point)))
454  (cond
455  ((or (eq beg (pos-bol))
456  (default-line-regexp-p 'empty))
457  (let* ((maybe-newline (unless (default-line-regexp-p 'empty 1) "\n")))
458  ;; NOTE 2021-07-24: we use this `insert' instead of
459  ;; `comment-region' because of a yet-to-be-determined bug that
460  ;; traps `undo' to the two states between the insertion of the
461  ;; string and its transformation into a comment.
462  (insert
463  (concat comment-start
464  ;; NOTE 2021-07-24: See function `comment-add' for
465  ;; why we need this.
466  (make-string
467  (comment-add nil)
468  (string-to-char comment-start))
469  comment-padding
470  string
471  comment-end))
472  (indent-region beg (point))
473  (when maybe-newline
474  (save-excursion (insert maybe-newline)))))
475  (t
476  (comment-indent t)
477  (insert (concat " " string))))))
478 
479 (setq hexl-bits 8)
480 (setq tab-width 4)
481 
482 ;;; Keyboard Macros
483 (defun toggle-macro-recording ()
484  (interactive)
485  (if defining-kbd-macro
486  (end-kbd-macro)
487  (start-kbd-macro nil)))
488 
489 (defun play-macro-if-not-playing ()
490  (interactive)
491  (if defining-kbd-macro
492  (end-kbd-macro)
493  (call-last-kbd-macro)))
494 
495 ;;; Registers
496 ;; - additional register vtypes: buffer
497 (defun decrement-register (number register)
498  "Subtract NUMBER from the contents of register REGISTER.
499 Interactively, NUMBER is the prefix arg."
500  (interactive "p\ncDecrement register: ")
501  (increment-register (- number) register))
502 
503 (defun copy-register (a b)
504  "Copy register A to B."
505  (interactive
506  (list (register-read-with-preview "From register: ")
507  (register-read-with-preview "To register: ")))
508  (set-register b (get-register a)))
509 
510 (defun buffer-to-register (register &optional delete)
511  "Put current buffer in register - this would also work for
512  just buffers, as switch-to-buffer can use both, but it
513  facilitates for easier saving/restoring of registers."
514  (interactive "cPut current buffername in register: \nP.")
515  (set-register register (cons 'buffer (buffer-name (current-buffer)))))
516 
517 (defun file-to-register (register &optional delete)
518  "This is better than put-buffer-in-register for file-buffers, because a closed
519  file can be opened again, but does not work for no-file-buffers."
520  (interactive "cPut the filename of current buffer in register: \nP")
521  (set-register register (cons 'file (buffer-file-name (current-buffer)))))
522 
523 (defun file-query-to-register (register &optional delete)
524  (interactive
525  (list
526  (register-read-with-preview "File query to register: ")))
527  (set-register register (list 'file-query (buffer-file-name (current-buffer)) (point))))
528 
529 ;; additional register-val handlers
530 ;; (cl-defmethod register-val-jump-to :around ((val cons) delete)
531 ;; (cond
532 ;; (t (cl-call-next-method val delete))))
533 
534 ;;; Outlines
535 (defun outline-hook (&optional rx)
536  "Enable `outline-minor-mode' and set `outline-regexp'."
537  (when rx (setq-local outline-regexp rx))
538  (outline-minor-mode 1))
539 
540 (setq outline-minor-mode-use-buttons nil)
541 
542 (defun add-outline-hook (mode &optional rx)
543  (let ((sym (symb mode "-hook")))
544  (add-hook sym (lambda () (outline-hook rx)))))
545 
546 (defmacro outline-hooks (&rest pairs)
547  `(mapc (lambda (x) (add-outline-hook (car x) (cadr x))) ',pairs))
548 
549 (outline-hooks (asm-mode ";;;+")
550  (nasm-mode ";;;+")
551  (rust-mode "\\(//!\\|////+\\)")
552  (sh-mode "###+")
553  (sh-script-mode "###+")
554  (makefile-mode "###+")
555  (conf-mode "###+")
556  (common-lisp-mode)
557  (emacs-lisp-mode)
558  (lisp-data-mode)
559  (org-mode)
560  (css-mode)
561  (html-mode)
562  (skel-mode))
563 
564 ;;; Scratch
565 (defcustom default-scratch-buffer-mode 'lisp-interaction-mode
566  "Default major mode for new scratch buffers"
567  :group 'default)
568 
569 ;; Adapted from the `scratch.el' package by Ian Eure.
570 (defun default-scratch-list-modes ()
571  "List known major modes."
572  (cl-loop for sym the symbols of obarray
573  for name = (symbol-name sym)
574  when (and (functionp sym)
575  (not (member sym minor-mode-list))
576  (string-match "-mode$" name)
577  (not (string-match "--" name)))
578  collect name))
579 
580 (defun default-scratch-buffer-setup (region &optional mode)
581  "Add contents to `scratch' buffer and name it accordingly.
582 
583 REGION is added to the contents to the new buffer.
584 
585 Use the current buffer's major mode by default. With optional
586 MODE use that major mode instead."
587  (let* ((major (or mode major-mode))
588  (string (format "Scratch buffer for: %s\n\n" major))
589  (text (concat string region))
590  (buf (format "*Scratch for %s*" major)))
591  (with-current-buffer (get-buffer-create buf)
592  (funcall major)
593  (save-excursion
594  (insert text)
595  (goto-char (point-min))
596  (comment-region (pos-bol) (pos-eol)))
597  (vertical-motion 2))
598  (pop-to-buffer buf)))
599 
600 ;;;###autoload
601 (defun default-scratch-buffer (&optional arg)
602  "Produce a bespoke scratch buffer matching current major mode.
603 
604 With optional ARG as a prefix argument (\\[universal-argument]),
605 use `default-scratch-buffer-mode'.
606 
607 With ARG as a double prefix argument, prompt for a major mode
608 with completion.
609 
610 If region is active, copy its contents to the new scratch
611 buffer."
612  (interactive "P")
613  (let* ((default-mode default-scratch-buffer-mode)
614  (modes (default-scratch-list-modes))
615  (region (with-current-buffer (current-buffer)
616  (if (region-active-p)
617  (buffer-substring-no-properties
618  (region-beginning)
619  (region-end))
620  "")))
621  (m))
622  (pcase (prefix-numeric-value arg)
623  (16 (progn
624  (setq m (intern (completing-read "Select major mode: " modes nil t)))
625  (default-scratch-buffer-setup region m)))
626  (4 (default-scratch-buffer-setup region default-mode))
627  (_ (default-scratch-buffer-setup region)))))
628 
629 ;;;###autoload
630 (defun scratch-new ()
631  "create a new scratch buffer. (could be *scratch* - *scratchN*)"
632  (interactive)
633  (let ((n 0)
634  bufname)
635  (while (progn
636  (setq bufname
637  (concat "*scratch"
638  (if (= n 0) "" (int-to-string n))
639  "*"))
640  (setq n (1+ n))
641  (get-buffer bufname)))
642  (switch-to-buffer (get-buffer-create bufname))
643  (insert initial-scratch-message)
644  (lisp-interaction-mode)))
645 
646 ;;; Shell
647 (defun set-no-process-query-on-exit ()
648  (let ((proc (get-buffer-process (current-buffer))))
649  (when (processp proc)
650  (set-process-query-on-exit-flag proc nil))))
651 
652 (add-hook 'shell-mode-hook 'set-no-process-query-on-exit)
653 (add-hook 'term-exec-hook 'set-no-process-query-on-exit)
654 
655 ;;; Eshell
656 (defun eshell-new()
657  "Open a new instance of eshell."
658  (interactive)
659  (eshell 'Z))
660 
661 (setq eshell-highlight-prompt t
662  eshell-hist-ignoredups t
663  eshell-save-history-on-exit t
664  eshell-prefer-lisp-functions nil
665  eshell-destroy-buffer-when-process-dies t)
666 
667 (add-hook 'eshell-mode-hook
668  (lambda ()
669  (eshell/alias "d" "dired $1")
670  (eshell/alias "ff" "find-file $1")
671  (eshell/alias "hgfe" "hg-fast-export.sh")))
672 
673 (defun eshell/clear ()
674  "Clear the eshell buffer."
675  (let ((inhibit-read-only t))
676  (erase-buffer)
677  (eshell-send-input)))
678 
679 (defun eshell-quit-or-delete-char (arg)
680  (interactive "p")
681  (if (and (eolp) (looking-back eshell-prompt-regexp))
682  (progn
683  (eshell-life-is-too-much) ; Why not? (eshell/exit)
684  (ignore-errors
685  (delete-window)))
686  (delete-forward-char arg)))
687 
688 (add-hook 'eshell-mode-hook
689  (lambda ()
690  (bind-keys :map eshell-mode-map
691  ("C-d" . eshell-quit-or-delete-char))))
692 
693 (defun eshell-next-prompt (n)
694  "Move to end of Nth next prompt in the buffer. See `eshell-prompt-regexp'."
695  (interactive "p")
696  (re-search-forward eshell-prompt-regexp nil t n)
697  (when eshell-highlight-prompt
698  (while (not (get-text-property (line-beginning-position) 'read-only) )
699  (re-search-forward eshell-prompt-regexp nil t n)))
700  (eshell-skip-prompt))
701 
702 (defun eshell-previous-prompt (n)
703  "Move to end of Nth previous prompt in the buffer. See `eshell-prompt-regexp'."
704  (interactive "p")
705  (backward-char)
706  (eshell-next-prompt (- n)))
707 
708 (defun eshell-insert-history ()
709  "Displays the eshell history to select and insert back into your eshell."
710  (interactive)
711  (insert (ido-completing-read "Eshell history: "
712  (delete-dups
713  (ring-elements eshell-history-ring)))))
714 
715 ;;; Eww
716 (setopt
717  browse-url-browser-function 'eww
718  eww-auto-rename-buffer 'title
719  eww-search-prefix "https://google.com/search?q=")
720 
721 ;; ref: https://github.com/oantolin/emacs-config/blob/master/my-lisp/shr-heading.el
722 (defun shr-heading-next (&optional arg)
723  "Move forward by ARG headings (any h1-h4).
724 If ARG is negative move backwards, ARG defaults to 1."
725  (interactive "p")
726  (unless arg (setq arg 1))
727  (catch 'return
728  (dotimes (_ (abs arg))
729  (when (> arg 0) (end-of-line))
730  (if-let ((match
731  (funcall (if (> arg 0)
732  #'text-property-search-forward
733  #'text-property-search-backward)
734  'face '(shr-h1 shr-h2 shr-h3 shr-h4)
735  (lambda (tags face)
736  (cl-loop for x in (if (consp face) face (list face))
737  thereis (memq x tags))))))
738  (goto-char
739  (if (> arg 0) (prop-match-beginning match) (prop-match-end match)))
740  (throw 'return nil))
741  (when (< arg 0) (beginning-of-line)))
742  (beginning-of-line)
743  (point)))
744 
745 (defun shr-heading-previous (&optional arg)
746  "Move backward by ARG headings (any h1-h4).
747 If ARG is negative move forwards instead, ARG defaults to 1."
748  (interactive "p")
749  (shr-heading-next (- (or arg 1))))
750 
751 (defun shr-heading--line-at-point ()
752  "Return the current line."
753  (buffer-substring (line-beginning-position) (line-end-position)))
754 
755 (defun shr-heading-setup-imenu ()
756  "Setup imenu for h1-h4 headings in eww buffer.
757 Add this function to appropriate major mode hooks such as
758 `eww-mode-hook' or `elfeed-show-mode-hook'."
759  (setq-local
760  imenu-prev-index-position-function #'shr-heading-previous
761  imenu-extract-index-name-function #'shr-heading--line-at-point))
762 
763 (defvar shr-heading-map
764  (let ((map (make-sparse-keymap)))
765  (define-key map "n" #'shr-heading-next)
766  (define-key map "\C-n" #'shr-heading-next)
767  (define-key map "p" #'shr-heading-previous)
768  (define-key map "\C-p" #'shr-heading-previous)
769  map))
770 
771 (add-hook 'eww-mode-hook 'shr-heading-setup-imenu)
772 (add-hook 'eww-mode-hook (lambda () (define-key eww-mode-map "i" shr-heading-map)))
773 
774 ;;; Tramp
775 (setopt tramp-default-method "ssh"
776  tramp-default-user user-login-name
777  tramp-default-host "localhost")
778 
779 ;;; Imenu
780 (use-package imenu-list :ensure t)
781 
782 ;;; Org
783 (require 'org)
784 (require 'org-agenda)
785 (require 'org-id)
786 (require 'org-protocol)
787 
788 (setq org-html-htmlize-output-type 'css
789  org-html-head-include-default-style nil
790  ;; cc default
791  org-ascii-text-width 80)
792 
793 (org-crypt-use-before-save-magic)
794 
795 (setq org-structure-template-alist
796  '(("s" . "src")
797  ("e" . "src emacs-lisp")
798  ("x" . "src shell")
799  ("l" . "src lisp")
800  ("h" . "export html")
801  ("p" . "src python")
802  ("r" . "src rust")
803  ("E" . "example")
804  ("q" . "quote")
805  ("c" . "center")
806  ("C" . "comment")
807  ("v" . "verse")))
808 
809 ;; org-sbx [[https://list.orgmode.org/d429d29b-42fa-7d7b-6f3a-9fe692fd6dc7@grinta.net/T/]]
810 (defun %org-sbx (name header args)
811  (let* ((args (mapconcat
812  (lambda (x)
813  (format "%s=%S" (symbol-name (car x)) (cadr x)))
814  args ", "))
815  (ctx (list 'babel-call (list :call name
816  :name name
817  :inside-header header
818  :arguments args
819  :end-header ":results silent")))
820  (info (org-babel-lob-get-info ctx)))
821  (when info (org-babel-execute-src-block nil info))))
822 
823 (defmacro org-sbx (name &rest args)
824  (let* ((header (if (stringp (car args)) (car args) nil))
825  (args (if (stringp (car args)) (cdr args) args)))
826  (unless (stringp name)
827  (setq name (symbol-name name)))
828  (let ((result (%org-sbx name header args)))
829  (org-trim (if (stringp result) result (format "%S" result))))))
830 
831 (defun org-babel-execute-region (beg end &optional arg)
832  (interactive "r")
833  (narrow-to-region beg end)
834  (org-babel-execute-buffer arg)
835  (widen))
836 
837 (defun org-schedule-effort ()
838  (interactive)
839  (save-excursion
840  (org-back-to-heading t)
841  (let* ((element (org-element-at-point))
842  (effort (org-element-property :EFFORT element))
843  (scheduled (org-element-property :scheduled element))
844  (ts-year-start (org-element-property :year-start scheduled))
845  (ts-month-start (org-element-property :month-start scheduled))
846  (ts-day-start (org-element-property :day-start scheduled))
847  (ts-hour-start (org-element-property :hour-start scheduled))
848  (ts-minute-start (org-element-property :minute-start scheduled)) )
849  (org-schedule nil (concat
850  (format "%s" ts-year-start)
851  "-"
852  (if (< ts-month-start 10)
853  (concat "0" (format "%s" ts-month-start))
854  (format "%s" ts-month-start))
855  "-"
856  (if (< ts-day-start 10)
857  (concat "0" (format "%s" ts-day-start))
858  (format "%s" ts-day-start))
859  " "
860  (if (< ts-hour-start 10)
861  (concat "0" (format "%s" ts-hour-start))
862  (format "%s" ts-hour-start))
863  ":"
864  (if (< ts-minute-start 10)
865  (concat "0" (format "%s" ts-minute-start))
866  (format "%s" ts-minute-start))
867  "+"
868  effort)) )))
869 
870 (setopt org-preview-latex-image-directory "~/.emacs.d/.cache/ltximg"
871  org-latex-image-default-width "8cm"
872  org-refile-use-cache t
873  org-refile-allow-creating-parent-nodes 'confirm
874 
875  org-refile-targets '((nil :maxlevel . 3)
876  (org-agenda-files :maxlevel . 3))
877  ;; org-agenda-files (list "inbox.org")
878  org-agenda-include-diary t
879  org-agenda-include-inactive-timestamps t
880  org-confirm-babel-evaluate nil
881  org-src-fontify-natively t
882  org-src-tabs-act-natively t
883  org-footnote-section nil
884  org-log-into-drawer t
885  org-log-refile 'time
886  org-log-redeadline 'time
887  org-log-states-order-reversed nil
888  org-clock-persist 'history)
889 
890 (add-hook 'after-init-hook #'org-clock-persistence-insinuate)
891 
892 ;; archive
893 (defun extract-org-directory-titles-as-list (&optional dir)
894  (interactive "D")
895  (print
896  (delete nil
897  (let ((case-fold-search t))
898  (mapcar (lambda (f)
899  (when (string-match "org$" f)
900  (with-temp-buffer
901  (insert-file-contents-literally
902  (concat (file-name-as-directory dir) f))
903  (while (and (not (looking-at-p "#\\+TITLE:"))
904  (not (eobp)))
905  (forward-line))
906  (when (not (eobp))
907  (cons f (substring (thing-at-point 'line) 9 -1))))))
908  (directory-files dir))))))
909 
910 (defun insert-directory-org-file-titles (&optional dir)
911  (interactive "D")
912  (let ((files-titles (extract-org-directory-titles-as-list dir)))
913  (dolist (ft files-titles)
914  (insert (concat "[[file:" (car ft)"][" (cdr ft) "]]\n")))))
915 
916 (defun insert-directory-org-files (&optional dir)
917  (interactive "D")
918  (let ((files (directory-files dir)))
919  (dolist (f files)
920  (insert (concat "[[file:" f "][" (file-name-base f) "]]\n")))))
921 
922 (defun include-directory-org-files (&optional dir)
923  (interactive "D")
924  (let ((files (directory-files dir)))
925  (dolist (f files)
926  (insert (concat "#+INCLUDE: " f "\n")))))
927 
928 (defun org-todo-at-date (date)
929  "create a todo entry for a given date."
930  (interactive (list (org-time-string-to-time (org-read-date))))
931  (cl-flet ((org-current-effective-time (&rest r) date)
932  (org-today (&rest r) (time-to-days date)))
933  (cond ((eq major-mode 'org-mode) (org-todo))
934  ((eq major-mode 'org-agenda-mode) (org-agenda-todo)))))
935 
936 (defun org-agenda-show-week-all (&optional arg ) (interactive "P") (org-agenda arg "n"))
937 
938 (defun org-ask-location ()
939  "prompt for a location."
940  (let* ((org-refile-targets '((nil :maxlevel . 9)))
941  (hd (condition-case nil
942  (car (org-refile-get-location))
943  (error (car org-refile-history)))))
944  (goto-char (point-min))
945  (outline-next-heading)
946  (if (re-search-forward
947  (format org-complex-heading-regexp-format (regexp-quote hd))
948  nil t)
949  (goto-char (line-beginning-position))
950  (goto-char (point-max))
951  (or (bolp) (insert "\n"))
952  (insert "* " hd "\n")))
953  (end-of-line))
954 
955 (defun org-capture-fileref-snippet (f type headers func-name)
956  (let* ((code-snippet
957  (buffer-substring-no-properties (mark) (- (point) 1)))
958  (file-name (buffer-file-name))
959  (file-base (file-name-nondirectory file-name))
960  (line-number (line-number-at-pos (region-beginning)))
961  (initial-txt (if (null func-name)
962  (format "From [[file:%s::%s][%s]]:"
963  file-name line-number file-base)
964  (format "From ~%s~ (in [[file:%s::%s][%s]]):"
965  func-name file-name line-number
966  file-base))))
967  (format "
968  %s
969  #+BEGIN_%s %s
970  %s
971  #+END_%s" initial-txt type headers code-snippet type)))
972 
973 (defun org-capture-clip-snippet (f)
974  "Given a file, F, this captures the currently selected text
975  within an Org EXAMPLE block and a backlink to the file."
976  (with-current-buffer (find-buffer-visiting f)
977  (org-capture-fileref-snippet f "EXAMPLE" "" nil)))
978 
979 (defun org-capture-code-snippet (f)
980  "Given a file, F, this captures the currently selected text
981  within an Org SRC block with a language based on the current mode
982  and a backlink to the function and the file."
983  (with-current-buffer (find-buffer-visiting f)
984  (let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode)))
985  (func-name (which-function)))
986  (org-capture-fileref-snippet f "SRC" org-src-mode func-name))))
987 
988 (defun region-to-clocked-task (start end)
989  "Copies the selected text to the currently clocked in org-mode task."
990  (interactive "r")
991  (org-capture-string (buffer-substring-no-properties start end) "3"))
992 
993 (setq org-global-properties
994  '(quote (("EFFORT_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00")
995  ("STYLE_ALL" . "habit"))))
996 
997 (defun org-mode-ask-effort ()
998  "Ask for an effort estimate when clocking in."
999  (unless (org-entry-get (point) "Effort")
1000  (let ((effort
1001  (completing-read
1002  "Effort: "
1003  (org-entry-get-multivalued-property (point) "Effort"))))
1004  (unless (equal effort "")
1005  (org-set-property "Effort" effort)))))
1006 
1007 (add-hook 'org-clock-in-prepare-hook
1008  'org-mode-ask-effort)
1009 
1010 ;;;###autoload
1011 (defun org-adjust-tags-column-reset-tags ()
1012  "In org-mode buffers it will reset tag position according to
1013 `org-tags-column'."
1014  (when (and
1015  (not (string= (buffer-name) "*Remember*"))
1016  (eql major-mode 'org-mode))
1017  (let ((b-m-p (buffer-modified-p)))
1018  (condition-case nil
1019  (save-excursion
1020  (goto-char (point-min))
1021  (command-execute 'outline-next-visible-heading)
1022  ;; disable (message) that org-set-tags generates
1023  (cl-flet ((message (&rest ignored) nil))
1024  (org-set-tags 1 t))
1025  (set-buffer-modified-p b-m-p))
1026  (error nil)))))
1027 
1028 ;; TODO 2024-08-05: infer logbook column-titles/props
1029 (defun column-display-value-transformer (column-title value)
1030  "Modifies the value to display in column view."
1031  (let ((title (upcase column-title)))
1032  (when (and (member title '("UPDATED" "NOTE")))
1033  (org-back-to-heading)
1034  (re-search-forward
1035  "Note taken on \\[\\(.*\\)\\] \\\\\\\\\\\n +\\(.*\\) *$"
1036  (org-entry-end-position) t))
1037  (if (equal column-title "UPDATED")
1038  (match-string-no-properties 1)
1039  (match-string-no-properties 2))))
1040 
1041 (setq org-columns-modify-value-for-display-function
1042  #'column-display-value-transformer)
1043 
1044 ;;;###autoload
1045 (defun org-align-all-tables ()
1046  "align all tables in current buffer"
1047  (interactive)
1048  (org-table-map-tables 'org-table-align 'quietly))
1049 
1050 (defun org-remove-redundant-tags ()
1051  "Remove redundant tags of headlines in current buffer.
1052 
1053 A tag is considered redundant if it is local to a headline and
1054 inherited by a parent headline."
1055  (interactive)
1056  (when (eq major-mode 'org-mode)
1057  (save-excursion
1058  (org-map-entries
1059  (lambda ()
1060  (let ((alltags (split-string (or (org-entry-get (point) "ALLTAGS") "") ":"))
1061  local inherited tag)
1062  (dolist (tag alltags)
1063  (if (get-text-property 0 'inherited tag)
1064  (push tag inherited) (push tag local)))
1065  (dolist (tag local)
1066  (if (member tag inherited) (org-toggle-tag tag 'off)))))
1067  t nil))))
1068 
1069 ;;;; Agenda
1070 (cl-pushnew '("w" "Work in progress tasks" ((todo "WIP") (agenda))) org-agenda-custom-commands)
1071 
1072 (defvar org-agenda-overriding-header)
1073 (defvar org-agenda-sorting-strategy)
1074 (defvar org-agenda-restrict)
1075 (defvar org-agenda-restrict-begin)
1076 (defvar org-agenda-restrict-end)
1077 
1078 ;;;###autoload
1079 (defun org-agenda-reschedule-to-today ()
1080  (interactive)
1081  (cl-flet ((org-read-date (&rest rest) (current-time)))
1082  (call-interactively 'org-agenda-schedule)))
1083 
1084 ;; Patch org-mode to use vertical splitting
1085 (defadvice org-prepare-agenda (after org-fix-split)
1086  (toggle-window-split))
1087 (ad-activate 'org-prepare-agenda)
1088 
1089 (add-hook 'org-agenda-mode-hook (lambda () (hl-line-mode 1)))
1090 
1091 (defun org-agenda-log-mode-colorize-block ()
1092  "Set different line spacing based on clock time duration."
1093  (save-excursion
1094  (let* ((colors (cl-case (alist-get 'background-mode (frame-parameters))
1095  (light
1096  (list "#F6B1C3" "#FFFF9D" "#BEEB9F" "#ADD5F7"))
1097  (dark
1098  (list "#aa557f" "DarkGreen" "DarkSlateGray" "DarkSlateBlue"))))
1099  pos
1100  duration)
1101  (nconc colors colors)
1102  (goto-char (point-min))
1103  (while (setq pos (next-single-property-change (point) 'duration))
1104  (goto-char pos)
1105  (when (and (not (equal pos (pos-bol)))
1106  (setq duration (org-get-at-bol 'duration)))
1107  ;; larger duration bar height
1108  (let ((line-height (if (< duration 15) 1.0 (+ 0.5 (/ duration 30))))
1109  (ov (make-overlay (pos-bol) (1+ (pos-eol)))))
1110  (overlay-put ov 'face `(:background ,(car colors) :foreground "black"))
1111  (setq colors (cdr colors))
1112  (overlay-put ov 'line-height line-height)
1113  (overlay-put ov 'line-spacing (1- line-height))))))))
1114 
1115 (add-hook 'org-agenda-finalize-hook #'org-agenda-log-mode-colorize-block)
1116 
1117 ;;;###autoload
1118 (defun org-agenda-current-subtree-or-region (only-todos)
1119  "Display an agenda view for the current subtree or region.
1120  With prefix, display only TODO-keyword items."
1121  (interactive "P")
1122  (let ((starting-point (point))
1123  header)
1124  (with-current-buffer (or (buffer-base-buffer (current-buffer))
1125  (current-buffer))
1126  (if (use-region-p)
1127  (progn
1128  (setq header "Region")
1129  (put 'org-agenda-files 'org-restrict (list (buffer-file-name (current-buffer))))
1130  (setq org-agenda-restrict (current-buffer))
1131  (move-marker org-agenda-restrict-begin (region-beginning))
1132  (move-marker org-agenda-restrict-end
1133  (save-excursion
1134  ;; If point is at beginning of line, include
1135  ;; heading on that line by moving forward 1.
1136  (goto-char (1+ (region-end)))
1137  (org-end-of-subtree))))
1138  ;; No region; restrict to subtree.
1139  (save-excursion
1140  (save-restriction
1141  ;; In case the command was called from an indirect buffer, set point
1142  ;; in the base buffer to the same position while setting restriction.
1143  (widen)
1144  (goto-char starting-point)
1145  (setq header "Subtree")
1146  (org-agenda-set-restriction-lock))))
1147  ;; NOTE: Unlike other agenda commands, binding `org-agenda-sorting-strategy'
1148  ;; around `org-search-view' seems to have no effect.
1149  (let ((org-agenda-sorting-strategy '(priority-down timestamp-up))
1150  (org-agenda-overriding-header header))
1151  (org-search-view (if only-todos t nil) "*"))
1152  (org-agenda-remove-restriction-lock t)
1153  (message nil))))
1154 
1155 (defun org-export-translate-to-lang (term-translations &optional lang)
1156  "Adds desired translations to `org-export-dictionary'.
1157  TERM-TRANSLATIONS is alist consisted of term you want to translate
1158  and its corresponding translation, first as :default then as :html and
1159  :utf-8. LANG is language you want to translate to."
1160  (dolist (term-translation term-translations)
1161  (let* ((term (car term-translation))
1162  (translation-default (nth 1 term-translation))
1163  (translation-html (nth 2 term-translation))
1164  (translation-utf-8 (nth 3 term-translation))
1165  (term-list (assoc term org-export-dictionary))
1166  (term-langs (cdr term-list)))
1167  (setcdr term-list (append term-langs
1168  (list
1169  (list lang
1170  :default translation-default
1171  :html translation-html
1172  :utf-8 translation-utf-8)))))))
1173 
1174 ;;; Dictionary
1175 (setq dictionary-server "compiler.company"
1176  switch-to-buffer-obey-display-actions t)
1177 
1178 ;;; Ispell
1179 ;; requires aspell and a hunspell dictionary (hunspell-en_us)
1180 (setq-default ispell-program-name "aspell")
1181 (add-hook 'mail-send-hook #'ispell-message)
1182 
1183 ;;; Skel
1184 (add-to-load-path user-emacs-lib-directory)
1185 (require 'sk)
1186 (require 'skt)
1187 
1188 (provide 'default)
1189 ;; default.el ends here