Compare commits
373 Commits
bankruptcy
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
2673a4f1d3 | ||
|
58e163e03b | ||
|
42947150ad | ||
|
4742605f98 | ||
|
9b2324c6e0 | ||
|
dbb6181a5d | ||
|
21b5d80814 | ||
|
af3eb37c8e | ||
|
2c8a3306db | ||
|
b6015fa0c2 | ||
|
69a0c8c199 | ||
|
80abceb212 | ||
|
41b2867466 | ||
|
3da59fb2f6 | ||
|
dd3afe747e | ||
|
02addc0aff | ||
|
b3c2d9ded9 | ||
|
97bbe5e322 | ||
|
134409aa67 | ||
|
1375113b0e | ||
|
1492d153f0 | ||
|
a6085b58a2 | ||
|
f0a4617a52 | ||
|
a0b156b7e1 | ||
|
ea979bdc41 | ||
|
40c8fe07fa | ||
|
4c4dd0e782 | ||
|
3cb2f98be4 | ||
|
222a20c7c1 | ||
|
eaa1c58e1b | ||
|
1f8021f0ee | ||
|
c66f44e360 | ||
|
2d69ea08d3 | ||
|
6e7d7fe47d | ||
|
fcd47a9c3a | ||
|
1df15735b2 | ||
|
246990fd15 | ||
|
e3356d3c39 | ||
|
a8e71fa8c7 | ||
|
144e5244d2 | ||
|
bcf56eff21 | ||
|
46487b4a33 | ||
|
be0546d73c | ||
|
f6512b78a2 | ||
|
f0febf6814 | ||
|
517b999407 | ||
|
f7099ebac6 | ||
|
5d4db143f2 | ||
|
4e562b202c | ||
|
8360e66d11 | ||
|
a6341764f3 | ||
|
9a8043d49b | ||
|
23836a13f1 | ||
|
13fed66dec | ||
|
2cc59cf768 | ||
|
02b8882f54 | ||
|
0730f1e1ac | ||
|
5c02bbc592 | ||
|
818cfc0380 | ||
|
c822c9bfd1 | ||
|
c2834c3511 | ||
|
7f7ede201b | ||
|
8565654433 | ||
|
9c5aa35b2a | ||
|
bb7f256f59 | ||
|
57f0dd43c4 | ||
|
0b573c7eba | ||
|
4b1eaab205 | ||
|
8edc3aa615 | ||
|
340c8583cc | ||
|
3f925fc0e3 | ||
|
3219bf88cc | ||
|
9b17702d3f | ||
|
47371e7484 | ||
|
0aab1430c8 | ||
|
6d62265dd0 | ||
|
f2b652dabc | ||
|
57b0d19290 | ||
|
30a7f9651b | ||
|
86c2a140a5 | ||
|
d4c3d79770 | ||
|
56b2c9fb54 | ||
|
d31baf887e | ||
|
28d11fd0e8 | ||
|
a7ecf23377 | ||
|
1f7e7ebf24 | ||
|
088f933bb1 | ||
|
53409a7eb2 | ||
|
5782c55e52 | ||
|
791f486e1a | ||
|
abce780f1b | ||
|
c4cbde3631 | ||
|
f8ce8b16f3 | ||
|
1e11dad4a1 | ||
|
7d71c6e5bc | ||
|
d26bfd92e1 | ||
|
3c5c175b84 | ||
|
e3e399ac52 | ||
|
bb756ce658 | ||
|
3970a88dd9 | ||
|
ca1823d2b2 | ||
|
b868fa30e8 | ||
|
f93e8df8cf | ||
|
a27eb917b8 | ||
|
4d83762191 | ||
|
35c88720df | ||
|
19cb761465 | ||
|
c6ccd9151d | ||
|
b391b15541 | ||
|
e0e49f18fd | ||
|
298ebabb88 | ||
|
7d720a4793 | ||
|
e64c1a2854 | ||
|
869f2192bd | ||
|
c0a0df1e3c | ||
|
6068ebf457 | ||
|
a6db4c2295 | ||
|
6f1f0de1c1 | ||
|
9ed685f740 | ||
|
9b23b33921 | ||
|
c0fa442767 | ||
|
f479cd9b30 | ||
|
99ddcb1718 | ||
|
94c3685e85 | ||
|
755bd8b646 | ||
|
191a223a1d | ||
|
6db8a02175 | ||
|
1d64079a52 | ||
|
2e488c508f | ||
|
2876e85cf6 | ||
|
8f8121e3a2 | ||
|
abf24e71c7 | ||
|
8383d8cb8b | ||
|
122ee05071 | ||
|
2e46fd9241 | ||
|
548e9b8acc | ||
|
a44a825f2c | ||
|
9742b1a3c8 | ||
|
f75a2fc9e3 | ||
|
173dd60dca | ||
|
29c287a8c3 | ||
|
31f595fafa | ||
|
b462cc8785 | ||
|
a11c0cdeb0 | ||
|
979fa11e49 | ||
|
f6512fe1bd | ||
|
8eda323c31 | ||
|
b867ee1889 | ||
|
f65f4387b8 | ||
|
b0346f6283 | ||
|
b454114819 | ||
|
1ecd585824 | ||
|
3d7fd0f224 | ||
|
dcbc12ed9f | ||
|
10515f443e | ||
|
45d179a4c3 | ||
|
49bd36e850 | ||
|
4326b4327f | ||
|
147f94f5d2 | ||
|
277dfcc6cd | ||
|
5b35d5c91b | ||
|
b05b4c8107 | ||
|
0194e2ea2d | ||
|
977ff8c2fe | ||
|
9019eb07f2 | ||
|
0064d11659 | ||
|
7f8a95ea03 | ||
|
c031326367 | ||
|
a2dac68a29 | ||
|
dbe26df4d4 | ||
|
51f004efdf | ||
|
55857d7441 | ||
|
a9d596fe80 | ||
|
434e105886 | ||
|
f0b220a7f4 | ||
|
fae8e9168f | ||
|
f5fcd05944 | ||
|
5666ae8631 | ||
|
2254c5c6c6 | ||
|
88ada4f1b9 | ||
|
914e5ff25a | ||
|
4219f9702b | ||
|
82869c1f4f | ||
|
017f1c065c | ||
|
90b28afdf1 | ||
|
ceff2ca8f1 | ||
|
b835fb6f90 | ||
|
83de113f4e | ||
|
21741b85e5 | ||
|
5738c05f5b | ||
|
fe8985ae47 | ||
|
265119b750 | ||
|
7167cd4962 | ||
|
a733d45b24 | ||
|
3b34e8cf35 | ||
|
62e37fdee6 | ||
|
77626da370 | ||
|
5dad1aee40 | ||
|
2a16bbd8db | ||
|
269d8f687e | ||
|
6d2d31a40e | ||
|
e0187eea1e | ||
|
635eee323d | ||
|
801ee6ec24 | ||
|
e8bcf53e55 | ||
|
5efe60c74b | ||
|
aa7d1157b7 | ||
|
ea5eea7046 | ||
|
50d7ff8077 | ||
|
88e218faf2 | ||
|
c921132330 | ||
|
3b9b91686f | ||
|
e76a106571 | ||
|
b0abd8aca6 | ||
|
f8e5e64308 | ||
|
a43c72e5c8 | ||
|
c3ff6dc08c | ||
|
a1be3555bc | ||
|
57c91fea2f | ||
|
ed8cce06cf | ||
|
aa99e1b33e | ||
|
9cf098bfb7 | ||
|
f4e794e9b0 | ||
|
16604de818 | ||
|
4178ff9c18 | ||
|
5455025f1b | ||
|
5eea6b4919 | ||
|
89a12141dc | ||
|
3c89c1ee06 | ||
|
27616fa08a | ||
|
9b4e6f10d3 | ||
|
2525937bc4 | ||
|
1293e0d071 | ||
|
981f581188 | ||
|
cd7f941ff8 | ||
|
3a69dcdd37 | ||
|
67cff9a99d | ||
|
7ffde2d9bb | ||
|
1b881512d1 | ||
|
fe79856349 | ||
|
b19c1f98fa | ||
|
5bb0040b8a | ||
|
f37cb20764 | ||
|
195618bcf3 | ||
|
90e13f3aac | ||
|
e57943cfa2 | ||
|
8f3631016d | ||
|
843a784d56 | ||
|
e8589cec6c | ||
|
6807db4e4a | ||
|
8e122e335b | ||
|
304e85be3a | ||
|
86d79d55a0 | ||
|
cd60a835bf | ||
|
301cd5df28 | ||
|
726d90d708 | ||
|
702502bbdd | ||
|
e559dd7880 | ||
|
aba16d7e16 | ||
|
20ff854f41 | ||
|
26b816767b | ||
|
e0b879deae | ||
|
1e5c351253 | ||
|
10622662b8 | ||
|
55c716cbe8 | ||
|
025c1af7f3 | ||
|
97687e8d01 | ||
|
da29487a9b | ||
|
181f3530f3 | ||
|
3b6dcdc3bd | ||
|
995f998872 | ||
|
3586cecd8b | ||
|
d799b1cded | ||
|
6b355568b0 | ||
|
3c30daf8a5 | ||
|
44ecd3b272 | ||
|
6de50bb864 | ||
|
dbc6934ae6 | ||
|
2d5cee697e | ||
|
10a2e1a0f4 | ||
|
dbe223f794 | ||
|
f7622fc591 | ||
|
48316556b1 | ||
|
d00598afe6 | ||
|
00f639319c | ||
|
81fb787be4 | ||
|
3ec991d541 | ||
|
2b99cc25d3 | ||
|
fbc03a1cdf | ||
|
adf815b61b | ||
|
44741fed54 | ||
|
40a6acaf56 | ||
|
6ae55c4a36 | ||
|
ef5719915d | ||
|
04e917c834 | ||
|
2341b8279d | ||
|
043a186158 | ||
|
81c6f17854 | ||
|
6852a7307a | ||
|
efc08126f7 | ||
|
c3601eaf2f | ||
|
4236a39dfb | ||
|
298fd65a4c | ||
|
21d53603b1 | ||
|
309a34cea5 | ||
|
1394b10658 | ||
|
ee1720b8ad | ||
|
5fe7d70d08 | ||
|
0fb567e1b3 | ||
|
6d2391e0e9 | ||
|
c42eb9d62d | ||
|
596a21b6a0 | ||
|
2243b1e4ef | ||
|
0a0c898a28 | ||
|
dba542cc18 | ||
|
84c320cda4 | ||
|
35fbd99419 | ||
|
4362009bad | ||
|
7f14098962 | ||
|
6f20710673 | ||
|
0495456fb9 | ||
|
4b2e57e396 | ||
|
6ffc1e7e42 | ||
|
b495c3b20c | ||
|
e037d2fb71 | ||
|
caf8d7aa54 | ||
|
125a82251c | ||
|
2a119625e6 | ||
|
e4f7ed9609 | ||
|
1c6d042d2f | ||
|
0e43d013ea | ||
|
e91bb5a1be | ||
|
4d1605ce45 | ||
|
dcf3a3aa02 | ||
|
3379638199 | ||
|
2918cb39a2 | ||
|
0adf0814ee | ||
|
eb40045abd | ||
|
b7295426c9 | ||
|
9360a54e62 | ||
|
04a90b906c | ||
|
1eb9b6cb39 | ||
|
f0f031fafe | ||
|
f0294f7fb4 | ||
|
9b9d026b61 | ||
|
1aa0df7631 | ||
|
6c2b242a8a | ||
|
00da04a0fd | ||
|
e4ce00f4d5 | ||
|
13cbd6644a | ||
|
be15049058 | ||
|
4d2a9603ad | ||
|
75e03850c0 | ||
|
4dee486f1f | ||
|
84139db9a8 | ||
|
81152ca242 | ||
|
5630465877 | ||
|
58d096ff3c | ||
|
de01982128 | ||
|
5ccf1ad613 | ||
|
724c0a6fce | ||
|
9e46efac61 | ||
|
084618a930 | ||
|
2c86771b1d | ||
|
1b9d1c5844 | ||
|
3962ebb9bc | ||
|
9ba030fff8 | ||
|
461e98df20 | ||
|
f66b316f8a | ||
|
b2980816c5 | ||
|
a3fc41f61b | ||
|
a59178fc7b | ||
|
710dfe7cd5 |
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -11,9 +11,14 @@ feeds.txt
|
|||
gnus/
|
||||
old/
|
||||
pkg/
|
||||
private.el
|
||||
racket-mode/
|
||||
server/
|
||||
straight/
|
||||
transient/
|
||||
var/
|
||||
var/
|
||||
eshell/*
|
||||
!eshell/aliases
|
||||
url/
|
||||
|
||||
# put random stuff in here
|
||||
scratch.el
|
||||
|
|
139
early-init.el
139
early-init.el
|
@ -21,29 +21,50 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(define-advice load (:before (feature &rest _))
|
||||
"Message the user when loading a library."
|
||||
(with-temp-message (format "Now loading: '%s'" feature)))
|
||||
|
||||
;;; Speed up init
|
||||
|
||||
(setq gc-cons-threshold most-positive-fixnum)
|
||||
;; Restore things after init
|
||||
(defvar +emacs--startup-restore-alist nil
|
||||
"Variables and values to restore after init.")
|
||||
|
||||
(add-hook 'emacs-startup-hook
|
||||
(defun emacs-startup@restore-values ()
|
||||
"Restore values set during early-init for speed."
|
||||
(setq gc-cons-threshold 134217728 ; 128mb
|
||||
;; I don't do the common `file-name-handler-alist' thing here
|
||||
;; because of a weirdness where my Emacs doesn't know how to
|
||||
;; load bookmark.el.gz when initializing.
|
||||
)))
|
||||
"Restore values set during init.
|
||||
This applies values in `+emacs--startup-restore-alist'."
|
||||
(dolist (a +emacs--startup-restore-alist)
|
||||
(set (car a) (cdr a)))))
|
||||
|
||||
(defun +set-during-startup (variable value &optional restore)
|
||||
"Set VARIABLE to VALUE during startup, but restore to RESTORE.
|
||||
If RESTORE is nil or not passed, save the original value and
|
||||
restore that."
|
||||
(unless after-init-time
|
||||
(setf (alist-get variable +emacs--startup-restore-alist)
|
||||
(or restore (symbol-value variable)))
|
||||
(set-default variable value)))
|
||||
|
||||
;; Garbage collection
|
||||
(+set-during-startup 'gc-cons-threshold most-positive-fixnum)
|
||||
|
||||
(add-hook 'minibuffer-setup-hook (defun garbage-collect@minibuffer-enter ()
|
||||
(setq gc-cons-threshold most-positive-fixnum)))
|
||||
(add-hook 'minibuffer-exit-hook (defun garbage-collect@minibuffer-exit ()
|
||||
(setq gc-cons-threshold 800000)))
|
||||
|
||||
;; Don't prematurely re-display
|
||||
(unless debug-on-error
|
||||
(+set-during-startup 'inhibit-redisplay t)
|
||||
(+set-during-startup 'inhibit-message t))
|
||||
|
||||
;; Debug during init
|
||||
(unless (eq debug-on-error 'startup)
|
||||
(+set-during-startup 'debug-on-error 'init))
|
||||
|
||||
;;; Set up extra load paths and functionality
|
||||
|
||||
(push (locate-user-emacs-file "lisp") load-path)
|
||||
(add-to-list 'load-path (locate-user-emacs-file "lisp/compat") :append)
|
||||
|
||||
(require 'acdw)
|
||||
(require 'compat)
|
||||
|
||||
(+define-dir .etc (locate-user-emacs-file ".etc")
|
||||
"Directory for all of Emacs's various files.
|
||||
|
@ -52,13 +73,6 @@ See `no-littering' for examples.")
|
|||
(+define-dir sync/ (expand-file-name "~/Sync")
|
||||
"My Syncthing directory.")
|
||||
|
||||
;; Load system-specific changes.
|
||||
(progn (require 'system)
|
||||
(setq system-default-font "DejaVu Sans Mono"
|
||||
system-variable-pitch-font "DejaVu Sans")
|
||||
(setq system-load-directory (sync/ "emacs/systems/" t))
|
||||
(system-settings-load nil :nowarn))
|
||||
|
||||
;;; Default frame settings
|
||||
|
||||
(setq default-frame-alist '((tool-bar-lines . 0)
|
||||
|
@ -70,38 +84,22 @@ See `no-littering' for examples.")
|
|||
window-resize-pixelwise t
|
||||
inhibit-x-resources t
|
||||
indicate-empty-lines nil
|
||||
indicate-buffer-boundaries '((top . right)
|
||||
(bottom . right)))
|
||||
indicate-buffer-boundaries nil
|
||||
;; '((top . right)
|
||||
;; (bottom . right))
|
||||
)
|
||||
|
||||
;;; Fonts
|
||||
;;; No littering!
|
||||
;; We install `no-littering' package below, but we can set the variables now.
|
||||
|
||||
;; Set default faces
|
||||
(setq no-littering-etc-directory .etc
|
||||
no-littering-var-directory .etc
|
||||
straight-base-dir .etc)
|
||||
|
||||
(let ((font-name system-default-font)
|
||||
(font-size system-default-height)
|
||||
(variable-font-name system-variable-pitch-font)
|
||||
(variable-font-size system-variable-pitch-height))
|
||||
(set-face-attribute 'default nil :family system-default-font
|
||||
:height font-size :weight 'book)
|
||||
(set-face-attribute 'italic nil :family font-name
|
||||
:height font-size :slant 'italic)
|
||||
(set-face-attribute 'variable-pitch nil :family variable-font-name
|
||||
:height variable-font-size))
|
||||
;; https://github.com/emacscollective/no-littering/wiki/Setting-gccemacs'-eln-cache
|
||||
|
||||
;; Emoji fonts
|
||||
|
||||
(let ((ffl (font-family-list)))
|
||||
(dolist (font '("Noto Color Emoji"
|
||||
"Noto Emoji"
|
||||
"Segoe UI Emoji"
|
||||
"Apple Color Emoji"
|
||||
"FreeSans"
|
||||
"FreeMono"
|
||||
"FreeSerif"
|
||||
"Unifont"
|
||||
"Symbola"))
|
||||
(when (member font ffl)
|
||||
(set-fontset-font t 'symbol (font-spec :family font) nil :append))))
|
||||
(when (boundp 'comp-eln-load-path)
|
||||
(setcar comp-eln-load-path (expand-file-name (.etc "eln-cache" t))))
|
||||
|
||||
;;; Packages
|
||||
|
||||
|
@ -112,28 +110,25 @@ See `no-littering' for examples.")
|
|||
straight-check-for-modifications '(check-on-save
|
||||
find-when-checking))
|
||||
|
||||
(setq no-littering-etc-directory .etc
|
||||
no-littering-var-directory .etc
|
||||
straight-base-dir .etc)
|
||||
|
||||
;; Bootstrap straight.el
|
||||
;; https://github.com/raxod502/straight.el
|
||||
|
||||
(defvar bootstrap-version)
|
||||
(let ((bootstrap-file
|
||||
(expand-file-name
|
||||
"straight/repos/straight.el/bootstrap.el"
|
||||
straight-base-dir))
|
||||
(bootstrap-version 5))
|
||||
(unless (file-exists-p bootstrap-file)
|
||||
(with-current-buffer
|
||||
(url-retrieve-synchronously
|
||||
(concat "https://raw.githubusercontent.com/"
|
||||
"raxod502/straight.el/develop/install.el")
|
||||
'silent 'inhibit-cookies)
|
||||
(goto-char (point-max))
|
||||
(eval-print-last-sexp)))
|
||||
(load bootstrap-file nil 'nomessage))
|
||||
(+with-message "Bootstrapping straight"
|
||||
(defvar bootstrap-version)
|
||||
(let ((bootstrap-file
|
||||
(expand-file-name
|
||||
"straight/repos/straight.el/bootstrap.el"
|
||||
straight-base-dir))
|
||||
(bootstrap-version 5))
|
||||
(unless (file-exists-p bootstrap-file)
|
||||
(with-current-buffer
|
||||
(url-retrieve-synchronously
|
||||
(concat "https://raw.githubusercontent.com/"
|
||||
"raxod502/straight.el/develop/install.el")
|
||||
'silent 'inhibit-cookies)
|
||||
(goto-char (point-max))
|
||||
(eval-print-last-sexp)))
|
||||
(load bootstrap-file nil 'nomessage)))
|
||||
|
||||
;; Early-loaded packages -- those that, for some reason or another,
|
||||
;; need to be ensured to be loaded first.
|
||||
|
@ -142,14 +137,20 @@ See `no-littering' for examples.")
|
|||
|
||||
(dolist (pkg '(el-patch
|
||||
no-littering
|
||||
setup))
|
||||
setup
|
||||
straight ; already installed, but what the hell
|
||||
))
|
||||
(straight-use-package pkg)
|
||||
(require pkg)
|
||||
(require (intern (format "+%s" pkg)) nil :noerror))
|
||||
|
||||
;; Setup `setup'
|
||||
|
||||
(add-to-list 'setup-modifier-list 'setup-wrap-to-demote-errors)
|
||||
(add-to-list 'setup-modifier-list '+setup-wrap-to-demote-errors)
|
||||
(unless (memq debug-on-error '(nil init))
|
||||
(define-advice setup (:around (fn head &rest args) +setup-report)
|
||||
(+with-progress ((format "[Setup] %S..." head))
|
||||
(apply fn head args))))
|
||||
|
||||
;;; Appendix
|
||||
|
||||
|
|
4
eshell/aliases
Normal file
4
eshell/aliases
Normal file
|
@ -0,0 +1,4 @@
|
|||
alias sudo eshell/sudo $*
|
||||
alias ff find-file $1
|
||||
alias e find-file $1
|
||||
alias edit find-file $1
|
|
@ -13,5 +13,72 @@ arg reversed."
|
|||
(interactive "P" Info-mode)
|
||||
(Info-copy-current-node-name (unless arg 0)))
|
||||
|
||||
(defun +Info-modeline-breadcrumbs ()
|
||||
(let ((nodes (Info-toc-nodes Info-current-file))
|
||||
(node Info-current-node)
|
||||
(crumbs ())
|
||||
(depth Info-breadcrumbs-depth-internal)
|
||||
(text ""))
|
||||
;; Get ancestors from the cached parent-children node info
|
||||
(while (and (not (equal "Top" node)) (> depth 0))
|
||||
(setq node (nth 1 (assoc node nodes)))
|
||||
(when node (push node crumbs))
|
||||
(setq depth (1- depth)))
|
||||
;; Add bottom node.
|
||||
(setq crumbs (nconc crumbs (list Info-current-node)))
|
||||
(when crumbs
|
||||
;; Add top node (and continuation if needed).
|
||||
(setq crumbs (cons "Top" (if (member (pop crumbs) '(nil "Top"))
|
||||
crumbs
|
||||
(cons nil crumbs))))
|
||||
(dolist (node crumbs)
|
||||
(let ((crumbs-map (make-sparse-keymap))
|
||||
(menu-map (make-sparse-keymap "Breadcrumbs in Mode Line")))
|
||||
(define-key crumbs-map [mode-line mouse-3] menu-map)
|
||||
(when node
|
||||
(define-key menu-map [Info-prev]
|
||||
`(menu-item "Previous Node" Info-prev
|
||||
:visible ,(Info-check-pointer "prev[ious]*") :help "Go to the previous node"))
|
||||
(define-key menu-map [Info-next]
|
||||
`(menu-item "Next Node" Info-next
|
||||
:visible ,(Info-check-pointer "next") :help "Go to the next node"))
|
||||
(define-key menu-map [separator] '("--"))
|
||||
(define-key menu-map [Info-breadcrumbs-in-mode-line-mode]
|
||||
`(menu-item "Toggle Breadcrumbs" Info-breadcrumbs-in-mode-line-mode
|
||||
:help "Toggle displaying breadcrumbs in the Info mode-line"
|
||||
:button (:toggle . Info-breadcrumbs-in-mode-line-mode)))
|
||||
(define-key menu-map [Info-set-breadcrumbs-depth]
|
||||
`(menu-item "Set Breadcrumbs Depth" Info-set-breadcrumbs-depth
|
||||
:help "Set depth of breadcrumbs to show in the mode-line"))
|
||||
(setq node (if (equal node Info-current-node)
|
||||
(propertize
|
||||
(replace-regexp-in-string "%" "%%" Info-current-node)
|
||||
'face 'mode-line-buffer-id
|
||||
'help-echo "mouse-1: Scroll back, mouse-2: Scroll forward, mouse-3: Menu"
|
||||
'mouse-face 'mode-line-highlight
|
||||
'local-map
|
||||
(progn
|
||||
(define-key crumbs-map [mode-line mouse-1] 'Info-mouse-scroll-down)
|
||||
(define-key crumbs-map [mode-line mouse-2] 'Info-mouse-scroll-up)
|
||||
crumbs-map))
|
||||
(propertize
|
||||
node
|
||||
'local-map (progn (define-key crumbs-map [mode-line mouse-1]
|
||||
`(lambda () (interactive) (Info-goto-node ,node)))
|
||||
(define-key crumbs-map [mode-line mouse-2]
|
||||
`(lambda () (interactive) (Info-goto-node ,node)))
|
||||
crumbs-map)
|
||||
'mouse-face 'mode-line-highlight
|
||||
'help-echo "mouse-1, mouse-2: Go to this node; mouse-3: Menu")))))
|
||||
(let ((nodetext (if (not (equal node "Top"))
|
||||
node
|
||||
(concat (format "(%s)" (if (stringp Info-current-file)
|
||||
(file-name-nondirectory Info-current-file)
|
||||
;; Some legacy code can still use a symbol.
|
||||
Info-current-file))
|
||||
node))))
|
||||
(setq text (concat text (if (equal node "Top") "" " > ") (if node nodetext "...")))))
|
||||
text)))
|
||||
|
||||
(provide '+Info)
|
||||
;;; +Info.el ends here
|
||||
|
|
|
@ -10,15 +10,21 @@
|
|||
;; This is stolen from ace-window.el but with the mode-line stuff ripped out.
|
||||
:global t
|
||||
(if +ace-window-display-mode
|
||||
(progn
|
||||
(progn ; Enable
|
||||
(aw-update)
|
||||
(force-mode-line-update t)
|
||||
(add-hook 'window-configuration-change-hook 'aw-update)
|
||||
(add-hook 'after-make-frame-functions 'aw--after-make-frame t)
|
||||
(advice-add 'aw--lead-overlay :override 'ignore))
|
||||
(remove-hook 'window-configuration-change-hook 'aw-update)
|
||||
(remove-hook 'after-make-frame-functions 'aw--after-make-frame)
|
||||
(advice-remove 'aw--lead-overlay 'ignore)))
|
||||
(progn ; Disable
|
||||
(remove-hook 'window-configuration-change-hook 'aw-update)
|
||||
(remove-hook 'after-make-frame-functions 'aw--after-make-frame)
|
||||
(advice-remove 'aw--lead-overlay 'ignore))))
|
||||
|
||||
;; (defun +ace-window--mode-line-hint (path leaf)
|
||||
;; (let ((wnd (cdr leaf)))
|
||||
;; (with-selected-window wnd
|
||||
;; ())))
|
||||
|
||||
;;;###autoload
|
||||
(defun +ace-window-or-switch-buffer (arg)
|
||||
|
@ -30,8 +36,5 @@ Switch to most recent buffer otherwise."
|
|||
(switch-to-buffer nil)
|
||||
(ace-window arg)))
|
||||
|
||||
(defun +ace-window@disable-overlay (_fn &rest _args)
|
||||
"ADVICE for FN `aw--lead-overlay' (and ARGS) to not show overlays.")
|
||||
|
||||
(provide '+ace-window)
|
||||
;;; +ace-window.el ends here
|
||||
|
|
17
lisp/+apheleia.el
Normal file
17
lisp/+apheleia.el
Normal file
|
@ -0,0 +1,17 @@
|
|||
;;; +apheleia.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
;; https://github.com/raxod502/apheleia/pull/63#issue-1077529623
|
||||
(cl-defun +apheleia-indent-region (&key buffer scratch formatter callback &allow-other-keys)
|
||||
(with-current-buffer scratch
|
||||
(setq-local indent-line-function
|
||||
(buffer-local-value 'indent-line-function buffer))
|
||||
(indent-region (point-min)
|
||||
(point-max))
|
||||
(funcall callback)))
|
||||
|
||||
(provide '+apheleia)
|
||||
;;; +apheleia.el ends here
|
76
lisp/+avy.el
76
lisp/+avy.el
|
@ -17,5 +17,81 @@
|
|||
(cdr (ring-ref avy-ring 0))))
|
||||
t)
|
||||
|
||||
|
||||
;;; Remove `buffer-face-mode' when avy is active.
|
||||
|
||||
(defcustom +avy-buffer-face-functions '(avy-goto-char
|
||||
avy-goto-char-in-line
|
||||
avy-goto-char-2
|
||||
avy-goto-char-2-above
|
||||
avy-goto-char-2-below
|
||||
avy-goto-word-0
|
||||
avy-goto-whitespace-end
|
||||
avy-goto-word-0-above
|
||||
avy-goto-word-0-below
|
||||
avy-goto-whitespace-end-above
|
||||
avy-goto-whitespace-end-below
|
||||
avy-goto-word-1
|
||||
avy-goto-word-1-above
|
||||
avy-goto-word-1-below
|
||||
avy-goto-symbol-1
|
||||
avy-goto-symbol-1-above
|
||||
avy-goto-symbol-1-below
|
||||
avy-goto-subword-0
|
||||
avy-goto-subword-1
|
||||
avy-goto-word-or-subword-1
|
||||
avy-goto-line
|
||||
avy-goto-line-above
|
||||
avy-goto-line-below
|
||||
avy-goto-end-of-line
|
||||
avy-goto-char-timer)
|
||||
"Functions to disable `buffer-face-mode' during.")
|
||||
|
||||
(defvar-local +avy-buffer-face-mode-face nil
|
||||
"The state of `buffer-face-mode' before calling `avy-with'.")
|
||||
|
||||
;;; XXX: Doesn't switch back if avy errors out or quits
|
||||
(defun +avy@un-buffer-face (win)
|
||||
"BEFORE advice on `avy-with' to disable `buffer-face-mode'."
|
||||
(with-current-buffer (window-buffer win)
|
||||
(when buffer-face-mode
|
||||
(setq +avy-buffer-face-mode-face buffer-face-mode-face)
|
||||
(buffer-face-mode -1))))
|
||||
|
||||
(defun +avy@re-buffer-face (win)
|
||||
"AFTER advice on `avy-with' to re-enable `buffer-face-mode'."
|
||||
(with-current-buffer (window-buffer win)
|
||||
(when +avy-buffer-face-mode-face
|
||||
(setq buffer-face-mode-face +avy-buffer-face-mode-face)
|
||||
(buffer-face-mode +1)))
|
||||
(let ((bounds (bounds-of-thing-at-point 'symbol)))
|
||||
(when (and (car bounds)
|
||||
(cdr bounds))
|
||||
(pulse-momentary-highlight-region (car bounds) (cdr bounds)))))
|
||||
|
||||
(defun +avy@buffer-face (fn &rest r)
|
||||
"AROUND advice for avy to dis/enable `buffer-face-mode'."
|
||||
(if avy-all-windows
|
||||
(walk-windows #'+avy@un-buffer-face nil (eq avy-all-windows 'all-frames)))
|
||||
(condition-case e
|
||||
(apply fn r)
|
||||
((quit error) (message "Avy: %S" e) nil)
|
||||
(:sucess e))
|
||||
(if avy-all-windows
|
||||
(walk-windows #'+avy@re-buffer-face nil (eq avy-all-windows 'all-frames))))
|
||||
|
||||
(define-minor-mode +avy-buffer-face-mode
|
||||
"Turn off `buffer-face-mode' before doing Avy selections.
|
||||
Restore the mode after the selection."
|
||||
:lighter ""
|
||||
:global t
|
||||
(setq +avy-buffer-face-mode-face nil)
|
||||
(cond
|
||||
(+avy-buffer-face-mode
|
||||
(dolist (fn +avy-buffer-face-functions)
|
||||
(advice-add fn :around #'+avy@buffer-face)))
|
||||
(t (dolist (fn +avy-buffer-face-functions)
|
||||
(advice-remove fn #'+avy@buffer-face)))))
|
||||
|
||||
(provide '+avy)
|
||||
;;; avy.el ends here
|
||||
|
|
60
lisp/+bongo.el
Normal file
60
lisp/+bongo.el
Normal file
|
@ -0,0 +1,60 @@
|
|||
;;; +bongo.el --- customizations in bongo -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defgroup +bongo nil
|
||||
"Extra customization for `bongo'."
|
||||
:group 'bongo)
|
||||
|
||||
(defun +bongo-notify ()
|
||||
(notifications-notify
|
||||
:title "Now Playing"
|
||||
:body (let ((bongo-field-separator "
|
||||
"))
|
||||
(substring-no-properties (bongo-formatted-infoset)))
|
||||
:urgency 'low
|
||||
:transient t))
|
||||
|
||||
(defun +bongo-stop-all ()
|
||||
"Ensure only one bongo playlist is playing at a time.
|
||||
This is intended to be :before advice to `bongo-play'."
|
||||
(mapc (lambda (b)
|
||||
(with-current-buffer b
|
||||
(when-let* ((modep (derived-mode-p
|
||||
'bongo-playlist-mode))
|
||||
(bongo-playlist-buffer b)
|
||||
(playingp (bongo-playing-p)))
|
||||
(bongo-stop))))
|
||||
(buffer-list)))
|
||||
|
||||
|
||||
;;; Bongo Radio
|
||||
|
||||
(defcustom +bongo-radio-stations nil
|
||||
"Stations to play using `+bongo-radio'.")
|
||||
|
||||
(defcustom +bongo-radio-buffer-name "*Bongo Radio*"
|
||||
"Name of the buffer that holds all bongo radio stations."
|
||||
:type 'string)
|
||||
|
||||
(defun +bongo-radio ()
|
||||
(interactive)
|
||||
(switch-to-buffer (or (get-buffer +bongo-radio-buffer-name)
|
||||
(+bongo-radio-init))))
|
||||
|
||||
(defun +bongo-radio-init ()
|
||||
(interactive)
|
||||
(let ((bongo-playlist-buffer (get-buffer-create +bongo-radio-buffer-name))
|
||||
(bongo-confirm-flush-playlist nil))
|
||||
(with-bongo-playlist-buffer
|
||||
(bongo-playlist-mode)
|
||||
(bongo-flush-playlist :delete-all)
|
||||
(cl-loop for (name . url) in +bongo-radio-stations
|
||||
do (bongo-insert-uri url name)))
|
||||
(prog1 (switch-to-buffer bongo-playlist-buffer)
|
||||
(goto-char (point-min)))))
|
||||
|
||||
(provide '+bongo)
|
||||
;;; +bongo.el ends here
|
|
@ -11,21 +11,25 @@
|
|||
|
||||
;;; URL Handlers
|
||||
|
||||
(defun +browse-url-set-handlers (handlers)
|
||||
"Set handlers for `browse-url'.
|
||||
(defun +browse-url-set-handlers (&optional handlers)
|
||||
"Set HANDLERS for `browse-url'.
|
||||
Set `browse-url-handlers', if they exist; else
|
||||
`browse-url-browser-function'. The reason for this switch is
|
||||
that the latter is deprecated in Emacs 28+."
|
||||
(set-default (if (boundp 'browse-url-handlers)
|
||||
'browse-url-handlers
|
||||
'browse-url-browser-function)
|
||||
handlers))
|
||||
that the latter is deprecated in Emacs 28+.
|
||||
|
||||
If HANDLERS is absent or nil, recompute handlers. This can be
|
||||
useful when changing the default browser."
|
||||
(let ((h (if (boundp 'browse-url-handlers)
|
||||
'browse-url-handlers
|
||||
'browse-url-browser-function)))
|
||||
(set-default h (or handlers (symbol-value h)))))
|
||||
|
||||
(cl-defmacro +browse-url-make-external-viewer-handler
|
||||
(viewer default-args &optional (prompt "URL: ")
|
||||
&key
|
||||
(custom-group '+browse-url)
|
||||
(name (format "+browse-url-with-%s" viewer)))
|
||||
(name (format "+browse-url-with-%s" viewer))
|
||||
(fallback #'browse-url-generic))
|
||||
"Create a `browse-url' handler function that calls VIEWER on the url.
|
||||
Also create a `customize' setting in CUSTOM-GROUP for VIEWER's
|
||||
arguments. DEFAULT-ARGS specifies the default arguments that
|
||||
|
@ -33,7 +37,10 @@ setting should have. PROMPT will be shown to user in the
|
|||
function's `interactive' spec, as an argument to
|
||||
`browse-url-interactive-arg'. The resulting function will be
|
||||
named NAME, defaulting to \"+browse-url-with-VIEWER\", and the variable
|
||||
\"NAME-args\"."
|
||||
\"NAME-args\".
|
||||
|
||||
If FALLBACK is non-nil, it's a function to fallback on if the
|
||||
`start-process' call fails in anyway."
|
||||
(declare (indent 1))
|
||||
`(progn
|
||||
(defcustom ,(intern (format "%s-args" name))
|
||||
|
@ -41,19 +48,24 @@ named NAME, defaulting to \"+browse-url-with-VIEWER\", and the variable
|
|||
,(format "Arguments to pass to %s in `%s'." viewer name)
|
||||
:type '(repeat :tag "Command-line argument" string)
|
||||
:group ',custom-group)
|
||||
(defun ,(intern name) (url &optional _new-window)
|
||||
(defun ,(intern name) (url &optional new-window)
|
||||
,(format "Open URL in %s." viewer)
|
||||
(interactive (browse-url-interactive-arg ,prompt))
|
||||
(let* ((url (browse-url-encode-url url))
|
||||
(process-environment (browse-url-process-environment)))
|
||||
(message ,(format "Opening %%s in %s..." viewer) url)
|
||||
(apply #'start-process
|
||||
(concat ,viewer " " url) nil
|
||||
,viewer
|
||||
(append ,(intern (format "%s-args" name)) (list url)))))))
|
||||
(unless (ignore-errors
|
||||
(apply #'start-process
|
||||
(concat ,viewer " " url) nil
|
||||
,viewer
|
||||
(append ,(intern (format "%s-args" name))
|
||||
(list url))))
|
||||
(funcall fallback url new-window))))))
|
||||
|
||||
;; Reference implementation: mpv
|
||||
(+browse-url-make-external-viewer-handler "mpv" nil "Video URL: ")
|
||||
(+browse-url-make-external-viewer-handler "mpv" '("--cache-pause-wait=30"
|
||||
"--cache-pause-initial=yes")
|
||||
"Video URL: ")
|
||||
;; And feh too
|
||||
(+browse-url-make-external-viewer-handler "feh" '("--auto-zoom"
|
||||
"--geometry" "800x600"))
|
||||
|
@ -108,9 +120,9 @@ ARGS are ignored here, but passed on for later processing."
|
|||
;; along with the rest of the args, in a list to the original caller (probably
|
||||
;; `browse-url'.)
|
||||
(apply 'list
|
||||
(cl-loop with url = (substring-no-properties
|
||||
(if (consp url) (car url) url))
|
||||
for (regex . transformation) in +browse-url-transformations
|
||||
(cl-loop with url = (substring-no-properties
|
||||
(if (consp url) (car url) url))
|
||||
for (regex . transformation) in +browse-url-transformations
|
||||
if (string-match regex url)
|
||||
return (replace-match transformation nil nil url)
|
||||
;; else
|
||||
|
@ -134,5 +146,11 @@ When using this mode, ensure that the transformed URL is also in
|
|||
(define-global-minor-mode +browse-url-transform-url-global-mode
|
||||
+browse-url-transform-url-mode +browse-url-transform-url-mode)
|
||||
|
||||
(defun +browse-url-other-window (&rest args)
|
||||
"Browse URL in the other window."
|
||||
(let ((browsed (apply #'browse-url args)))
|
||||
(when (bufferp browsed)
|
||||
(switch-to-buffer-other-window browsed))))
|
||||
|
||||
(provide '+browse-url)
|
||||
;;; +browse-url.el ends here
|
||||
|
|
|
@ -4,13 +4,6 @@
|
|||
|
||||
(require 'thingatpt)
|
||||
|
||||
(defvar +casing-map (let ((map (make-sparse-keymap)))
|
||||
(define-key map "u" #'+upcase-dwim)
|
||||
(define-key map "l" #'+downcase-dwim)
|
||||
(define-key map "c" #'+capitalize-dwim)
|
||||
map)
|
||||
"Keymap for word-casing.")
|
||||
|
||||
;;;###autoload
|
||||
(defun +upcase-dwim (arg)
|
||||
"Upcase words in the region, or upcase word at point.
|
||||
|
@ -24,9 +17,10 @@ Otherwise, it calls `upcase-word' on the word at point (using
|
|||
(word-bound (save-excursion
|
||||
(skip-chars-forward "^[:word:]")
|
||||
(bounds-of-thing-at-point 'word))))
|
||||
(upcase-region (car word-bound) (cdr word-bound))
|
||||
(goto-char (cdr word-bound))
|
||||
(upcase-word following))))
|
||||
(when (and (car word-bound) (cdr word-bound))
|
||||
(upcase-region (car word-bound) (cdr word-bound))
|
||||
(goto-char (cdr word-bound))
|
||||
(upcase-word following)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +downcase-dwim (arg)
|
||||
|
@ -41,9 +35,10 @@ Otherwise, it calls `downcase-word' on the word at point (using
|
|||
(word-bound (save-excursion
|
||||
(skip-chars-forward "^[:word:]")
|
||||
(bounds-of-thing-at-point 'word))))
|
||||
(downcase-region (car word-bound) (cdr word-bound))
|
||||
(goto-char (cdr word-bound))
|
||||
(downcase-word following))))
|
||||
(when (and (car word-bound) (cdr word-bound))
|
||||
(downcase-region (car word-bound) (cdr word-bound))
|
||||
(goto-char (cdr word-bound))
|
||||
(downcase-word following)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +capitalize-dwim (arg)
|
||||
|
@ -58,11 +53,30 @@ Otherwise, it calls `capitalize-word' on the word at point (using
|
|||
(word-bound (save-excursion
|
||||
(skip-chars-forward "^[:word:]")
|
||||
(bounds-of-thing-at-point 'word))))
|
||||
(capitalize-region (car word-bound) (cdr word-bound))
|
||||
(goto-char (cdr word-bound))
|
||||
(capitalize-word following))))
|
||||
(when (and (car word-bound) (cdr word-bound))
|
||||
(capitalize-region (car word-bound) (cdr word-bound))
|
||||
(goto-char (cdr word-bound))
|
||||
(capitalize-word following)))))
|
||||
|
||||
;; Later on, I'll add repeat maps and stuff in here...
|
||||
|
||||
(defvar +casing-map (let ((map (make-sparse-keymap)))
|
||||
(define-key map "u" #'+upcase-dwim)
|
||||
(define-key map (kbd "M-u") #'+upcase-dwim)
|
||||
(define-key map "l" #'+downcase-dwim)
|
||||
(define-key map (kbd "M-l") #'+downcase-dwim)
|
||||
(define-key map "c" #'+capitalize-dwim)
|
||||
(define-key map (kbd "M-c") #'+capitalize-dwim)
|
||||
map)
|
||||
"Keymap for case-related twiddling.")
|
||||
|
||||
(define-minor-mode +casing-mode
|
||||
"Enable easy case-twiddling commands."
|
||||
:lighter " cC"
|
||||
:global t
|
||||
:keymap (let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "M-c") +casing-map)
|
||||
map))
|
||||
|
||||
(provide '+casing)
|
||||
;;; +casing.el ends here
|
||||
|
|
22
lisp/+chicken.el
Normal file
22
lisp/+chicken.el
Normal file
|
@ -0,0 +1,22 @@
|
|||
;;; +chicken.el --- Chicken Scheme additions -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Reload [[https://wiki.call-cc.org/eggref/5/awful][awful]] with a keybinding
|
||||
|
||||
(defun +chicken-awful-reload ()
|
||||
"Reload awful by visiting /reload."
|
||||
(interactive)
|
||||
(save-buffer)
|
||||
(condition-case e
|
||||
(url-retrieve-synchronously "http://localhost:8080/reload")
|
||||
(file-error (progn
|
||||
(message "Couldn't ping awful's server. Starting...")
|
||||
(start-process "awful" (generate-new-buffer "*awful*")
|
||||
"awful" "--development-mode" (buffer-file-name))))
|
||||
(t (message "Some awful error occurred!"))))
|
||||
|
||||
(provide '+chicken)
|
||||
;;; +chicken.el ends here
|
179
lisp/+circe.el
179
lisp/+circe.el
|
@ -45,21 +45,27 @@
|
|||
|
||||
;;; Channel information
|
||||
|
||||
(defvar-local +circe-current-topic ""
|
||||
"Cached topic of the buffer's channel.")
|
||||
|
||||
(defun +circe-current-topic (&optional message)
|
||||
"Return the topic of the current channel.
|
||||
When called with optional MESSAGE non-nil, or interactively, also
|
||||
message the current topic."
|
||||
(interactive "p")
|
||||
(let ((topic
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(or (re-search-backward
|
||||
(rx (group "*** "
|
||||
(or "Topic" "topic" "TOPIC")
|
||||
(* (not ":")) ": ")
|
||||
(group (+ nonl)))))
|
||||
(buffer-substring-no-properties
|
||||
(match-beginning 2) (match-end 2)))))
|
||||
(or (save-excursion
|
||||
(goto-char (point-max))
|
||||
(and (re-search-backward
|
||||
(rx (group "*** "
|
||||
(or "Topic" "topic" "TOPIC")
|
||||
(* (not ":")) ": ")
|
||||
(group (+ nonl)))
|
||||
nil t)
|
||||
(buffer-substring-no-properties
|
||||
(match-beginning 2) (match-end 2))))
|
||||
+circe-current-topic)))
|
||||
(setq +circe-current-topic topic)
|
||||
(when message
|
||||
(message "%s" topic))
|
||||
topic))
|
||||
|
@ -86,8 +92,8 @@ replace {nick} in the string with {NO-NICK}."
|
|||
"Make a formatting regex for CHAR delimiters.
|
||||
For entry into `lui-formatting-list'."
|
||||
`(rx (or bol whitespace)
|
||||
(group ,char (+? (not (any whitespace ,char))) ,char)
|
||||
(or eol whitespace)))
|
||||
(group ,char (+? (not (any whitespace ,char))) ,char)
|
||||
(or eol whitespace)))
|
||||
|
||||
;;; Hooks & Advice
|
||||
|
||||
|
@ -101,9 +107,11 @@ For entry into `lui-formatting-list'."
|
|||
|
||||
(defun +circe-kill-buffer (&rest _)
|
||||
"Kill a circe buffer without confirmation, and after a delay."
|
||||
(let ((circe-channel-killed-confirmation nil)
|
||||
(circe-server-killed-confirmation nil))
|
||||
(run-with-timer 0.25 nil 'kill-buffer)))
|
||||
(let ((circe-channel-killed-confirmation)
|
||||
(circe-server-killed-confirmation))
|
||||
(when (derived-mode-p 'lui-mode) ; don't spuriously kill
|
||||
(ignore-errors
|
||||
(kill-buffer)))))
|
||||
|
||||
(defun +circe-quit@kill-buffer (&rest _)
|
||||
"ADVICE: kill all buffers of a server after `circe-command-QUIT'."
|
||||
|
@ -115,9 +123,11 @@ For entry into `lui-formatting-list'."
|
|||
|
||||
(defun +circe-gquit@kill-buffer (&rest _)
|
||||
"ADVICE: kill all Circe buffers after `circe-command-GQUIT'."
|
||||
(dolist (buf (circe-server-buffers))
|
||||
(with-current-buffer buf
|
||||
(+circe-quit@kill-buffer))))
|
||||
(let ((circe-channel-killed-confirmation)
|
||||
(circe-server-killed-confirmation))
|
||||
(dolist (buf (circe-server-buffers))
|
||||
(with-current-buffer buf
|
||||
(+circe-quit@kill-buffer)))))
|
||||
|
||||
(defun +circe-quit-all@kill-emacs ()
|
||||
"Quit all circe buffers when killing Emacs."
|
||||
|
@ -135,7 +145,7 @@ For entry into `lui-formatting-list'."
|
|||
"What to do with `circe-server' buffers when created.")
|
||||
|
||||
(el-patch-defun circe (network-or-server &rest server-options)
|
||||
"Connect to IRC.
|
||||
"Connect to IRC.
|
||||
|
||||
Connect to the given network specified by NETWORK-OR-SERVER.
|
||||
|
||||
|
@ -153,36 +163,123 @@ All SERVER-OPTIONS are treated as variables by getting the string
|
|||
locally in the server buffer.
|
||||
|
||||
See `circe-network-options' for a list of common options."
|
||||
(interactive (circe--read-network-and-options))
|
||||
(let* ((options (circe--server-get-network-options network-or-server
|
||||
server-options))
|
||||
(buffer (circe--server-generate-buffer options)))
|
||||
(with-current-buffer buffer
|
||||
(circe-server-mode)
|
||||
(circe--server-set-variables options)
|
||||
(circe-reconnect))
|
||||
(el-patch-swap (pop-to-buffer-same-window buffer)
|
||||
(funcall +circe-server-buffer-action buffer))))
|
||||
(interactive (circe--read-network-and-options))
|
||||
(let* ((options (circe--server-get-network-options network-or-server
|
||||
server-options))
|
||||
(buffer (circe--server-generate-buffer options)))
|
||||
(with-current-buffer buffer
|
||||
(circe-server-mode)
|
||||
(circe--server-set-variables options)
|
||||
(circe-reconnect))
|
||||
(el-patch-swap (pop-to-buffer-same-window buffer)
|
||||
(funcall +circe-server-buffer-action buffer))))
|
||||
|
||||
;;; Chat commands
|
||||
|
||||
(defun circe-command-SHORTEN (url)
|
||||
"Shorten URL using `0x0-shorten-uri'.")
|
||||
|
||||
(defun circe-command-SLAP (nick)
|
||||
"Slap NICK around a bit with a large trout.")
|
||||
"Slap NICK around a bit with a large trout."
|
||||
(interactive (list (completing-read "Nick to slap: "
|
||||
(circe-channel-nicks)
|
||||
nil t nil)))
|
||||
(circe-command-ME (format "slaps %s about a bit with a large trout" nick)))
|
||||
|
||||
;;; Pure idiocy
|
||||
;;; Filtering functions --- XXX: These don't work right.
|
||||
;; Set `lui-input-function' to `+lui-filter', then add the filters you want to
|
||||
;; `circe-channel-mode-hook'.
|
||||
|
||||
(define-minor-mode circe-cappy-hour-mode
|
||||
(defvar +lui-filters nil
|
||||
"Stack of input functions to apply.
|
||||
This is an alist with cells of the structure (TAG . FN), so we
|
||||
can easily remove elements.")
|
||||
(make-variable-buffer-local '+lui-filters)
|
||||
|
||||
(defun +lui-filter (text &optional fn-alist)
|
||||
(let ((fs (nreverse (purecopy (or fn-alist +lui-filters)))))
|
||||
(while fs
|
||||
(setq text (funcall (cdr (pop fs)) text)))
|
||||
(circe--input text)))
|
||||
|
||||
(defmacro +circe-define-filter (name docstring &rest body)
|
||||
"Define a filter for circe-inputted text."
|
||||
(declare (doc-string 2)
|
||||
(indent 1))
|
||||
(let (plist)
|
||||
(while (keywordp (car-safe body))
|
||||
(push (pop body) plist)
|
||||
(push (pop body) plist))
|
||||
;; Return value
|
||||
`(define-minor-mode ,name
|
||||
,docstring
|
||||
,@(nreverse plist)
|
||||
(when (derived-mode-p 'circe-chat-mode)
|
||||
(if ,name
|
||||
(push '(,name . (lambda (it) ,@body)) +lui-filters)
|
||||
(setq +lui-filters
|
||||
(assoc-delete-all ',name +lui-filters)))))))
|
||||
|
||||
;; CAPPY HOUR! (Pure idiocy)
|
||||
|
||||
(+circe-define-filter +circe-cappy-hour-mode
|
||||
"ENABLE CAPPY HOUR IN CIRCE!"
|
||||
:lighter "CAPPY HOUR"
|
||||
(when (derived-mode-p 'circe-chat-mode)
|
||||
(if circe-cappy-hour-mode
|
||||
(setq-local lui-input-function
|
||||
(lambda (input) (circe--input (upcase input))))
|
||||
;; XXX: It'd be better if this were more general, but whatever.
|
||||
(setq-local lui-input-function #'circe--input))))
|
||||
:lighter " CAPPY HOUR"
|
||||
(upcase it))
|
||||
|
||||
;; URL Shortener
|
||||
|
||||
(+circe-define-filter +circe-shorten-url-mode
|
||||
"Shorten long urls when chatting."
|
||||
:lighter " c0x0"
|
||||
(+circe-0x0-shorten-urls it))
|
||||
|
||||
(defvar +circe-0x0-max-length 20
|
||||
"Maximum length of URLs before using a shortener.")
|
||||
|
||||
(defun +circe-0x0-shorten-urls (text)
|
||||
"Find urls in TEXT and shorten them using `0x0'."
|
||||
(require '0x0)
|
||||
(require 'browse-url)
|
||||
(let ((case-fold-search t))
|
||||
(replace-regexp-in-string
|
||||
browse-url-button-regexp
|
||||
(lambda (match)
|
||||
(if (> (length match) +circe-0x0-max-length)
|
||||
(+with-message (format "Shortening URL: %s" match)
|
||||
(0x0-shorten-uri (0x0--choose-server)
|
||||
(substring-no-properties match)))
|
||||
match))
|
||||
text)))
|
||||
|
||||
(defun +circe-shorten-urls-all ()
|
||||
"Turn on `+circe-shorten-url-mode' in all chat buffers."
|
||||
(interactive)
|
||||
(+mapc-some-buffers
|
||||
(lambda () (+circe-shorten-url-mode +1))
|
||||
(lambda (buf)
|
||||
(derived-mode-p 'circe-chat-mode))))
|
||||
|
||||
;; Temperature conversion
|
||||
|
||||
(+circe-define-filter +circe-F/C-mode
|
||||
"Convert degF to degF/degC for international chats."
|
||||
:lighter " F/C"
|
||||
(str-F/C it))
|
||||
|
||||
(defun fahrenheit-to-celsius (degf)
|
||||
"Convert DEGF to Celsius."
|
||||
(round (* (/ 5.0 9.0) (- degf 32))))
|
||||
|
||||
(defun celsius-to-fahrenheit (degc)
|
||||
"Convert DEGC to Fahrenheit."
|
||||
(round (+ 32 (* (/ 9.0 5.0) degc))))
|
||||
|
||||
(defun str-F/C (text)
|
||||
(replace-regexp-in-string "[^.]\\([[:digit:]]+\\(?:\\.[[:digit:]]+\\)?[fF]\\)"
|
||||
(lambda (match)
|
||||
(format "%s/%dC" match
|
||||
(fahrenheit-to-celsius
|
||||
(string-to-number match))))
|
||||
text
|
||||
nil 1))
|
||||
|
||||
(provide '+circe)
|
||||
;;; +circe.el ends here
|
||||
|
|
64
lisp/+compat.el
Normal file
64
lisp/+compat.el
Normal file
|
@ -0,0 +1,64 @@
|
|||
;;; +compat.el --- Thin backward-compatibility shim -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; I use different versionso of Emacs. Sometimes I have to copy-paste functions
|
||||
;; from newer Emacs to make my customizations work. This is that file.
|
||||
|
||||
;; This is probably ill-advised.
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;; Load stuff in +compat/ subdirectory
|
||||
(dolist (file (directory-files (locate-user-emacs-file "lisp/+compat") :full "\\.el\\'"))
|
||||
(load file :noerror))
|
||||
|
||||
;;; Only define things if not already defined
|
||||
(defmacro +compat-defun (name &rest args)
|
||||
`(if (fboundp ',name)
|
||||
(message "+compat: `%s' already bound." ',name)
|
||||
(defun ,name ,@args)))
|
||||
|
||||
(defmacro +compat-defmacro (name &rest args)
|
||||
`(if (fboundp ',name)
|
||||
(message "+compat: `%s' already bound." ',name)
|
||||
(defmacro ,name ,@args)))
|
||||
|
||||
;;; Single functions
|
||||
|
||||
(+compat-defmacro dlet (binders &rest body)
|
||||
"Like `let' but using dynamic scoping."
|
||||
(declare (indent 1) (debug let))
|
||||
;; (defvar FOO) only affects the current scope, but in order for
|
||||
;; this not to affect code after the main `let' we need to create a new scope,
|
||||
;; which is what the surrounding `let' is for.
|
||||
;; FIXME: (let () ...) currently doesn't actually create a new scope,
|
||||
;; which is why we use (let (_) ...).
|
||||
`(let (_)
|
||||
,@(mapcar (lambda (binder)
|
||||
`(defvar ,(if (consp binder) (car binder) binder)))
|
||||
binders)
|
||||
(let ,binders ,@body)))
|
||||
|
||||
;; https://git.savannah.gnu.org/cgit/emacs.git/diff/?id=772b189143453745a8e014e21d4b6b78f855bba3
|
||||
(+compat-defun rename-visited-file (new-location)
|
||||
"Rename the file visited by the current buffer to NEW-LOCATION.
|
||||
This command also sets the visited file name. If the buffer
|
||||
isn't visiting any file, that's all it does.
|
||||
|
||||
Interactively, this prompts for NEW-LOCATION."
|
||||
(interactive
|
||||
(list (if buffer-file-name
|
||||
(read-file-name "Rename visited file to: ")
|
||||
(read-file-name "Set visited file name: "
|
||||
default-directory
|
||||
(expand-file-name
|
||||
(file-name-nondirectory (buffer-name))
|
||||
default-directory)))))
|
||||
(when (and buffer-file-name
|
||||
(file-exists-p buffer-file-name))
|
||||
(rename-file buffer-file-name new-location))
|
||||
(set-visited-file-name new-location nil t))
|
||||
|
||||
(provide '+compat)
|
||||
;;; +compat.el ends here
|
20
lisp/+compile.el
Normal file
20
lisp/+compile.el
Normal file
|
@ -0,0 +1,20 @@
|
|||
;;; +compile.el --- Extras for compile -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defcustom +compile-function nil
|
||||
"Function to run to \"compile\" a buffer."
|
||||
:type 'function
|
||||
:local t
|
||||
:risky nil)
|
||||
|
||||
(defun +compile-dispatch (&optional arg)
|
||||
"Run `+compile-function', if bound, or `compile'.
|
||||
Any prefix ARG is passed to that function."
|
||||
(interactive "P")
|
||||
(call-interactively (or +compile-function #'compile)))
|
||||
|
||||
(provide '+compile)
|
||||
;;; +compile.el ends here
|
|
@ -5,14 +5,14 @@
|
|||
(defun +consult-project-root ()
|
||||
"Return either the current project, or the VC root, of current file."
|
||||
(if (and (functionp 'project-current)
|
||||
(project-current))
|
||||
(project-current))
|
||||
(car (project-roots (project-current)))
|
||||
(vc-root-dir)))
|
||||
|
||||
;;; Cribbed functions
|
||||
;; https://github.com/minad/consult/wiki
|
||||
|
||||
(defun consult--orderless-regexp-compiler (input type)
|
||||
(defun consult--orderless-regexp-compiler (input type &rest _)
|
||||
(setq input (orderless-pattern-compiler input))
|
||||
(cons
|
||||
(mapcar (lambda (r) (consult--convert-regexp r type)) input)
|
||||
|
|
|
@ -15,8 +15,10 @@ Copy from BEGIN to END using `kill-ring-save' if no argument was
|
|||
passed, or with `crux-indent-rigidly-and-copy-to-clipboard' if
|
||||
one was."
|
||||
(interactive "r\nP")
|
||||
(call-interactively (if arg #'kill-ring-save
|
||||
#'crux-indent-rigidly-and-copy-to-clipboard)))
|
||||
(call-interactively (if arg
|
||||
#'crux-indent-rigidly-and-copy-to-clipboard
|
||||
#'kill-ring-save))
|
||||
(pulse-momentary-highlight-region begin end))
|
||||
|
||||
(defcustom +crux-default-date-format "%c"
|
||||
"Default date format to use for `+crux-insert-date-or-time'.
|
||||
|
@ -42,5 +44,15 @@ prompt for the time format."
|
|||
(format-time-string +crux-alternate-date-format time))
|
||||
(t (format-time-string (read-string "Time Format: ") time))))))
|
||||
|
||||
(defun +crux-kill-and-join-forward (&optional arg)
|
||||
"If at end of line, join with following; else (visual)-kill line.
|
||||
In `visual-line-mode', runs command `kill-visual-line'; in other
|
||||
modes, runs command `kill-line'. Passes ARG to command when
|
||||
provided. Deletes whitespace at join."
|
||||
(interactive "P")
|
||||
(if (and (eolp) (not (bolp)))
|
||||
(delete-indentation 1)
|
||||
(funcall (if visual-line-mode #'kill-visual-line #'kill-line) arg)))
|
||||
|
||||
(provide '+crux)
|
||||
;;; +crux.el ends here
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
(defcustom +custom-variable-allowlist nil
|
||||
"Variables to allow changing while loading the Custom file.")
|
||||
|
||||
(defcustom +custom-after-load-hook nil
|
||||
"Functions to run after loading the custom file.")
|
||||
|
||||
(defun +custom-load-ignoring-most-customizations (&optional
|
||||
error
|
||||
nomessage
|
||||
|
@ -50,12 +53,13 @@ pass t to it."
|
|||
(cl-letf (((symbol-function 'custom-set-faces) 'ignore)
|
||||
((symbol-function 'custom-set-variables)
|
||||
(lambda (&rest args)
|
||||
(apply 'custom-theme-set-variables 'user
|
||||
(apply #'custom-theme-set-variables 'user
|
||||
(seq-filter (lambda (el)
|
||||
(memq (car el)
|
||||
+custom-variable-allowlist))
|
||||
args)))))
|
||||
(load custom-file (not error) nomessage nosuffix must-suffix)))
|
||||
(load custom-file (not error) nomessage nosuffix must-suffix))
|
||||
(run-hooks '+custom-after-load-hook))
|
||||
|
||||
(defun +cus-edit-expand-widgets (&rest _)
|
||||
"Expand descriptions in `Custom-mode' buffers."
|
||||
|
|
|
@ -2,7 +2,27 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(with-eval-after-load 'vertico
|
||||
(defun +dired-goto-file (file)
|
||||
"ADVICE for `dired-goto-file' to make RET call `vertico-exit'."
|
||||
(interactive ; stolen from `dired-goto-file'
|
||||
(prog1
|
||||
(list (dlet ((vertico-map (copy-keymap vertico-map)))
|
||||
(define-key vertico-map (kbd "RET") #'vertico-exit)
|
||||
(expand-file-name (read-file-name "Goto file: "
|
||||
(dired-current-directory)))))
|
||||
(push-mark)))
|
||||
(dired-goto-file file)))
|
||||
|
||||
;;; [[https://www.reddit.com/r/emacs/comments/u2lf9t/weekly_tips_tricks_c_thread/i4n9aoa/?context=3][Dim files in .gitignore]]
|
||||
|
||||
(defun +dired-dim-git-ignores ()
|
||||
"Dim out .gitignore contents"
|
||||
(require 'vc)
|
||||
(when-let ((ignores (vc-default-ignore-completion-table 'git ".gitignore"))
|
||||
(exts (make-local-variable 'completion-ignored-extensions)))
|
||||
(dolist (item ignores)
|
||||
(add-to-list exts item))))
|
||||
|
||||
(provide '+dired)
|
||||
;;; +dired.el ends here
|
||||
|
|
45
lisp/+ecomplete.el
Normal file
45
lisp/+ecomplete.el
Normal file
|
@ -0,0 +1,45 @@
|
|||
;;; +ecomplete.el --- ecomplete extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; see [[https://github.com/oantolin/emacs-config/blob/master/my-lisp/ecomplete-extras.el][oantolin's config]]
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ecomplete)
|
||||
|
||||
(defun +ecomplete--name+address (email)
|
||||
"Return a pair of the name and address for an EMAIL."
|
||||
(let (name)
|
||||
(when (string-match "^\\(?:\\(.*\\) \\)?<\\(.*\\)>$" email)
|
||||
(setq name (match-string 1 email)
|
||||
email (match-string 2 email)))
|
||||
(cons name email)))
|
||||
|
||||
(defun +ecomplete-add-email (email)
|
||||
"Add email address to ecomplete's database."
|
||||
(interactive "sEmail address: ")
|
||||
(pcase-let ((`(,name . ,email) (+ecomplete--name+address email)))
|
||||
(unless name (setq name (read-string "Name: ")))
|
||||
(ecomplete-add-item
|
||||
'mail email
|
||||
(format (cond ((equal name "") "%s%s")
|
||||
((string-match-p "^\\(?:[A-Za-z0-9 ]*\\|\".*\"\\)$" name)
|
||||
"%s <%s>")
|
||||
(t "\"%s\" <%s>"))
|
||||
name email))
|
||||
(ecomplete-save)))
|
||||
|
||||
(defun +ecomplete-remove-email (email)
|
||||
"Remove email address from ecomplete's database."
|
||||
(interactive
|
||||
(list (completing-read "Email address: "
|
||||
(ecomplete-completion-table 'mail))))
|
||||
(when-let ((email (cdr (+ecomplete--name+address email)))
|
||||
(entry (ecomplete-get-item 'mail email)))
|
||||
(setf (cdr (assq 'mail ecomplete-database))
|
||||
(remove entry (cdr (assq 'mail ecomplete-database))))
|
||||
(ecomplete-save)))
|
||||
|
||||
(provide '+ecomplete)
|
||||
;;; +ecomplete.el ends here
|
161
lisp/+elfeed.el
161
lisp/+elfeed.el
|
@ -4,6 +4,7 @@
|
|||
|
||||
(require 'elfeed)
|
||||
|
||||
;; https://karthinks.com/software/lazy-elfeed/
|
||||
(defun +elfeed-scroll-up-command (&optional arg)
|
||||
"Scroll up or go to next feed item in Elfeed"
|
||||
(interactive "^P")
|
||||
|
@ -20,5 +21,165 @@
|
|||
(scroll-down-command arg)
|
||||
(error (elfeed-show-prev)))))
|
||||
|
||||
(defun +elfeed-search-browse-generic ()
|
||||
"Browse a url with `browse-url-generic-browser'."
|
||||
(interactive)
|
||||
(elfeed-search-browse-url t))
|
||||
|
||||
(defun +elfeed-show-browse-generic ()
|
||||
"Browse a url with `browse-url-generic-browser'."
|
||||
(interactive)
|
||||
(elfeed-show-visit t))
|
||||
|
||||
(defun +elfeed-show-mark-read-and-advance ()
|
||||
"Mark an item as read and advance to the next item.
|
||||
If multiple items are selected, don't advance."
|
||||
(interactive)
|
||||
(call-interactively #'elfeed-search-untag-all-unread)
|
||||
(unless (region-active-p)
|
||||
(call-interactively #'next-line)))
|
||||
|
||||
;;; Fetch feeds async
|
||||
;; https://github.com/skeeto/elfeed/issues/367
|
||||
|
||||
(defun +elfeed--update-message ()
|
||||
(message "[Elfeed] Update in progress")
|
||||
'ignore)
|
||||
|
||||
(defvar +elfeed--update-running-p nil "Whether an update is currently running.")
|
||||
(defvar +elfeed--update-count 0 "How many times `+elfeed-update-command' has run.")
|
||||
(defcustom +elfeed-update-niceness 15
|
||||
"How \"nice\" `+elfeed-update-command' should be."
|
||||
:type 'integer
|
||||
:group 'elfeed)
|
||||
|
||||
(defcustom +elfeed-update-lockfile
|
||||
(expand-file-name "+elfeed-update-lock" (temporary-file-directory))
|
||||
"The file to ")
|
||||
|
||||
(defun +elfeed-update-command ()
|
||||
(interactive)
|
||||
(unless (or +elfeed--update-running-p
|
||||
(derived-mode-p 'elfeed-show-mode 'elfeed-search-mode))
|
||||
(let ((script (expand-file-name "/tmp/elfeed-update.el"))
|
||||
(update-message-format "[Elfeed] Background update: %s"))
|
||||
(setq +elfeed--update-running-p t)
|
||||
(elfeed-db-save)
|
||||
(advice-add 'elfeed :override #'+elfeed--update-message)
|
||||
(ignore-errors (kill-buffer "*elfeed-search*"))
|
||||
(ignore-errors (kill-buffer "*elfeed-log*"))
|
||||
(elfeed-db-unload)
|
||||
(make-directory (file-name-directory script) :parents)
|
||||
(with-temp-buffer
|
||||
(insert
|
||||
(let ((print-level nil)
|
||||
(print-length nil))
|
||||
(prin1-to-string ;; Print the following s-expression to a string
|
||||
`(progn
|
||||
;; Set up the environment
|
||||
(setq lexical-binding t)
|
||||
(load (locate-user-emacs-file "early-init"))
|
||||
(dolist (pkg '(elfeed elfeed-org))
|
||||
(straight-use-package pkg)
|
||||
(require pkg))
|
||||
;; Copy variables from current environment
|
||||
(progn
|
||||
,@(cl-loop for copy-var in '(rmh-elfeed-org-files
|
||||
elfeed-db-directory
|
||||
elfeed-curl-program-name
|
||||
elfeed-use-curl
|
||||
elfeed-curl-extra-arguments
|
||||
elfeed-enclosure-default-dir)
|
||||
collect `(progn (message "%S = %S" ',copy-var ',(symbol-value copy-var))
|
||||
(setq ,copy-var ',(symbol-value copy-var)))))
|
||||
;; Define new variables for this environment
|
||||
(progn
|
||||
,@(cl-loop for (new-var . new-val) in '((elfeed-curl-max-connections . 4))
|
||||
collect `(progn (message "%S = %S" ',new-var ',new-val)
|
||||
(setq ,new-var ',new-val))))
|
||||
;; Redefine `elfeed-log' to log everything
|
||||
(defun elfeed-log (level fmt &rest objects)
|
||||
(princ (format "[%s] [%s]: %s\n"
|
||||
(format-time-string "%F %T")
|
||||
level
|
||||
(apply #'format fmt objects))))
|
||||
;; Run elfeed
|
||||
(elfeed-org)
|
||||
(elfeed)
|
||||
(elfeed-db-load)
|
||||
(elfeed-update)
|
||||
;; Wait for `elfeed-update' to finish
|
||||
(let ((q<5-count 0))
|
||||
(while (and (> (elfeed-queue-count-total) 0)
|
||||
(< q<5-count 5))
|
||||
(sleep-for 5)
|
||||
(message "Elfeed queue count total: %s" (elfeed-queue-count-total))
|
||||
(when (< (elfeed-queue-count-total) 5)
|
||||
(cl-incf q<5-count))
|
||||
(accept-process-output)))
|
||||
;; Garbage collect and save the database
|
||||
(elfeed-db-gc)
|
||||
(elfeed-db-save)
|
||||
(princ (format ,update-message-format "done."))))))
|
||||
(write-file script))
|
||||
(chmod script #o777)
|
||||
(message update-message-format "start")
|
||||
(set-process-sentinel (start-process-shell-command
|
||||
"Elfeed" "*+elfeed-update-background*"
|
||||
(format "nice -n %d %s %s"
|
||||
+elfeed-update-niceness
|
||||
"emacs -Q --script"
|
||||
script))
|
||||
(lambda (proc stat)
|
||||
(advice-remove 'elfeed #'+elfeed--update-message)
|
||||
(setq +elfeed--update-running-p nil)
|
||||
(unless (string= stat "killed")
|
||||
(setq +elfeed--update-count (1+ +elfeed--update-count)))
|
||||
(message update-message-format (string-trim stat)))))))
|
||||
|
||||
(defvar +elfeed--update-timer nil "Timer for `elfeed-update-command'.")
|
||||
(defvar +elfeed--update-first-time 6 "How long to wait for the first time.")
|
||||
(defvar +elfeed--update-repeat (* 60 15) "How long between updates.")
|
||||
|
||||
(defcustom +elfeed-update-proceed-hook nil
|
||||
"Predicates to query before running `+elfeed-update-command'.
|
||||
Each hook is passed no arguments."
|
||||
:type 'hook)
|
||||
|
||||
(defun +elfeed-update-command-wrapper ()
|
||||
"Run `+elfeed-update-command', but only sometimes.
|
||||
If any of the predicates in `+elfeed-update-proceed-hook' return
|
||||
nil, don't run `+elfeed-update-command'. If they all return
|
||||
non-nil, proceed."
|
||||
(when (run-hook-with-args-until-failure '+elfeed-update-proceed-hook)
|
||||
(+elfeed-update-command)))
|
||||
|
||||
(defun +elfeed--cancel-update-timer ()
|
||||
"Cancel `+elfeed--update-timer'."
|
||||
(unless +elfeed--update-running-p
|
||||
(ignore-errors (cancel-timer +elfeed--update-timer))
|
||||
(setq +elfeed--update-timer nil)))
|
||||
|
||||
(defun +elfeed--reinstate-update-timer ()
|
||||
"Reinstate `+elfeed--update-timer'."
|
||||
;; First, unload the db
|
||||
(setq +elfeed--update-timer
|
||||
(run-at-time +elfeed--update-first-time
|
||||
+elfeed--update-repeat
|
||||
#'+elfeed-update-command-wrapper)))
|
||||
|
||||
(define-minor-mode +elfeed-update-async-mode
|
||||
"Minor mode to update elfeed async-style."
|
||||
:global t
|
||||
(if +elfeed-update-async-mode
|
||||
(progn ; enable
|
||||
(+elfeed--reinstate-update-timer)
|
||||
(advice-add 'elfeed :before '+elfeed--cancel-update-timer)
|
||||
(advice-add 'elfeed-search-quit-window :after '+elfeed--reinstate-update-timer))
|
||||
(progn ; disable
|
||||
(advice-remove 'elfeed '+elfeed--cancel-update-timer)
|
||||
(advice-remove 'elfeed-search-quit-window '+elfeed--reinstate-update-timer)
|
||||
(+elfeed--cancel-update-timer))))
|
||||
|
||||
(provide '+elfeed)
|
||||
;;; +elfeed.el ends here
|
||||
|
|
181
lisp/+emacs.el
181
lisp/+emacs.el
|
@ -25,6 +25,7 @@ Do this only if the buffer is not visiting a file."
|
|||
(let ((buffer-file-name (buffer-name buf)))
|
||||
(set-auto-mode))))
|
||||
|
||||
|
||||
;;; General settings
|
||||
|
||||
(setq-default
|
||||
|
@ -42,6 +43,7 @@ Do this only if the buffer is not visiting a file."
|
|||
backup-by-copying t
|
||||
backup-directory-alist `((".*" . ,(.etc "backup/" t)))
|
||||
blink-cursor-blinks 1
|
||||
comp-deferred-compilation nil
|
||||
completion-category-defaults nil
|
||||
completion-category-overrides '((file (styles . (partial-completion))))
|
||||
completion-ignore-case t
|
||||
|
@ -60,45 +62,52 @@ Do this only if the buffer is not visiting a file."
|
|||
fast-but-imprecise-scrolling t
|
||||
file-name-shadow-properties '(invisible t intangible t)
|
||||
fill-column 80
|
||||
find-file-visit-truename t
|
||||
frame-resize-pixelwise t
|
||||
global-auto-revert-non-file-buffers t
|
||||
global-mark-ring-max 100
|
||||
hscroll-margin 1
|
||||
hscroll-step 1
|
||||
imenu-auto-rescan t
|
||||
image-use-external-converter (or (executable-find "convert")
|
||||
(executable-find "gm")
|
||||
(executable-find "ffmpeg"))
|
||||
indent-tabs-mode nil
|
||||
indicate-empty-lines nil
|
||||
indicate-buffer-boundaries 'left
|
||||
inhibit-startup-screen t
|
||||
initial-buffer-choice t
|
||||
kept-new-versions 6
|
||||
kept-old-versions 2
|
||||
kill-do-not-save-duplicates t
|
||||
kill-read-only-ok t
|
||||
kill-ring-max 500
|
||||
kmacro-ring-max 20
|
||||
load-prefer-newer t
|
||||
load-prefer-newer noninteractive
|
||||
major-mode '+set-major-mode-from-buffer-name
|
||||
mark-ring-max 50
|
||||
minibuffer-eldef-shorten-default t
|
||||
minibuffer-prompt-properties (list 'read-only t
|
||||
'cursor-intangible t
|
||||
'face 'minibuffer-prompt)
|
||||
'cursor-intangible t
|
||||
'face 'minibuffer-prompt)
|
||||
mode-require-final-newline 'visit-save
|
||||
mouse-drag-copy-region t
|
||||
mouse-wheel-progressive-speed nil
|
||||
mouse-yank-at-point t
|
||||
native-comp-async-report-warnings-errors 'silent
|
||||
native-comp-deferred-compilation nil
|
||||
read-answer-short t
|
||||
read-buffer-completion-ignore-case t
|
||||
read-extended-command-predicate (when (fboundp
|
||||
'command-completion-default-include-p)
|
||||
'command-completion-default-include-p)
|
||||
;; read-extended-command-predicate
|
||||
;; (when (fboundp
|
||||
;; 'command-completion-default-include-p)
|
||||
;; 'command-completion-default-include-p)
|
||||
read-process-output-max (+bytes 1 :mib) ; We’re in the future man. Set that to at least a megabyte
|
||||
recenter-positions '(top middle bottom)
|
||||
regexp-search-ring-max 100
|
||||
regexp-search-ring-max 200
|
||||
save-interprogram-paste-before-kill t
|
||||
scroll-conservatively 101
|
||||
scroll-down-aggressively 0.01
|
||||
scroll-margin 1
|
||||
scroll-margin 2
|
||||
scroll-preserve-screen-position 1
|
||||
scroll-step 1
|
||||
scroll-up-aggressively 0.01
|
||||
|
@ -112,7 +121,7 @@ Do this only if the buffer is not visiting a file."
|
|||
show-paren-when-point-inside-paren t
|
||||
;;show-trailing-whitespace t
|
||||
tab-bar-show 1
|
||||
tab-width 4
|
||||
tab-width 8 ; so alignment expecting the default looks right
|
||||
tramp-backup-directory-alist backup-directory-alist
|
||||
undo-limit 100000000 ; 10 MB
|
||||
use-dialog-box nil
|
||||
|
@ -139,8 +148,12 @@ Do this only if the buffer is not visiting a file."
|
|||
(when (version< emacs-version "28")
|
||||
(fset 'yes-or-no-p 'y-or-n-p))
|
||||
|
||||
|
||||
;;; Encodings
|
||||
|
||||
;; Allegedly, this is the only one you need...
|
||||
(set-language-environment "UTF-8")
|
||||
;; But I still set all of these, for fun.
|
||||
(setq-default locale-coding-system 'utf-8-unix
|
||||
coding-system-for-read 'utf-8-unix
|
||||
coding-system-for-write 'utf-8-unix
|
||||
|
@ -152,7 +165,6 @@ Do this only if the buffer is not visiting a file."
|
|||
STRING))
|
||||
|
||||
(set-charset-priority 'unicode)
|
||||
(set-language-environment "UTF-8")
|
||||
(prefer-coding-system 'utf-8-unix)
|
||||
(set-default-coding-systems 'utf-8-unix)
|
||||
(set-terminal-coding-system 'utf-8-unix)
|
||||
|
@ -166,34 +178,47 @@ Do this only if the buffer is not visiting a file."
|
|||
(set-selection-coding-system 'utf-8)
|
||||
(set-clipboard-coding-system 'utf-8)))
|
||||
|
||||
|
||||
;;; Modes
|
||||
|
||||
(dolist (enable-mode '(global-auto-revert-mode
|
||||
blink-cursor-mode
|
||||
electric-pair-mode
|
||||
show-paren-mode
|
||||
global-so-long-mode
|
||||
minibuffer-depth-indicate-mode
|
||||
file-name-shadow-mode
|
||||
minibuffer-electric-default-mode
|
||||
delete-selection-mode
|
||||
column-number-mode))
|
||||
blink-cursor-mode
|
||||
electric-pair-mode
|
||||
show-paren-mode
|
||||
global-so-long-mode
|
||||
minibuffer-depth-indicate-mode
|
||||
file-name-shadow-mode
|
||||
minibuffer-electric-default-mode
|
||||
delete-selection-mode
|
||||
;; column-number-mode
|
||||
))
|
||||
(when (fboundp enable-mode)
|
||||
(funcall enable-mode +1)))
|
||||
|
||||
(dolist (disable-mode '(tooltip-mode
|
||||
tool-bar-mode
|
||||
menu-bar-mode
|
||||
scroll-bar-mode
|
||||
horizontal-scroll-bar-mode))
|
||||
menu-bar-mode
|
||||
scroll-bar-mode
|
||||
horizontal-scroll-bar-mode))
|
||||
(when (fboundp disable-mode)
|
||||
(funcall disable-mode -1)))
|
||||
|
||||
|
||||
;;; Hooks
|
||||
|
||||
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
|
||||
(add-hook 'minibuffer-setup-hook 'cursor-intangible-mode)
|
||||
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
|
||||
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
|
||||
|
||||
(defun +auto-create-missing-dirs ()
|
||||
"Automatically create missing directories when finding a file."
|
||||
;; https://emacsredux.com/blog/2022/06/12/auto-create-missing-directories/
|
||||
(let ((target-dir (file-name-directory buffer-file-name)))
|
||||
(unless (file-exists-p target-dir)
|
||||
(make-directory target-dir t))))
|
||||
|
||||
(add-hook 'find-file-not-found-functions #'+auto-create-missing-dirs)
|
||||
|
||||
|
||||
;;; Better-default functions ...
|
||||
|
||||
(defun +cycle-spacing (&optional n preserve-nl-back mode)
|
||||
|
@ -220,6 +245,43 @@ kill without asking."
|
|||
(save-buffers-kill-emacs))
|
||||
(delete-frame nil :force)))
|
||||
|
||||
(defun +kill-word-backward-or-region (&optional arg backward-kill-word-fn)
|
||||
"Kill active region or ARG words backward.
|
||||
BACKWARD-KILL-WORD-FN is the function to call to kill a word
|
||||
backward. It defaults to `backward-kill-word'."
|
||||
(interactive "P")
|
||||
(call-interactively (if (region-active-p)
|
||||
#'kill-region
|
||||
(or backward-kill-word-fn #'backward-kill-word))))
|
||||
|
||||
(defun +backward-kill-word-wrapper (fn &optional arg)
|
||||
"Kill backward using FN until the beginning of a word, smartly.
|
||||
If point is on at the beginning of a line, kill the previous new
|
||||
line. If the only thing before point on the current line is
|
||||
whitespace, kill that whitespace.
|
||||
|
||||
With argument ARG: if ARG is a number, just call FN
|
||||
ARG times. Otherwise, just call FN."
|
||||
;; I want this to be a wrapper so that I can call other word-killing functions
|
||||
;; with it. It's *NOT* advice because those functions probably use
|
||||
;; `backward-kill-word' under the hood (looking at you, paredit), so advice
|
||||
;; will make things weird.
|
||||
(if (null arg)
|
||||
(cond
|
||||
((looking-back "^" 1)
|
||||
(let ((delete-active-region nil))
|
||||
(delete-backward-char 1)))
|
||||
((looking-back "^[ ]*")
|
||||
(delete-horizontal-space :backward-only))
|
||||
(t (call-interactively fn)))
|
||||
(funcall fn (if (listp arg) 1 arg))))
|
||||
|
||||
(defun +backward-kill-word (&optional arg)
|
||||
"Kill word backward using `backward-kill-word'.
|
||||
ARG is passed to `backward-kill-word'."
|
||||
(interactive "P")
|
||||
(+backward-kill-word-wrapper #'backward-kill-word arg))
|
||||
|
||||
;; ... and advice
|
||||
|
||||
;; Indent the region after a yank.
|
||||
|
@ -229,6 +291,7 @@ kill without asking."
|
|||
(advice-add #'yank :after #'+yank@indent)
|
||||
(advice-add #'yank-pop :after #'+yank@indent)
|
||||
|
||||
|
||||
;;; Bindings
|
||||
|
||||
;; I need to place these bindings under `+key-mode-map' so that they aren't
|
||||
|
@ -243,19 +306,17 @@ kill without asking."
|
|||
("C-s" . isearch-forward-regexp)
|
||||
("C-r" . isearch-backward-regexp)
|
||||
("C-M-s" . isearch-forward)
|
||||
("C-M-r" . isearch-backward)
|
||||
("M-u" . upcase-dwim)
|
||||
("M-l" . downcase-dwim)
|
||||
("M-c" . capitalize-dwim)))
|
||||
(define-key +key-mode-map (kbd (car binding)) (cdr binding)))
|
||||
("C-M-r" . isearch-backward)))
|
||||
(define-key (current-global-map) (kbd (car binding)) (cdr binding)))
|
||||
|
||||
|
||||
;;; Required libraries
|
||||
|
||||
(when (require 'uniquify nil :noerror)
|
||||
(setq-default uniquify-buffer-name-style 'forward
|
||||
uniquify-separator path-separator
|
||||
uniquify-after-kill-buffer-p t
|
||||
uniquify-ignore-buffers-re "^\\*"))
|
||||
uniquify-separator path-separator
|
||||
uniquify-after-kill-buffer-p t
|
||||
uniquify-ignore-buffers-re "^\\*"))
|
||||
|
||||
(when (require 'goto-addr)
|
||||
(if (fboundp 'global-goto-address-mode)
|
||||
|
@ -264,36 +325,40 @@ kill without asking."
|
|||
|
||||
(when (require 'recentf nil :noerror)
|
||||
(setq-default recentf-save-file (.etc "recentf.el")
|
||||
recentf-max-menu-items 100
|
||||
recentf-max-saved-items nil
|
||||
recentf-auto-cleanup 'mode)
|
||||
recentf-max-menu-items 100
|
||||
recentf-max-saved-items nil
|
||||
recentf-auto-cleanup 'mode)
|
||||
(add-to-list 'recentf-exclude .etc)
|
||||
(recentf-mode +1))
|
||||
|
||||
(when (require 'repeat nil :noerror)
|
||||
(setq-default repeat-exit-key "g"
|
||||
repeat-exit-timeout 5)
|
||||
(when (fboundp 'repeat-mode)
|
||||
;; `repeat-mode' is defined in repeat.el, which is an older library.
|
||||
(repeat-mode +1)))
|
||||
|
||||
(when (require 'savehist nil :noerror)
|
||||
(setq-default history-length t
|
||||
history-delete-duplicates t
|
||||
history-autosave-interval 60
|
||||
savehist-file (.etc "savehist.el"))
|
||||
history-delete-duplicates t
|
||||
history-autosave-interval 60
|
||||
savehist-file (.etc "savehist.el")
|
||||
;; Other variables --- don't truncate any of these.
|
||||
;; `add-to-history' uses the values of these variables unless
|
||||
;; they're nil, in which case it falls back to `history-length'.
|
||||
kill-ring-max 100
|
||||
mark-ring-max 100
|
||||
global-mark-ring-max 100
|
||||
regexp-search-ring-max 100
|
||||
search-ring-max 100
|
||||
kmacro-ring-max 100
|
||||
eww-history-limit 100)
|
||||
(dolist (var '(extended-command-history
|
||||
global-mark-ring
|
||||
kill-ring
|
||||
regexp-search-ring
|
||||
search-ring
|
||||
mark-ring))
|
||||
global-mark-ring
|
||||
mark-ring
|
||||
kill-ring
|
||||
kmacro-ring
|
||||
regexp-search-ring
|
||||
search-ring))
|
||||
(add-to-list 'savehist-additional-variables var))
|
||||
(savehist-mode +1))
|
||||
|
||||
(when (require 'saveplace nil :noerror)
|
||||
(setq-default save-place-file (.etc "places.el")
|
||||
save-place-forget-unreadable-files (eq system-type 'gnu/linux))
|
||||
save-place-forget-unreadable-files (eq system-type 'gnu/linux))
|
||||
(save-place-mode +1))
|
||||
|
||||
;; (when (require 'tramp)
|
||||
|
@ -303,5 +368,17 @@ kill without asking."
|
|||
;; (add-to-list 'tramp-default-proxies-alist
|
||||
;; '((regexp-quote (system-name)) nil nil)))
|
||||
|
||||
|
||||
;;; Newer features
|
||||
;; These aren't in older version of Emacs, but they're so nice.
|
||||
|
||||
(when (fboundp 'repeat-mode)
|
||||
(setq-default repeat-exit-key "g"
|
||||
repeat-exit-timeout 5)
|
||||
(repeat-mode +1))
|
||||
|
||||
(when (fboundp 'pixel-scroll-precision-mode)
|
||||
(pixel-scroll-precision-mode +1))
|
||||
|
||||
(provide '+emacs)
|
||||
;;; +emacs.el ends here
|
||||
|
|
28
lisp/+embark.el
Normal file
28
lisp/+embark.el
Normal file
|
@ -0,0 +1,28 @@
|
|||
;;; +embark.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; https://github.com/oantolin/embark/wiki/Additional-Actions
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'embark)
|
||||
|
||||
(embark-define-keymap embark-straight-map
|
||||
("u" straight-visit-package-website)
|
||||
("r" straight-get-recipe)
|
||||
("i" straight-use-package)
|
||||
("c" straight-check-package)
|
||||
("F" straight-pull-package)
|
||||
("f" straight-fetch-package)
|
||||
("p" straight-push-package)
|
||||
("n" straight-normalize-package)
|
||||
("m" straight-merge-package))
|
||||
|
||||
(add-to-list 'embark-keymap-alist '(straight . embark-straight-map))
|
||||
|
||||
(with-eval-after-load 'marginalia
|
||||
(add-to-list 'marginalia-prompt-categories '("recipe\\|package" . straight)))
|
||||
|
||||
(provide '+embark)
|
||||
;;; +embark.el ends here
|
46
lisp/+emms.el
Normal file
46
lisp/+emms.el
Normal file
|
@ -0,0 +1,46 @@
|
|||
;;; +emms.el --- EMMS customizations -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'emms-player-mpv)
|
||||
(require 'el-patch)
|
||||
|
||||
;; https://lists.gnu.org/archive/html/emms-help/2022-01/msg00006.html
|
||||
(el-patch-feature emms-player-mpv)
|
||||
(with-eval-after-load 'emms-player-mpv
|
||||
(el-patch-defun emms-player-mpv-start (track)
|
||||
(setq emms-player-mpv-stopped nil)
|
||||
(emms-player-mpv-proc-playing nil)
|
||||
(let
|
||||
((track-name (emms-track-get track 'name))
|
||||
(track-is-playlist (memq (emms-track-get track 'type)
|
||||
'(streamlist playlist))))
|
||||
(if (emms-player-mpv-ipc-fifo-p)
|
||||
(progn
|
||||
;; ipc-stop is to clear any buffered commands
|
||||
(emms-player-mpv-ipc-stop)
|
||||
(emms-player-mpv-proc-init (if track-is-playlist "--playlist" "--")
|
||||
track-name)
|
||||
(emms-player-started emms-player-mpv))
|
||||
(let*
|
||||
((play-cmd
|
||||
`(batch
|
||||
((,(el-patch-swap
|
||||
(if track-is-playlist 'loadlist 'loadfile)
|
||||
'loadfile)
|
||||
,track-name replace))
|
||||
((set pause no))))
|
||||
(start-func
|
||||
;; Try running play-cmd and retry it on connection failure, e.g. if mpv died
|
||||
(apply-partially 'emms-player-mpv-cmd play-cmd
|
||||
(lambda (_mpv-data mpv-error)
|
||||
(when (eq mpv-error 'connection-error)
|
||||
(emms-player-mpv-cmd play-cmd))))))
|
||||
(if emms-player-mpv-ipc-stop-command
|
||||
(setq emms-player-mpv-ipc-stop-command start-func)
|
||||
(funcall start-func)))))))
|
||||
|
||||
(provide '+emms)
|
||||
;;; +emms.el ends here
|
|
@ -25,11 +25,37 @@ any directory proferred by `consult-dir'."
|
|||
|
||||
;;; Start and quit
|
||||
|
||||
;; from https://old.reddit.com/r/emacs/comments/1zkj2d/advanced_usage_of_eshell/
|
||||
(defun +eshell-here ()
|
||||
"Go to eshell and set current directory to current buffer's."
|
||||
;; consider: make a new eshell buffer when given a prefix argument.
|
||||
(interactive)
|
||||
(let ((dir (file-name-directory (or (buffer-file-name)
|
||||
default-directory))))
|
||||
(eshell)
|
||||
(eshell/pushd ".")
|
||||
(cd dir)
|
||||
(goto-char (point-max))
|
||||
(eshell-kill-input)
|
||||
(eshell-send-input)
|
||||
(setq-local scroll-margin 0)
|
||||
(recenter 0)))
|
||||
|
||||
(defun +eshell-quit-or-delete-char (arg)
|
||||
"Delete the character to the right, or quit eshell on an empty line."
|
||||
(interactive "p")
|
||||
(if (and (eolp) (looking-back eshell-prompt-regexp))
|
||||
(eshell-life-is-too-much)
|
||||
(progn (eshell-life-is-too-much)
|
||||
(when (and (<= 1 (count-windows))
|
||||
;; TODO: This is not what I want. What I really want is
|
||||
;; for an eshell-only frame (i.e., called from a
|
||||
;; keybind) to delete itself, but a regular Emacs frame
|
||||
;; with Eshell inside to stick around. I think I'll
|
||||
;; need to make a frame-local (?) variable for that to
|
||||
;; work.
|
||||
(> (length (frame-list)) 2)
|
||||
server-process)
|
||||
(delete-frame)))
|
||||
(delete-forward-char arg)))
|
||||
|
||||
;;; Insert previous arguments
|
||||
|
@ -76,5 +102,25 @@ any directory proferred by `consult-dir'."
|
|||
(add-hook 'eshell-post-command-hook #'eshell-record-args nil t)
|
||||
(remove-hook 'eshell-post-command-hook #'eshell-record-args t)))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro +eshell-eval-after-load (&rest forms)
|
||||
"Execute FORMS after Eshell is loaded.
|
||||
If Eshell is already loaded in the session, immediately execute
|
||||
forms.
|
||||
|
||||
I wrote this because Eshell doesn't properly do loading or
|
||||
something, it's really annoying to work with."
|
||||
(declare (indent 0))
|
||||
`(progn
|
||||
(defun +eshell@setup ()
|
||||
"Setup the Eshell session."
|
||||
,@forms)
|
||||
(when (featurep 'eshell)
|
||||
`(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when (derived-mode-p 'eshell-mode)
|
||||
(+eshell@setup)))))
|
||||
(add-hook 'eshell-mode-hook #'+eshell@setup)))
|
||||
|
||||
(provide '+eshell)
|
||||
;;; +eshell.el ends here
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
|
||||
(defun +eww-bookmark-setup ()
|
||||
"Setup eww bookmark integration."
|
||||
(setq-local bookmark-make-record-function #'eww-bookmark--make))
|
||||
(setq-local bookmark-make-record-function #'+eww-bookmark--make))
|
||||
|
||||
(provide '+eww)
|
||||
;;; +eww.el ends here
|
||||
|
|
24
lisp/+expand-region.el
Normal file
24
lisp/+expand-region.el
Normal file
|
@ -0,0 +1,24 @@
|
|||
;;; +expand-region.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Because of `wrap-region', I can't use `expand-region-fast-keys-enabled'. So
|
||||
;; instead of that, I'm adding this to the binding to C--, but I also want to be
|
||||
;; able to use the negative argument. So there's this.
|
||||
(defun +er/contract-or-negative-argument (arg)
|
||||
"Contract the region if the last command expanded it.
|
||||
Otherwise, pass the ARG as a negative argument."
|
||||
(interactive "p")
|
||||
(cond ((memq last-command '(er/expand-region
|
||||
er/contract-region
|
||||
+er/contract-or-negative-argument))
|
||||
|
||||
(er/contract-region arg))
|
||||
(t (call-interactively #'negative-argument))))
|
||||
|
||||
(provide '+expand-region)
|
||||
;;; +expand-region.el ends here
|
46
lisp/+finger.el
Normal file
46
lisp/+finger.el
Normal file
|
@ -0,0 +1,46 @@
|
|||
;;; +finger.el --- Finger bugfix -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; `net-utils' defines `finger', which purportedly consults
|
||||
;; `finger-X.500-host-regexps' to determine what hosts to only send a username
|
||||
;; to. I've found that that is not the case, and so I've patched it. At some
|
||||
;; point I'll submit this to Emacs itself.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'net-utils) ; this requires everything else I'll need.
|
||||
(require 'seq)
|
||||
|
||||
(defun finger (user host)
|
||||
"Finger USER on HOST.
|
||||
This command uses `finger-X.500-host-regexps'
|
||||
and `network-connection-service-alist', which see."
|
||||
;; One of those great interactive statements that's actually
|
||||
;; longer than the function call! The idea is that if the user
|
||||
;; uses a string like "pbreton@cs.umb.edu", we won't ask for the
|
||||
;; host name. If we don't see an "@", we'll prompt for the host.
|
||||
(interactive
|
||||
(let* ((answer (read-from-minibuffer "Finger User: "
|
||||
(net-utils-url-at-point)))
|
||||
(index (string-match (regexp-quote "@") answer)))
|
||||
(if index
|
||||
(list (substring answer 0 index)
|
||||
(substring answer (1+ index)))
|
||||
(list answer
|
||||
(read-from-minibuffer "At Host: "
|
||||
(net-utils-machine-at-point))))))
|
||||
(let* ((user-and-host (concat user "@" host))
|
||||
(process-name (concat "Finger [" user-and-host "]"))
|
||||
(regexps finger-X.500-host-regexps)
|
||||
) ;; found
|
||||
(when (seq-some (lambda (r) (string-match-p r host)) regexps)
|
||||
(setq user-and-host user))
|
||||
(run-network-program
|
||||
process-name
|
||||
host
|
||||
(cdr (assoc 'finger network-connection-service-alist))
|
||||
user-and-host)))
|
||||
|
||||
(provide '+finger)
|
||||
;;; +finger.el ends here
|
24
lisp/+flyspell-correct.el
Normal file
24
lisp/+flyspell-correct.el
Normal file
|
@ -0,0 +1,24 @@
|
|||
;;; +flyspell-correct.el --- -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'flyspell-correct)
|
||||
|
||||
(defun +flyspell-correct-buffer (&optional prefix)
|
||||
"Run `flyspell-correct-wrapper' on all misspelled words in the buffer.
|
||||
With PREFIX, prompt to change the current dictionary."
|
||||
(interactive "P")
|
||||
(flyspell-buffer)
|
||||
(when prefix
|
||||
(let ((current-prefix-arg nil))
|
||||
(call-interactively #'ispell-change-dictionary)))
|
||||
(+with-message "Checking spelling"
|
||||
(flyspell-correct-move (point-min) :forward :rapid)))
|
||||
|
||||
(defun +flyspell-correct-buffer-h (&rest _)
|
||||
"Run `+flyspell-correct-buffer'.
|
||||
This is suitable for placement in a hook."
|
||||
(+flyspell-correct-buffer))
|
||||
|
||||
(provide '+flyspell-correct)
|
||||
;;; +flyspell-correct.el ends here
|
17
lisp/+god-mode.el
Normal file
17
lisp/+god-mode.el
Normal file
|
@ -0,0 +1,17 @@
|
|||
;;; +god-mode.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defun +god-mode-insert ()
|
||||
"Leave `god-local-mode' at point."
|
||||
(interactive)
|
||||
(god-local-mode -1))
|
||||
|
||||
(defun +god-mode-append ()
|
||||
"Leave `god-local-mode' after point."
|
||||
(interactive)
|
||||
(forward-char 1)
|
||||
(god-local-mode -1))
|
||||
|
||||
(provide '+god-mode)
|
||||
;;; +god-mode.el ends here
|
107
lisp/+init.el
107
lisp/+init.el
|
@ -28,62 +28,79 @@ within that group, forms with a HEAD of `:require' are sorted
|
|||
first, and `:straight' HEADs are sorted last. All other forms
|
||||
are sorted lexigraphically."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(widen)
|
||||
(+lisp-sort-sexps
|
||||
(point-min) (point-max)
|
||||
;; Key function
|
||||
nil
|
||||
;; Sort function
|
||||
(lambda (s1 s2)
|
||||
(let ((s1 (cdr s1)) (s2 (cdr s2)))
|
||||
(cond
|
||||
;; Sort everything /not/ `setup' /before/ `setup'
|
||||
((and (+init--sexp-setup-p s1)
|
||||
(not (+init--sexp-setup-p s2)))
|
||||
nil)
|
||||
((and (+init--sexp-setup-p s2)
|
||||
(not (+init--sexp-setup-p s1)))
|
||||
t)
|
||||
;; otherwise...
|
||||
(t (let ((s1-straight (+init--sexp-setup-p s1 :straight))
|
||||
(s2-straight (+init--sexp-setup-p s2 :straight))
|
||||
(s1-require (+init--sexp-setup-p s1 :require))
|
||||
(s2-require (+init--sexp-setup-p s2 :require)))
|
||||
(cond
|
||||
;; `:straight' setups have extra processing
|
||||
((and s1-straight s2-straight)
|
||||
(let* ((r (rx (: ":straight" (? "-when") (* space) (? "("))))
|
||||
(s1 (replace-regexp-in-string r "" s1))
|
||||
(s2 (replace-regexp-in-string r "" s2)))
|
||||
(string< s1 s2)))
|
||||
;; `:require' setups go first
|
||||
((and s1-require (not s2-require)) t)
|
||||
((and s2-require (not s1-require)) nil)
|
||||
;; `:straight' setups go last
|
||||
((and s1-straight (not s2-straight)) nil)
|
||||
((and s2-straight (not s1-straight)) t)
|
||||
;; otherwise, sort lexigraphically
|
||||
(t (string< s1 s2))))))))))))
|
||||
;; I have to make my own "version" of `save-excursion', since the mark and
|
||||
;; point are lost (I think that's the problem) when sorting the buffer.
|
||||
(let* ((current-point (point))
|
||||
(current-defun (beginning-of-defun))
|
||||
(defun-point (- current-point (point)))
|
||||
(current-defun-re (buffer-substring-no-properties (line-beginning-position)
|
||||
(line-end-position))))
|
||||
(widen) ; It makes no sense to `save-restriction'
|
||||
(+lisp-sort-sexps
|
||||
(point-min) (point-max)
|
||||
;; Key function
|
||||
nil
|
||||
;; Sort function
|
||||
(lambda (s1 s2)
|
||||
(let ((s1 (cdr s1)) (s2 (cdr s2)))
|
||||
(cond
|
||||
;; Sort everything /not/ `setup' /before/ `setup'
|
||||
((and (+init--sexp-setup-p s1)
|
||||
(not (+init--sexp-setup-p s2)))
|
||||
nil)
|
||||
((and (+init--sexp-setup-p s2)
|
||||
(not (+init--sexp-setup-p s1)))
|
||||
t)
|
||||
;; otherwise...
|
||||
(t (let ((s1-straight (+init--sexp-setup-p s1 :straight))
|
||||
(s2-straight (+init--sexp-setup-p s2 :straight))
|
||||
(s1-require (+init--sexp-setup-p s1 :require))
|
||||
(s2-require (+init--sexp-setup-p s2 :require)))
|
||||
(cond
|
||||
;; `:straight' setups have extra processing
|
||||
((and s1-straight s2-straight)
|
||||
(let* ((r (rx (: ":straight" (? "-when") (* space) (? "("))))
|
||||
(s1 (replace-regexp-in-string r "" s1))
|
||||
(s2 (replace-regexp-in-string r "" s2)))
|
||||
(string< s1 s2)))
|
||||
;; `:require' setups go first
|
||||
((and s1-require (not s2-require)) t)
|
||||
((and s2-require (not s1-require)) nil)
|
||||
;; `:straight' setups go last
|
||||
((and s1-straight (not s2-straight)) nil)
|
||||
((and s2-straight (not s1-straight)) t)
|
||||
;; otherwise, sort lexigraphically
|
||||
(t (string< s1 s2)))))))))
|
||||
;; Return to original point relative to the defun we were in
|
||||
(ignore-errors (goto-char (point-min))
|
||||
(re-search-forward current-defun-re)
|
||||
(beginning-of-defun)
|
||||
(goto-char (+ (point) defun-point)))))
|
||||
|
||||
(defun +init-sort-then-save ()
|
||||
"Sort init.el, then save it."
|
||||
(interactive)
|
||||
(+init-sort)
|
||||
(save-buffer))
|
||||
(if (fboundp #'user-save-buffer)
|
||||
(user-save-buffer)
|
||||
(save-buffer)))
|
||||
|
||||
;;; Add `setup' forms to `imenu-generic-expression'
|
||||
|
||||
(defun +init-add-setup-to-imenu ()
|
||||
"Recognize `setup' forms in `imenu'."
|
||||
;; `imenu-generic-expression' automatically becomes buffer-local when set
|
||||
(setf (alist-get "Setup" imenu-generic-expression nil nil 'string-equal)
|
||||
(list
|
||||
(rx (: bol (* space)
|
||||
"(setup" (+ space)
|
||||
(group (? "(") (* nonl))))
|
||||
1)))
|
||||
(setf (alist-get "Setup" imenu-generic-expression nil nil #'equal)
|
||||
(list
|
||||
(rx (: "(setup" (+ space)
|
||||
(group (? "(") (* nonl))))
|
||||
1))
|
||||
(when (boundp 'consult-imenu-config)
|
||||
(setf (alist-get ?s
|
||||
(plist-get
|
||||
(alist-get 'emacs-lisp-mode consult-imenu-config)
|
||||
:types))
|
||||
'("Setup"))))
|
||||
|
||||
;;; Major mode
|
||||
|
||||
|
|
97
lisp/+ispell.el
Normal file
97
lisp/+ispell.el
Normal file
|
@ -0,0 +1,97 @@
|
|||
;;; +ispell.el --- Customizations for `ispell' -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'seq)
|
||||
|
||||
;; Utility function TODO: move elsewhere
|
||||
(defun +ispell-append-removing-duplicates (&rest lists)
|
||||
"Append LISTS, removing duplicates from the result.
|
||||
Any keyword arguments to `cl-remove-duplicates' should come
|
||||
before the LISTS."
|
||||
(let (cl-remove-duplicates-args)
|
||||
(while (keywordp (car lists))
|
||||
(push (pop lists) cl-remove-duplicates-args)
|
||||
(push (pop lists) cl-remove-duplicates-args))
|
||||
(apply #'cl-remove-duplicates (apply #'append lists)
|
||||
(nreverse cl-remove-duplicates-args))))
|
||||
|
||||
;;; Ispell in .dir-locals
|
||||
|
||||
;; Let Emacs know a list of strings is safe
|
||||
(defun +ispell-safe-local-p (list)
|
||||
(and (listp list)
|
||||
(seq-every-p #'stringp list)))
|
||||
|
||||
;; Can I instruct ispell to insert LocalWords in a different file?
|
||||
;; https://emacs.stackexchange.com/q/31396/2264
|
||||
|
||||
;; How can I move all my file-local LocalWords to .dir-locals.el?
|
||||
;; https://emacs.stackexchange.com/q/31419
|
||||
|
||||
;; Adapted from ispell.el:ispell-buffer-local-words
|
||||
(defun +ispell-buffer-local-words-list ()
|
||||
(let (words)
|
||||
(or ispell-buffer-local-name
|
||||
(setq ispell-buffer-local-name (buffer-name)))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (search-forward ispell-words-keyword nil t)
|
||||
(let ((end (point-at-eol))
|
||||
(ispell-casechars (ispell-get-casechars))
|
||||
string)
|
||||
(while (re-search-forward " *\\([^ ]+\\)" end t)
|
||||
(setq string (match-string-no-properties 1))
|
||||
(if (and (< 1 (length string))
|
||||
(equal 0 (string-match ispell-casechars string)))
|
||||
(push string words))))))
|
||||
words))
|
||||
|
||||
;;;###autoload
|
||||
(defun +ispell-move-buffer-words-to-dir-locals (&optional arg)
|
||||
"Move the current buffer-local words to .dir-locals.el.
|
||||
This function prompts the user to save .dir-locals.el, unless
|
||||
prefix ARG is non-nil; then it just saves them."
|
||||
(interactive "P")
|
||||
(unless (buffer-file-name)
|
||||
(user-error "Buffer not attached to file"))
|
||||
(hack-dir-local-variables)
|
||||
(let ((print-level nil)
|
||||
(print-length nil))
|
||||
(when-let ((new-words (cl-remove-if (lambda (el) (eq el '\.\.\.)) ; XXX: NO IDEA
|
||||
; where this came from
|
||||
(+ispell-append-removing-duplicates
|
||||
:test #'string=
|
||||
ispell-buffer-session-localwords
|
||||
(alist-get 'ispell-buffer-session-localwords
|
||||
dir-local-variables-alist)
|
||||
(alist-get 'ispell-buffer-session-localwords
|
||||
file-local-variables-alist)
|
||||
(+ispell-buffer-local-words-list)))))
|
||||
(save-excursion
|
||||
(add-dir-local-variable
|
||||
major-mode
|
||||
'ispell-buffer-session-localwords
|
||||
(setq ispell-buffer-session-localwords
|
||||
new-words))
|
||||
(when (or arg
|
||||
(y-or-n-p "Save .dir-locals.el?"))
|
||||
(save-buffer))
|
||||
(bury-buffer))
|
||||
(or ispell-buffer-local-name
|
||||
(setq ispell-buffer-local-name (buffer-name)))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (search-forward ispell-words-keyword nil t)
|
||||
(delete-region (point-at-bol) (1+ (point-at-eol))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +ispell-move-buffer-words-to-dir-locals-hook ()
|
||||
"Convenience function for binding to a hook."
|
||||
(+ispell-move-buffer-words-to-dir-locals t))
|
||||
|
||||
(provide '+ispell)
|
||||
;;; +ispell.el ends here
|
278
lisp/+jabber.el
Normal file
278
lisp/+jabber.el
Normal file
|
@ -0,0 +1,278 @@
|
|||
;;; +jabber.el --- Customizations for jabber.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Most changes I want to PR and contribute, but a few don't make sense to
|
||||
;; contribute upstream, at least not now.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'jabber)
|
||||
(require 'tracking)
|
||||
|
||||
(defgroup +jabber nil
|
||||
"Extra jabber.el customizations."
|
||||
:group 'jabber)
|
||||
|
||||
(defcustom +jabber-ws-prefix 0
|
||||
"Width to pad left side of chats."
|
||||
:type 'string)
|
||||
|
||||
(defcustom +jabber-pre-prompt "\n"
|
||||
"String to put before the prompt."
|
||||
:type 'string)
|
||||
|
||||
(defvar +jabber-tracking-show-p #'jabber-activity-show-p-default
|
||||
"Function that checks if the given JID should be shown in the mode line.
|
||||
This does the same as `jabber-activity-show-p', but for the
|
||||
`tracking-mode' mode-line.")
|
||||
|
||||
(defun +jabber-tracking-add (from buffer text proposed-alert)
|
||||
"ADVICE to add jabber buffers to `tracking-buffers'."
|
||||
(when (funcall +jabber-tracking-show-p from)
|
||||
(tracking-add-buffer buffer 'jabber-activity-face)))
|
||||
|
||||
(defun +jabber-tracking-add-muc (nick group buffer text proposed-alert)
|
||||
"ADVICE to add jabber MUC buffers to `tracking-buffers'."
|
||||
(when (funcall +jabber-tracking-show-p group)
|
||||
(tracking-add-buffer buffer 'jabber-activity-face)))
|
||||
|
||||
;;; Hiding presence messages:
|
||||
;; https://paste.sr.ht/~hdasch/f0ad09fbcd08e940a4fda71c2f40abc1c4efd45f
|
||||
|
||||
;; Tame MUC presence notifications.
|
||||
|
||||
;; This patch hides or applies a face to MUC presence notifications in
|
||||
;; the MUC chat buffer. To control its behavior, customize
|
||||
;; ’jabber-muc-decorate-presence-patterns’. By default it does nothing.
|
||||
|
||||
;; ’jabber-muc-decorate-presence-patterns’ is a list of pairs consisting
|
||||
;; of a regular expression and a either a face or ‘nil’. If a the
|
||||
;; regular expression matches a presence notification, then either:
|
||||
|
||||
;; - the specified face is applied to the notification message
|
||||
;; - or if the second value of the pair is nil, the notification is
|
||||
;; discarded
|
||||
|
||||
;; If no regular expression in the list of pairs matches the notification
|
||||
;; message, the message is displayed unchanged.
|
||||
|
||||
;; For example, the customization:
|
||||
|
||||
;; '(jabber-muc-decorate-presence-patterns
|
||||
;; '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$")
|
||||
;; ("." . jabber-muc-presence-dim)))
|
||||
|
||||
;; hides participant enter/leave notifications. It also diminishes other
|
||||
;; presence notification messages to make it easier to distinguish
|
||||
;; between conversation and notifications.
|
||||
|
||||
(defface jabber-muc-presence-dim
|
||||
'((t (:foreground "dark grey" :weight light :slant italic)))
|
||||
"face for diminished presence notifications.")
|
||||
|
||||
(defcustom jabber-muc-decorate-presence-patterns nil
|
||||
"List of regular expressions and face pairs.
|
||||
When a presence notification matches a pattern, display it with
|
||||
associated face. Ignore notification if face is ‘nil’."
|
||||
:type '(repeat
|
||||
:tag "Patterns"
|
||||
(cons :format "%v"
|
||||
(regexp :tag "Regexp")
|
||||
(choice
|
||||
(const :tag "Ignore" nil)
|
||||
(face :tag "Face" :value jabber-muc-presence-dim))))
|
||||
:group 'jabber-alerts)
|
||||
|
||||
(defun jabber-muc-maybe-decorate-presence (node)
|
||||
"Filter presence notifications."
|
||||
(cl-destructuring-bind (key msg &key time) node
|
||||
(let* ((match (cl-find-if
|
||||
(lambda (pair)
|
||||
(string-match (car pair) msg))
|
||||
jabber-muc-decorate-presence-patterns))
|
||||
(face (cdr-safe match)))
|
||||
(if match
|
||||
(when face
|
||||
(jabber-maybe-print-rare-time
|
||||
(ewoc-enter-last
|
||||
jabber-chat-ewoc
|
||||
(list key
|
||||
(propertize msg 'face face)
|
||||
:time time))))
|
||||
(jabber-maybe-print-rare-time
|
||||
(ewoc-enter-last jabber-chat-ewoc node))))))
|
||||
|
||||
(defun jabber-muc-process-presence (jc presence)
|
||||
(let* ((from (jabber-xml-get-attribute presence 'from))
|
||||
(type (jabber-xml-get-attribute presence 'type))
|
||||
(x-muc (cl-find-if
|
||||
(lambda (x) (equal (jabber-xml-get-attribute x 'xmlns)
|
||||
"http://jabber.org/protocol/muc#user"))
|
||||
(jabber-xml-get-children presence 'x)))
|
||||
(group (jabber-jid-user from))
|
||||
(nickname (jabber-jid-resource from))
|
||||
(symbol (jabber-jid-symbol from))
|
||||
(our-nickname (gethash symbol jabber-pending-groupchats))
|
||||
(item (car (jabber-xml-get-children x-muc 'item)))
|
||||
(actor (jabber-xml-get-attribute (car (jabber-xml-get-children item 'actor)) 'jid))
|
||||
(reason (car (jabber-xml-node-children (car (jabber-xml-get-children item 'reason)))))
|
||||
(error-node (car (jabber-xml-get-children presence 'error)))
|
||||
(status-codes (if error-node
|
||||
(list (jabber-xml-get-attribute error-node 'code))
|
||||
(mapcar
|
||||
(lambda (status-element)
|
||||
(jabber-xml-get-attribute status-element 'code))
|
||||
(jabber-xml-get-children x-muc 'status)))))
|
||||
;; handle leaving a room
|
||||
(cond
|
||||
((or (string= type "unavailable") (string= type "error"))
|
||||
;; error from room itself? or are we leaving?
|
||||
(if (or (null nickname)
|
||||
(member "110" status-codes)
|
||||
(string= nickname our-nickname))
|
||||
;; Assume that an error means that we were thrown out of the
|
||||
;; room...
|
||||
(let* ((leavingp t)
|
||||
(message (cond
|
||||
((string= type "error")
|
||||
(cond
|
||||
;; ...except for certain cases.
|
||||
((or (member "406" status-codes)
|
||||
(member "409" status-codes))
|
||||
(setq leavingp nil)
|
||||
(concat "Nickname change not allowed"
|
||||
(when error-node
|
||||
(concat ": " (jabber-parse-error error-node)))))
|
||||
(t
|
||||
(concat "Error entering room"
|
||||
(when error-node
|
||||
(concat ": " (jabber-parse-error error-node)))))))
|
||||
((member "301" status-codes)
|
||||
(concat "You have been banned"
|
||||
(when actor (concat " by " actor))
|
||||
(when reason (concat " - '" reason "'"))))
|
||||
((member "307" status-codes)
|
||||
(concat "You have been kicked"
|
||||
(when actor (concat " by " actor))
|
||||
(when reason (concat " - '" reason "'"))))
|
||||
(t
|
||||
"You have left the chatroom"))))
|
||||
(when leavingp
|
||||
(jabber-muc-remove-groupchat group))
|
||||
;; If there is no buffer for this groupchat, don't bother
|
||||
;; creating one just to tell that user left the room.
|
||||
(let ((buffer (get-buffer (jabber-muc-get-buffer group))))
|
||||
(if buffer
|
||||
(with-current-buffer buffer
|
||||
(jabber-muc-maybe-decorate-presence
|
||||
(list (if (string= type "error")
|
||||
:muc-error
|
||||
:muc-notice)
|
||||
message
|
||||
:time (current-time)))))
|
||||
(message "%s: %s" (jabber-jid-displayname group) message))))
|
||||
;; or someone else?
|
||||
(let* ((plist (jabber-muc-participant-plist group nickname))
|
||||
(jid (plist-get plist 'jid))
|
||||
(name (concat nickname
|
||||
(when jid
|
||||
(concat " <"
|
||||
(jabber-jid-user jid)
|
||||
">")))))
|
||||
(jabber-muc-remove-participant group nickname)
|
||||
(with-current-buffer (jabber-muc-create-buffer jc group)
|
||||
(jabber-muc-maybe-decorate-presence
|
||||
(list :muc-notice
|
||||
(cond
|
||||
((member "301" status-codes)
|
||||
(concat name " has been banned"
|
||||
(when actor (concat " by " actor))
|
||||
(when reason (concat " - '" reason "'"))))
|
||||
((member "307" status-codes)
|
||||
(concat name " has been kicked"
|
||||
(when actor (concat " by " actor))
|
||||
(when reason (concat " - '" reason "'"))))
|
||||
((member "303" status-codes)
|
||||
(concat name " changes nickname to "
|
||||
(jabber-xml-get-attribute item 'nick)))
|
||||
(t
|
||||
(concat name " has left the chatroom")))
|
||||
:time (current-time))))))
|
||||
(t
|
||||
;; someone is entering
|
||||
|
||||
(when (or (member "110" status-codes) (string= nickname our-nickname))
|
||||
;; This is us. We just succeeded in entering the room.
|
||||
;;
|
||||
;; The MUC server is supposed to send a 110 code whenever this
|
||||
;; is our presence ("self-presence"), but at least one
|
||||
;; (ejabberd's mod_irc) doesn't, so check the nickname as well.
|
||||
;;
|
||||
;; This check might give incorrect results if the server
|
||||
;; changed our nickname to avoid collision with an existing
|
||||
;; participant, but even in this case the window where we have
|
||||
;; incorrect information should be very small, as we should be
|
||||
;; getting our own 110+210 presence shortly.
|
||||
(let ((whichgroup (assoc group *jabber-active-groupchats*)))
|
||||
(if whichgroup
|
||||
(setcdr whichgroup nickname)
|
||||
(add-to-list '*jabber-active-groupchats* (cons group nickname))))
|
||||
;; The server may have changed our nick. Record the new one.
|
||||
(puthash symbol nickname jabber-pending-groupchats))
|
||||
|
||||
;; Whoever enters, we create a buffer (if it didn't already
|
||||
;; exist), and print a notice. This is where autojoined MUC
|
||||
;; rooms have buffers created for them. We also remember some
|
||||
;; metadata.
|
||||
(let ((old-plist (jabber-muc-participant-plist group nickname))
|
||||
(new-plist (jabber-muc-parse-affiliation x-muc)))
|
||||
(jabber-muc-modify-participant group nickname new-plist)
|
||||
(let ((report (jabber-muc-report-delta nickname old-plist new-plist
|
||||
reason actor)))
|
||||
(when report
|
||||
(with-current-buffer (jabber-muc-create-buffer jc group)
|
||||
(jabber-muc-maybe-decorate-presence
|
||||
(list :muc-notice report
|
||||
:time (current-time)))
|
||||
;; Did the server change our nick?
|
||||
(when (member "210" status-codes)
|
||||
(ewoc-enter-last
|
||||
jabber-chat-ewoc
|
||||
(list :muc-notice
|
||||
(concat "Your nick was changed to " nickname " by the server")
|
||||
:time (current-time))))
|
||||
;; Was this room just created? If so, it's a locked
|
||||
;; room. Notify the user.
|
||||
(when (member "201" status-codes)
|
||||
(ewoc-enter-last
|
||||
jabber-chat-ewoc
|
||||
(list :muc-notice
|
||||
(with-temp-buffer
|
||||
(insert "This room was just created, and is locked to other participants.\n"
|
||||
"To unlock it, ")
|
||||
(insert-text-button
|
||||
"configure the room"
|
||||
'action (apply-partially 'call-interactively 'jabber-muc-get-config))
|
||||
(insert " or ")
|
||||
(insert-text-button
|
||||
"accept the default configuration"
|
||||
'action (apply-partially 'call-interactively 'jabber-muc-instant-config))
|
||||
(insert ".")
|
||||
(buffer-string))
|
||||
:time (current-time))))))))))))
|
||||
|
||||
(defun +jabber-colors-update (&optional buffer)
|
||||
"Update jabber colors in BUFFER, defaulting to the current."
|
||||
(with-current-buffer (or buffer (current-buffer))
|
||||
(when jabber-buffer-connection
|
||||
(setq jabber-muc-participant-colors nil)
|
||||
(cond (jabber-chatting-with
|
||||
(jabber-chat-create-buffer jabber-buffer-connection
|
||||
jabber-chatting-with))
|
||||
(jabber-group
|
||||
(jabber-muc-create-buffer jabber-buffer-connection
|
||||
jabber-group))))))
|
||||
|
||||
(provide '+jabber)
|
||||
;;; +jabber.el ends here
|
14
lisp/+key.el
14
lisp/+key.el
|
@ -19,9 +19,9 @@
|
|||
|
||||
;; I need to define this map before the proper mode map.
|
||||
(defvar +key-leader-map (let ((map (make-sparse-keymap))
|
||||
(c-z (global-key-binding "\C-z")))
|
||||
(define-key map "\C-z" c-z)
|
||||
map)
|
||||
(c-z (global-key-binding "\C-z")))
|
||||
;;(define-key map "\C-z" c-z)
|
||||
map)
|
||||
"A leader keymap under the \"C-z\" bind.")
|
||||
|
||||
;; http://xahlee.info/emacs/emacs/emacs_menu_app_keys.html and
|
||||
|
@ -83,15 +83,15 @@
|
|||
`(define-key +key-mode-map ,key ,command))
|
||||
:documentation "Bind KEY to COMMAND in `+key-mode-map'."
|
||||
:debug '(form sexp)
|
||||
:ensure '(kbd func)
|
||||
:ensure '(kbd nil)
|
||||
:repeatable t)
|
||||
|
||||
|
||||
(setup-define :+leader
|
||||
(lambda (key command)
|
||||
`(define-key +key-leader-map ,key ,command))
|
||||
:documentation "Bind KEY to COMMAND in `+key-leader-map'."
|
||||
:debug '(form sexp)
|
||||
:ensure '(kbd func)
|
||||
:ensure '(kbd nil)
|
||||
:repeatable t)
|
||||
|
||||
(setup-define :+menu
|
||||
|
@ -99,7 +99,7 @@
|
|||
`(define-key +key-menu-map ,key ,command))
|
||||
:documentation "Bind KEY to COMMAND in `+key-leader-map'."
|
||||
:debug '(form sexp)
|
||||
:ensure '(kbd func)
|
||||
:ensure '(kbd nil)
|
||||
:repeatable t))
|
||||
|
||||
(provide '+key)
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
(defun +kmacro-change-mode-line (&rest _)
|
||||
"Remap the mode-line face when recording a kmacro."
|
||||
|
||||
(add-to-list 'face-remapping-alist '(mode-line . +kmacro-modeline)))
|
||||
|
||||
(defun +kmacro-restore-mode-line (&rest _)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'link-hint)
|
||||
|
||||
(defgroup +link-hint nil
|
||||
|
@ -22,7 +23,71 @@
|
|||
w3m-message-link)
|
||||
"Link types to define `:open-secondary' for.")
|
||||
|
||||
(defun +link-hint-setup-open-secondary (&optional types)
|
||||
(defvar +link-hint-map (make-sparse-keymap)
|
||||
"Keymap for `link-hint' functionality.")
|
||||
|
||||
(cl-defmacro +link-hint-define-keyword (keyword handler docstring
|
||||
&optional (types 'link-hint-types)
|
||||
&rest rest
|
||||
&key multiple &allow-other-keys)
|
||||
"Set up a `link-hint' KEYWORD, with optional TYPES.
|
||||
If TYPES is not present, use `link-hint-types'.
|
||||
|
||||
KEYWORD defines the link-hint type. It will be used to create a
|
||||
function for opening links of the form \"link-hint-openKEYWORD\".
|
||||
|
||||
HANDLER is the function to open a link with.
|
||||
|
||||
DOCSTRING is the macro's documentation.
|
||||
|
||||
Keyword arguments are passed to `link-hint-define-type' prefixed
|
||||
with the KEYWORD."
|
||||
(declare (indent 2)
|
||||
(doc-string 3))
|
||||
(let ((types (symbol-value types))
|
||||
(func-sym (intern (format "+link-hint-open%s" keyword)))
|
||||
(mult-sym (intern (format "%s-multiple" keyword)))
|
||||
(expr))
|
||||
;; Define the type
|
||||
(push `(dolist (type ',types)
|
||||
(link-hint-define-type type
|
||||
,keyword ,handler
|
||||
,@(mapcar (lambda (el)
|
||||
(if (eq el :multiple)
|
||||
mult-sym
|
||||
el))
|
||||
rest)))
|
||||
expr)
|
||||
;; Define an opener
|
||||
(push `(defun ,func-sym ()
|
||||
,(format "%s\n\nDefined by `+link-hint-define'." docstring)
|
||||
(interactive)
|
||||
(avy-with link-hint-open-link
|
||||
(link-hint--one ,keyword)))
|
||||
expr)
|
||||
;; Handle `:multiple'
|
||||
(when multiple
|
||||
(push `(defun ,(intern (format "+link-hint-open-multiple%s" keyword)) ()
|
||||
,(format "Open multiple links with `%s'.\n\nDefined by `+link-hint-define'."
|
||||
func-sym)
|
||||
(avy-with link-hint-open-multiple-links
|
||||
(link-hint--multiple ,keyword)))
|
||||
expr)
|
||||
(push `(defun ,(intern (format "+link-hint-open-all%s" keyword)) ()
|
||||
,(format "Open all visible links with `%s'.\n\nDefined by `+link-hint-define'."
|
||||
func-sym)
|
||||
(avy-with link-hint-open-all-links
|
||||
(link-hint--all ,keyword)))
|
||||
expr))
|
||||
;; Return the built expression
|
||||
`(progn ,@(nreverse expr))))
|
||||
|
||||
(+link-hint-define-keyword :secondary browse-url-secondary-browser-function
|
||||
"Open a link in the secondary browser."
|
||||
+link-hint-open-secondary-types
|
||||
:multiple t)
|
||||
|
||||
(defun +link-hint-open-secondary-setup (&optional types)
|
||||
"Define the `:open-secondary' link-hint type for TYPES.
|
||||
If TYPES is nil, define it for `+link-hint-open-secondary-types'."
|
||||
(dolist (type (or types +link-hint-open-secondary-types))
|
||||
|
@ -30,6 +95,36 @@ If TYPES is nil, define it for `+link-hint-open-secondary-types'."
|
|||
:open-secondary browse-url-secondary-browser-function
|
||||
:open-secondary-multiple t)))
|
||||
|
||||
(defun +link-hint-open-secondary ()
|
||||
"Open a link in the secondary browser."
|
||||
(interactive)
|
||||
(avy-with link-hint-open-link
|
||||
(link-hint--one :open-secondary)))
|
||||
|
||||
(defun +link-hint-open-chrome-setup (&optional types)
|
||||
"Define the `:open-chrome' link-hint type for TYPES.
|
||||
If TYPES is nil, define it for `+link-hint-open-secondary-types'."
|
||||
(dolist (type (or types +link-hint-open-secondary-types))
|
||||
(link-hint-define-type type
|
||||
:open-chrome #'browse-url-chrome
|
||||
:open-chrome-multiple t)))
|
||||
|
||||
(defun +link-hint-open-chrome ()
|
||||
"Open a link with chrome."
|
||||
(interactive)
|
||||
(avy-with link-hint-open-link
|
||||
(link-hint--one :open-chrome)))
|
||||
|
||||
;; (cl-defmacro +link-hint-add-type (keyword )
|
||||
;; "Define link-hint type KEYWORD to operate on TYPES.
|
||||
;; If TYPES is nil or absent, define KEYWORD for all
|
||||
;; `link-hint-types'."
|
||||
;; (let (forms)
|
||||
;; (dolist (type (or types link-hint-types))
|
||||
;; (push `(link-hint-define-type ,type ,keyword ,function) forms))
|
||||
;; (push `(defun ,(intern (format "+link-hint%s" ,keyword))
|
||||
;; ))))
|
||||
|
||||
(defun +link-hint-open-link (prefix)
|
||||
"Open a link.
|
||||
Without a PREFIX, open using `browse-url-browser-function'; with
|
||||
|
@ -54,5 +149,21 @@ a PREFIX, use `browse-url-secondary-browser-function'."
|
|||
(avy-with link-hint-open-all-links
|
||||
(link-hint--one (if prefix :open-secondary :open))))
|
||||
|
||||
;;; Pocket-reader.el integration
|
||||
|
||||
(defun +link-hint-pocket-add-setup (&optional types)
|
||||
"Define the `:pocket-add' link-hint type for TYPES.
|
||||
If TYPES is nil, define it for `link-hint-types'."
|
||||
(dolist (type (or types link-hint-types))
|
||||
(link-hint-define-type type
|
||||
:pocket-add #'pocket-reader-generic-add-link
|
||||
:pocket-add-multiple t)))
|
||||
|
||||
(defun +link-hint-pocket-add ()
|
||||
"Add a link to the Pocket reader."
|
||||
(interactive)
|
||||
(avy-with link-hint-open-link
|
||||
(link-hint--one :pocket-add)))
|
||||
|
||||
(provide '+link-hint)
|
||||
;;; +link-hint.el ends here
|
||||
|
|
|
@ -156,5 +156,40 @@ With a prefix argument N, (un)comment that many sexps."
|
|||
(dotimes (_ (or n 1))
|
||||
(+lisp-comment-sexp--raw))))
|
||||
|
||||
;;; Sort `setq' constructs
|
||||
;;https://emacs.stackexchange.com/questions/33039/
|
||||
|
||||
(defun +lisp-sort-setq ()
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(let ((sort-end (progn
|
||||
(end-of-defun)
|
||||
(backward-char)
|
||||
(point-marker)))
|
||||
(sort-beg (progn
|
||||
(beginning-of-defun)
|
||||
(or (re-search-forward "[ \\t]*(" (point-at-eol) t)
|
||||
(point-at-eol))
|
||||
(forward-sexp)
|
||||
(or (re-search-forward "\\<" (point-at-eol) t)
|
||||
(point-at-eol))
|
||||
(point-marker))))
|
||||
(narrow-to-region (1- sort-beg) (1+ sort-end))
|
||||
(sort-subr nil #'+lisp-sort-setq-next-record
|
||||
#'+lisp-sort-setq-end-record)))))
|
||||
|
||||
(defun +lisp-sort-setq-next-record ()
|
||||
(condition-case nil
|
||||
(progn
|
||||
(forward-sexp 1)
|
||||
(backward-sexp))
|
||||
('scan-error (end-of-buffer))))
|
||||
|
||||
(defun +lisp-sort-setq-end-record ()
|
||||
(condition-case nil
|
||||
(forward-sexp 2)
|
||||
('scan-error (end-of-buffer))))
|
||||
|
||||
(provide '+lisp)
|
||||
;;; +lisp.el ends here
|
||||
|
|
26
lisp/+message.el
Normal file
26
lisp/+message.el
Normal file
|
@ -0,0 +1,26 @@
|
|||
;;; +message.el --- Extra message-mode functions -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Thanks to Alex Schroeder for this!
|
||||
;; https://www.emacswiki.org/emacs/Change_Signature_Dynamically
|
||||
|
||||
(defun +message-check-for-signature-change (&rest ignore)
|
||||
"Check for a change in the To: or Cc: fields"
|
||||
(when (and (message--in-tocc-p)
|
||||
(not (buffer-narrowed-p)))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(let ((end (point)))
|
||||
(when (re-search-backward message-signature-separator nil t)
|
||||
(delete-region (1- (match-beginning 0)) end)))
|
||||
(message-insert-signature))))
|
||||
|
||||
(defun +message-signature-setup ()
|
||||
(make-local-variable 'after-change-functions)
|
||||
(push '+message-check-for-signature-change after-change-functions))
|
||||
|
||||
(provide '+message)
|
||||
;;; +message.el ends here
|
|
@ -9,6 +9,7 @@
|
|||
;;; Code:
|
||||
|
||||
(require '+util)
|
||||
(require 'actually-selected-window)
|
||||
(require 'simple-modeline)
|
||||
(require 'minions)
|
||||
|
||||
|
@ -25,46 +26,90 @@ will default to this string.")
|
|||
;;; Combinators
|
||||
|
||||
(defun +modeline-concat (segments &optional separator)
|
||||
"Concatenate multiple `simple-modeline'-style SEGMENTS.
|
||||
SEGMENTS is a list of either modeline segment-functions (see
|
||||
`simple-modeline' functions for an example of types of
|
||||
functions), though it can also contain cons cells of the
|
||||
form (SEGMENT . PREDICATE).
|
||||
"Concatenate multiple functional modeline SEGMENTS.
|
||||
Each segment in SEGMENTS is a function returning a mode-line
|
||||
construct.
|
||||
|
||||
Segments are separated from each other using SEPARATOR, which
|
||||
defaults to a \" \". space. Only segments that evaluate to a
|
||||
non-trivial string (that is, a string not equal to \"\") will be
|
||||
separated, for a cleaner look.
|
||||
Segments are separated using SEPARATOR, which defaults to
|
||||
`+modeline-default-spacer'. Only segments that evaluate to a
|
||||
non-zero-length string will be separated, for a cleaner look.
|
||||
|
||||
This function makes a lambda, so you can throw it straight into
|
||||
`simple-modeline-segments'."
|
||||
(setq separator (or separator +modeline-default-spacer))
|
||||
(lambda ()
|
||||
(apply #'concat
|
||||
(let (this-sep result-list)
|
||||
(dolist (segment segments)
|
||||
(push (funcall (or (car-safe segment) segment)
|
||||
this-sep)
|
||||
result-list)
|
||||
(if (or (cdr-safe segment)
|
||||
(and (car result-list)
|
||||
(not (equal (car result-list) ""))))
|
||||
(setq this-sep separator)
|
||||
(setq this-sep nil)))
|
||||
(unless (seq-some #'null result-list)
|
||||
(push +modeline-default-spacer result-list))
|
||||
(nreverse result-list)))))
|
||||
This function returns a lambda that should be `:eval'd or
|
||||
`funcall'd in a mode-line context."
|
||||
(let ((separator (or separator +modeline-default-spacer)))
|
||||
(lambda ()
|
||||
(let (this-sep result)
|
||||
(dolist (segment segments)
|
||||
(let ((segstr (funcall segment this-sep)))
|
||||
(when (and segstr
|
||||
(not (equal segstr "")))
|
||||
(push segstr result)
|
||||
(setq this-sep separator))))
|
||||
(apply #'concat
|
||||
(nreverse result))))))
|
||||
|
||||
(defun +modeline-spacer (&optional n spacer &rest strings)
|
||||
"Make an N-length SPACER, or prepend SPACER to STRINGS.
|
||||
When called with no arguments, insert `+modeline-default-spacer'.
|
||||
N will repeat SPACER N times, and defaults to 1. SPACER defaults
|
||||
to `+modeline-default-spacer', but can be any string. STRINGS
|
||||
should form a mode-line construct when `concat'ed."
|
||||
(declare (indent 2))
|
||||
(let ((spacer (or spacer +modeline-default-spacer))
|
||||
(n (or n 1))
|
||||
(strings (cond((null strings) '(""))
|
||||
((equal strings '("")) nil)
|
||||
((atom strings) (list strings))
|
||||
(t strings)))
|
||||
r)
|
||||
(when strings (dotimes (_ n) (push spacer r)))
|
||||
(apply #'concat (apply #'concat r) strings)))
|
||||
|
||||
;;; Modeline segments
|
||||
|
||||
(defun +modeline-sanitize-string (string)
|
||||
"Sanitize a string for `format-mode-line'."
|
||||
(when string
|
||||
(string-replace "%" "%%" string)))
|
||||
|
||||
(defcustom +modeline-buffer-name-max-length 0
|
||||
"Maximum length of `+modeline-buffer-name'.
|
||||
If > 0 and < 1, use that portion of the window's width. If > 1,
|
||||
use that many characters. If anything else, don't limit. If the
|
||||
buffer name is longer than the max length, it will be shortened
|
||||
and appended with `truncate-string-ellipsis'."
|
||||
:type '(choice (const :tag "No maximum length" 0)
|
||||
(natnum :tag "Number of characters")
|
||||
(float :tag "Fraction of window's width")))
|
||||
|
||||
(defcustom +modeline-buffer-position nil
|
||||
"What to put in the `+modeline-buffer-name' position."
|
||||
:type 'function
|
||||
:local t)
|
||||
|
||||
(defun +modeline-buffer-name (&optional spacer) ; gonsie
|
||||
"Display the buffer name."
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(propertize
|
||||
(+string-align (buffer-name) 20 :ellipsis nil)
|
||||
'help-echo (or (buffer-file-name)
|
||||
(buffer-name))
|
||||
'mouse-face 'mode-line-highlight)))
|
||||
(let ((bufname (string-trim (string-replace "%" "" (buffer-name)))))
|
||||
(+modeline-spacer nil spacer
|
||||
(if (and +modeline-buffer-position (fboundp +modeline-buffer-position))
|
||||
(funcall +modeline-buffer-position)
|
||||
(propertize (cond
|
||||
((ignore-errors
|
||||
(and (> +modeline-buffer-name-max-length 0)
|
||||
(< +modeline-buffer-name-max-length 1)))
|
||||
(truncate-string-to-width bufname
|
||||
(* (window-total-width)
|
||||
+modeline-buffer-name-max-length)
|
||||
nil nil t))
|
||||
((ignore-errors
|
||||
(> +modeline-buffer-name-max-length 1))
|
||||
(truncate-string-to-width bufname
|
||||
+modeline-buffer-name-max-length
|
||||
nil nil t))
|
||||
(t bufname))
|
||||
'help-echo (or (buffer-file-name)
|
||||
(buffer-name))
|
||||
'mouse-face 'mode-line-highlight)))))
|
||||
|
||||
(defcustom +modeline-minions-icon "&"
|
||||
"The \"icon\" for `+modeline-minions' button."
|
||||
|
@ -72,28 +117,61 @@ This function makes a lambda, so you can throw it straight into
|
|||
|
||||
(defun +modeline-minions (&optional spacer)
|
||||
"Display a button for `minions-minor-modes-menu'."
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(propertize
|
||||
+modeline-minions-icon
|
||||
'help-echo "Minor modes menu\nmouse-1: show menu."
|
||||
'local-map (purecopy (simple-modeline-make-mouse-map
|
||||
'mouse-1
|
||||
(lambda (event)
|
||||
(interactive "e")
|
||||
(with-selected-window
|
||||
(posn-window (event-start event))
|
||||
(minions-minor-modes-menu)))))
|
||||
'mouse-face 'mode-line-highlight)))
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize
|
||||
+modeline-minions-icon
|
||||
'help-echo "Minor modes menu\nmouse-1: show menu."
|
||||
'local-map (purecopy (simple-modeline-make-mouse-map
|
||||
'mouse-1
|
||||
(lambda (event)
|
||||
(interactive "e")
|
||||
(with-selected-window
|
||||
(posn-window (event-start event))
|
||||
(minions-minor-modes-menu)))))
|
||||
'mouse-face 'mode-line-highlight)))
|
||||
|
||||
(defcustom +modeline-major-mode-faces '((text-mode . font-lock-string-face)
|
||||
(prog-mode . font-lock-keyword-face)
|
||||
(t . font-lock-warning-face))
|
||||
"Mode->face mapping for `+modeline-major-mode'.
|
||||
If the current mode is derived from the car of a cell, the face
|
||||
in the cdr will be applied to the major-mode in the mode line."
|
||||
:type '(alist :key-type function
|
||||
:value-type face))
|
||||
|
||||
(defface +modeline-major-mode-face nil
|
||||
"Face for modeline major-mode.")
|
||||
|
||||
(defun +modeline-major-mode (&optional spacer)
|
||||
"Display the current `major-mode'."
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(propertize (+string-truncate (format-mode-line mode-name) 12)
|
||||
'face 'bold
|
||||
'keymap mode-line-major-mode-keymap
|
||||
'help-echo (concat (format-mode-line mode-name)
|
||||
" mode\nmouse-1: show menu.")
|
||||
'mouse-face 'mode-line-highlight)))
|
||||
(+modeline-spacer nil spacer
|
||||
"("
|
||||
(propertize ;; (+string-truncate (format-mode-line mode-name) 16)
|
||||
(format-mode-line mode-name)
|
||||
'face (when (actually-selected-window-p)
|
||||
;; XXX: This is probably really inefficient. I need to
|
||||
;; simply detect which mode it's in when I change major
|
||||
;; modes (`change-major-mode-hook') and change the face
|
||||
;; there, probably.
|
||||
;; (catch :done (dolist (cel +modeline-major-mode-faces)
|
||||
;; (when (derived-mode-p (car cel))
|
||||
;; (throw :done (cdr cel))))
|
||||
;; (alist-get t +modeline-major-mode-faces))
|
||||
'+modeline-major-mode-face)
|
||||
'keymap (let ((map (make-sparse-keymap)))
|
||||
(bindings--define-key map [mode-line down-mouse-1]
|
||||
`(menu-item "Menu Bar" ignore
|
||||
:filter ,(lambda (_) (mouse-menu-major-mode-map))))
|
||||
(define-key map [mode-line mouse-2] 'describe-mode)
|
||||
(bindings--define-key map [mode-line down-mouse-3]
|
||||
`(menu-item "Minions" minions-minor-modes-menu))
|
||||
map)
|
||||
'help-echo (+concat (list (format-mode-line mode-name) " mode")
|
||||
"mouse-1: show menu"
|
||||
"mouse-2: describe mode"
|
||||
"mouse-3: display minor modes")
|
||||
'mouse-face 'mode-line-highlight)
|
||||
")"))
|
||||
|
||||
(defcustom +modeline-modified-icon-alist '((ephemeral . "*")
|
||||
(readonly . "=")
|
||||
|
@ -124,95 +202,185 @@ The order of elements matters: whichever one matches first is applied."
|
|||
(defun +modeline-modified (&optional spacer) ; modified from `simple-modeline-status-modified'
|
||||
"Display a color-coded \"icon\" indicator for the buffer's status."
|
||||
(let* ((icon (catch :icon
|
||||
(dolist (cell +modeline-modified-icon-alist)
|
||||
(when (pcase (car cell)
|
||||
('ephemeral (not (buffer-file-name)))
|
||||
('readonly buffer-read-only)
|
||||
('modified (buffer-modified-p))
|
||||
('special
|
||||
(apply 'derived-mode-p
|
||||
+modeline-modified-icon-special-modes))
|
||||
('t t)
|
||||
(_ nil))
|
||||
(throw :icon (cdr cell)))))))
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(propertize (or icon "")
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
|
||||
(defun +modeline-buffer-modes (&optional spacer)
|
||||
"Display various buffer-specific stuff cleanly."
|
||||
;; This is clunky and should probably be improved.
|
||||
(concat (+modeline-reading-mode)
|
||||
(+modeline-narrowed (when reading-mode ","))))
|
||||
(dolist (cell +modeline-modified-icon-alist)
|
||||
(when (pcase (car cell)
|
||||
('ephemeral (not (buffer-file-name)))
|
||||
('readonly buffer-read-only)
|
||||
('modified (buffer-modified-p))
|
||||
('special
|
||||
(apply 'derived-mode-p
|
||||
+modeline-modified-icon-special-modes))
|
||||
('t t)
|
||||
(_ nil))
|
||||
(throw :icon cell))))))
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize (or (cdr-safe icon) "")
|
||||
'help-echo (format "Buffer \"%s\" is %s."
|
||||
(buffer-name)
|
||||
(pcase (car-safe icon)
|
||||
('t "unmodified")
|
||||
('nil "unknown")
|
||||
(_ (car-safe icon))))))))
|
||||
|
||||
(defun +modeline-narrowed (&optional spacer)
|
||||
"Display an indication that the buffer is narrowed."
|
||||
(when (buffer-narrowed-p)
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(propertize "N"
|
||||
'help-echo (format "%s\n%s"
|
||||
"Buffer is narrowed."
|
||||
"mouse-2: widen buffer.")
|
||||
'local-map (purecopy (simple-modeline-make-mouse-map
|
||||
'mouse-2 'mode-line-widen))
|
||||
'face 'font-lock-doc-face
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize "N"
|
||||
'help-echo (format "%s\n%s"
|
||||
"Buffer is narrowed."
|
||||
"mouse-2: widen buffer.")
|
||||
'local-map (purecopy (simple-modeline-make-mouse-map
|
||||
'mouse-2 'mode-line-widen))
|
||||
'face 'font-lock-doc-face
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
|
||||
(defun +modeline-reading-mode (&optional spacer)
|
||||
"Display an indication that the buffer is in `reading-mode'."
|
||||
(when reading-mode
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(propertize
|
||||
(concat "R" (when (bound-and-true-p +eww-readable-p) "w"))
|
||||
'help-echo (format "%s\n%s"
|
||||
"Buffer is in reading-mode."
|
||||
"mouse-2: disable reading-mode.")
|
||||
'local-map (purecopy
|
||||
(simple-modeline-make-mouse-map
|
||||
'mouse-2 (lambda (ev)
|
||||
(interactive "e")
|
||||
(with-selected-window
|
||||
(posn-window
|
||||
(event-start ev))
|
||||
(reading-mode -1)
|
||||
(force-mode-line-update)))))
|
||||
'face 'font-lock-doc-face
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize
|
||||
(concat "R" (when (bound-and-true-p +eww-readable-p) "w"))
|
||||
'help-echo (format "%s\n%s"
|
||||
"Buffer is in reading-mode."
|
||||
"mouse-2: disable reading-mode.")
|
||||
'local-map (purecopy
|
||||
(simple-modeline-make-mouse-map
|
||||
'mouse-2 (lambda (ev)
|
||||
(interactive "e")
|
||||
(with-selected-window
|
||||
(posn-window
|
||||
(event-start ev))
|
||||
(reading-mode -1)
|
||||
(force-mode-line-update)))))
|
||||
'face 'font-lock-doc-face
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
|
||||
(define-minor-mode file-percentage-mode
|
||||
"Toggle the percentage display in the mode line (File Percentage Mode)."
|
||||
:init-value t :global t :group 'mode-line)
|
||||
|
||||
(defun +modeline-position (&optional _) ; adapted from `simple-modeline'
|
||||
"Display the current cursor position."
|
||||
(list '((line-number-mode
|
||||
((column-number-mode
|
||||
(column-number-indicator-zero-based
|
||||
(9 " %l:%c")
|
||||
(9 " %l:%C"))
|
||||
(6 " %l:")))
|
||||
((column-number-mode
|
||||
(column-number-indicator-zero-based
|
||||
(5 " :%c")
|
||||
(5 " :%C"))))))
|
||||
'(file-percentage-mode
|
||||
((-3 "%p") "%% "))
|
||||
(if (region-active-p)
|
||||
(propertize (format "%s%-5d"
|
||||
(if (and (mark) (< (point) (mark))) "-" "+")
|
||||
(apply '+ (mapcar
|
||||
(lambda (pos)
|
||||
(- (cdr pos)
|
||||
(car pos)))
|
||||
(region-bounds))))
|
||||
'font-lock-face 'font-lock-variable-name-face))))
|
||||
(defun +modeline--percentage ()
|
||||
"Return point's progress through current file as a percentage."
|
||||
(let ((tot (count-screen-lines (point-min) (point-max) :ignore-invisible)))
|
||||
(floor (* 100 (/ (float (line-number-at-pos)) tot)))))
|
||||
|
||||
(defun +modeline--buffer-contained-in-window-p ()
|
||||
"Whether the buffer is totally contained within its window."
|
||||
(let ((window-min (save-excursion (move-to-window-line 0) (point)))
|
||||
(window-max (save-excursion (move-to-window-line -1) (point))))
|
||||
(and (<= window-min (point-min))
|
||||
(>= window-max (point-max)))))
|
||||
|
||||
(defun +modeline-file-percentage (&optional spacer)
|
||||
"Display the position in the current file."
|
||||
(when file-percentage-mode
|
||||
;; (let ((perc (+modeline--percentage)))
|
||||
;; (propertize (+modeline-spacer nil spacer
|
||||
;; (cond
|
||||
;; ((+modeline--buffer-contained-in-window-p) "All")
|
||||
;; ((= (line-number-at-pos) (line-number-at-pos (point-min))) "Top")
|
||||
;; ((= (line-number-at-pos) (line-number-at-pos (point-max))) "Bot")
|
||||
;; ;; Why the 10 %s? Not sure. `format' knocks them
|
||||
;; ;; down to 5, then `format-mode-line' kills all but
|
||||
;; ;; two. If I use only 8, the margin is much too
|
||||
;; ;; large. Something else is obviously going on, but
|
||||
;; ;; I'm at a loss as to what it could be.
|
||||
;; (t (format "%d%%%%%%%%%%" perc))))
|
||||
;; ;; TODO: add scroll-up and scroll-down bindings.
|
||||
;; ))
|
||||
(let ((perc (format-mode-line '(-2 "%p"))))
|
||||
(+modeline-spacer nil spacer
|
||||
"/"
|
||||
(pcase perc
|
||||
("To" "Top")
|
||||
("Bo" "Bot")
|
||||
("Al" "All")
|
||||
(_ (format ".%02d" (string-to-number perc))))))))
|
||||
|
||||
(defun +modeline-file-percentage-ascii-icon (&optional spacer)
|
||||
(when file-percentage-mode
|
||||
(+modeline-spacer nil spacer
|
||||
(let ((perc (format-mode-line '(-2 "%p"))))
|
||||
(pcase perc
|
||||
("To" "/\\")
|
||||
("Bo" "\\/")
|
||||
("Al" "[]")
|
||||
(_ (let ((vec (vector "/|" "//" "||" "\\\\" "\\|" "\\|"))
|
||||
(perc (string-to-number perc)))
|
||||
(aref vec (floor (/ perc 17))))))))))
|
||||
|
||||
(defun +modeline-file-percentage-icon (&optional spacer)
|
||||
"Display the position in the current file as an icon."
|
||||
(when file-percentage-mode
|
||||
(let ((perc (+modeline--percentage)))
|
||||
(propertize (+modeline-spacer nil spacer
|
||||
(cond
|
||||
((+modeline--buffer-contained-in-window-p) "111")
|
||||
((= perc 0) "000")
|
||||
((< perc 20) "001")
|
||||
((< perc 40) "010")
|
||||
((< perc 60) "011")
|
||||
((< perc 80) "100")
|
||||
((< perc 100) "101")
|
||||
((>= perc 100) "110")))
|
||||
'help-echo (format "Point is %d%% through the buffer."
|
||||
perc)))))
|
||||
|
||||
(define-minor-mode region-indicator-mode
|
||||
"Toggle the region indicator in the mode line."
|
||||
:init-value t :global t :group 'mode-line)
|
||||
|
||||
(defun +modeline-region (&optional spacer)
|
||||
"Display an indicator if the region is active."
|
||||
(when (and region-indicator-mode
|
||||
(region-active-p))
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize (format "%d%s"
|
||||
(apply '+ (mapcar (lambda (pos)
|
||||
(- (cdr pos)
|
||||
(car pos)))
|
||||
(region-bounds)))
|
||||
(if (and (< (point) (mark))) "-" "+"))
|
||||
'font-lock-face 'font-lock-variable-name-face))))
|
||||
|
||||
(defun +modeline-line (&optional spacer)
|
||||
(when line-number-mode
|
||||
(+modeline-spacer nil spacer
|
||||
"%3l")))
|
||||
|
||||
(defun +modeline-column (&optional spacer)
|
||||
(when column-number-mode
|
||||
(+modeline-spacer nil spacer
|
||||
"|"
|
||||
(if column-number-indicator-zero-based "%2c" "%2C"))))
|
||||
|
||||
(defcustom +modeline-position-function nil
|
||||
"Function to use instead of `+modeline-position' in modeline."
|
||||
:type '(choice (const :tag "Default" nil)
|
||||
function)
|
||||
:local t)
|
||||
|
||||
(defun +modeline-position (&optional spacer)
|
||||
"Display the current cursor position.
|
||||
See `line-number-mode', `column-number-mode', and
|
||||
`file-percentage-mode'. If `+modeline-position-function' is set
|
||||
to a function in the current buffer, call that function instead."
|
||||
(cond ((functionp +modeline-position-function)
|
||||
(when-let* ((str (funcall +modeline-position-function)))
|
||||
(+modeline-spacer nil spacer str)))
|
||||
(t (funcall (+modeline-concat '(+modeline-region
|
||||
+modeline-line
|
||||
+modeline-column
|
||||
+modeline-file-percentage)
|
||||
"")))))
|
||||
|
||||
(defun +modeline-vc (&optional spacer)
|
||||
"Display the version control branch of the current buffer in the modeline."
|
||||
;; from https://www.gonsie.com/blorg/modeline.html, from Doom
|
||||
(if-let ((backend (vc-backend buffer-file-name)))
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2)))))
|
||||
(when-let ((backend (vc-backend buffer-file-name)))
|
||||
(+modeline-spacer nil spacer
|
||||
(substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2)))))
|
||||
|
||||
(defun +modeline-track (&optional spacer)
|
||||
"Display `tracking-mode' information."
|
||||
|
@ -221,24 +389,99 @@ The order of elements matters: whichever one matches first is applied."
|
|||
|
||||
(defun +modeline-anzu (&optional spacer)
|
||||
"Display `anzu--update-mode-line'."
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(anzu--update-mode-line)))
|
||||
(+modeline-spacer nil spacer
|
||||
(anzu--update-mode-line)))
|
||||
|
||||
(defun +modeline-text-scale (&optional spacer)
|
||||
"Display text scaling level."
|
||||
;; adapted from https://github.com/seagle0128/doom-modeline
|
||||
(when (and (boundp 'text-scale-mode-amount)
|
||||
(/= text-scale-mode-amount 0))
|
||||
(format (if (> text-scale-mode-amount 0) "%s(%+d)" "%s(%-d)")
|
||||
(or spacer +modeline-default-spacer)
|
||||
text-scale-mode-amount)))
|
||||
(+modeline-spacer nil spacer
|
||||
(concat (if (> text-scale-mode-amount 0) "+" "-")
|
||||
(number-to-string text-scale-mode-amount)))))
|
||||
|
||||
(defun +modeline-ace-window-display (&optional spacer)
|
||||
"Display `ace-window-display-mode' information in the modeline."
|
||||
(when (and +ace-window-display-mode
|
||||
ace-window-mode)
|
||||
(concat (or spacer +modeline-default-spacer)
|
||||
(window-parameter (selected-window) 'ace-window-path))))
|
||||
(+modeline-spacer nil spacer
|
||||
(window-parameter (selected-window) 'ace-window-path))))
|
||||
|
||||
(defun +modeline-god-mode (&optional spacer)
|
||||
"Display an icon when `god-mode' is active."
|
||||
(when (and (boundp 'god-local-mode) god-local-mode)
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize "Ω"
|
||||
'help-echo (concat "God mode is active."
|
||||
"\nmouse-1: exit God mode.")
|
||||
'local-map (purecopy
|
||||
(simple-modeline-make-mouse-map
|
||||
'mouse-1 (lambda (e)
|
||||
(interactive "e")
|
||||
(with-selected-window
|
||||
(posn-window
|
||||
(event-start e))
|
||||
(god-local-mode -1)
|
||||
(force-mode-line-update)))))
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
|
||||
(defun +modeline-input-method (&optional spacer)
|
||||
"Display which input method is active."
|
||||
(when current-input-method
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize current-input-method-title
|
||||
'help-echo (format
|
||||
(concat "Current input method: %s\n"
|
||||
"mouse-1: Describe current input method\n"
|
||||
"mouse-3: Toggle input method")
|
||||
current-input-method)
|
||||
'local-map (purecopy
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [mode-line mouse-1]
|
||||
(lambda (e)
|
||||
(interactive "e")
|
||||
(with-selected-window (posn-window (event-start e))
|
||||
(describe-current-input-method))))
|
||||
(define-key map [mode-line mouse-3]
|
||||
(lambda (e)
|
||||
(interactive "e")
|
||||
(with-selected-window (posn-window (event-start e))
|
||||
(toggle-input-method nil :interactive))))
|
||||
map))
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
|
||||
(defface +modeline-kmacro-indicator '((t :foreground "Firebrick"))
|
||||
"Face for the kmacro indicator in the modeline.")
|
||||
|
||||
(defun +modeline-kmacro-indicator (&optional spacer)
|
||||
"Display an indicator when recording a kmacro."
|
||||
(when defining-kbd-macro
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize "●"
|
||||
'face '+modeline-kmacro-indicator
|
||||
'help-echo (format (concat "Defining a macro\n"
|
||||
"Current step: %d\n"
|
||||
"mouse-1: Stop recording")
|
||||
kmacro-counter)
|
||||
'local-map (purecopy (simple-modeline-make-mouse-map
|
||||
'mouse-1 (lambda (e)
|
||||
(interactive "e")
|
||||
(with-selected-window
|
||||
(posn-window (event-start e))
|
||||
(kmacro-end-macro nil)))))
|
||||
'mouse-face 'mode-line-highlight))))
|
||||
|
||||
(defface +nyan-mode-line nil
|
||||
"Face for nyan-cat in mode line.")
|
||||
|
||||
(defun +modeline-nyan-on-focused (&optional spacer)
|
||||
"Display the cat from `nyan-mode', but only on the focused window."
|
||||
(require 'nyan-mode)
|
||||
(when (and (or nyan-mode (bound-and-true-p +nyan-local-mode))
|
||||
(actually-selected-window-p))
|
||||
(+modeline-spacer nil spacer
|
||||
(propertize (nyan-create) 'face '+nyan-mode-line))))
|
||||
|
||||
(provide '+modeline)
|
||||
;;; +modeline.el ends here
|
||||
|
|
42
lisp/+mwim.el
Normal file
42
lisp/+mwim.el
Normal file
|
@ -0,0 +1,42 @@
|
|||
;;; +mwim.el --- Extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'seq)
|
||||
|
||||
(defgroup +mwim nil
|
||||
"Extra `mwim' customizations."
|
||||
:group 'mwim)
|
||||
|
||||
(defcustom +mwim-passthrough-modes nil
|
||||
"Modes to not move-where-I-mean."
|
||||
:type '(repeat function))
|
||||
|
||||
(defun +mwim-beginning-maybe (&optional arg)
|
||||
"Perform `mwim-beginning', maybe.
|
||||
Will just do \\[beginning-of-line] in one of
|
||||
`+mwim-passthrough-modes'."
|
||||
(interactive)
|
||||
(if (apply #'derived-mode-p +mwim-passthrough-modes)
|
||||
(let ((this-mode-map (symbol-value (intern (format "%s-map" major-mode))))
|
||||
(key "C-a"))
|
||||
(call-interactively (or (keymap-lookup this-mode-map key t t)
|
||||
(keymap-lookup (current-global-map) key t t))))
|
||||
(call-interactively #'mwim-beginning)))
|
||||
|
||||
(defun +mwim-end-maybe (&optional arg)
|
||||
"Perform `mwim-beginning', maybe.
|
||||
Will just do \\[end-of-line] in one of
|
||||
`+mwim-passthrough-modes'."
|
||||
(interactive)
|
||||
(if (apply #'derived-mode-p +mwim-passthrough-modes)
|
||||
(let ((this-mode-map (symbol-value (intern (format "%s-map" major-mode))))
|
||||
(key "C-e"))
|
||||
(call-interactively (or (keymap-lookup this-mode-map key t t)
|
||||
(keymap-lookup (current-global-map) key t t))))
|
||||
(call-interactively #'mwim-end)))
|
||||
|
||||
(provide '+mwim)
|
||||
;;; +mwim.el ends here
|
62
lisp/+notmuch.el
Normal file
62
lisp/+notmuch.el
Normal file
|
@ -0,0 +1,62 @@
|
|||
;;; +notmuch.el --- Notmuch extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This is stuff that I suppose /could/ go in notmuch/init.el, but ... doesn't.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'notmuch)
|
||||
|
||||
(defvar +notmuch-send-dispatch-rules nil
|
||||
"Alist of from addresses and variables to set when sending.")
|
||||
|
||||
(defun +notmuch-query-concat (&rest queries)
|
||||
"Concatenate notmuch queries."
|
||||
(mapconcat #'identity queries " AND "))
|
||||
|
||||
(defun +send-mail-dispatch ()
|
||||
"Dispatch mail sender, depending on account."
|
||||
(let ((from (message-fetch-field "from")))
|
||||
(dolist (vars (cl-loop for (addr . vars) in +notmuch-send-dispatch-rules
|
||||
if (string-match-p addr from) return vars))
|
||||
(set (car vars) (cdr vars)))))
|
||||
|
||||
(defun +notmuch-correct-tags (args)
|
||||
(list (car args) (mapcar #'string-trim (cadr args))))
|
||||
|
||||
(defun +notmuch-goto (&optional prefix)
|
||||
"Go straight to a `notmuch' search.
|
||||
Without PREFIX argument, go to the first one in
|
||||
`notmuch-saved-searches'; with a PREFIX argument, prompt the user
|
||||
for which saved search to go to; with a double PREFIX
|
||||
argument (\\[universal-argument] \\[universal-argument]), prompt
|
||||
for search."
|
||||
(interactive "P")
|
||||
(pcase prefix
|
||||
('nil (notmuch-search (plist-get (car notmuch-saved-searches) :query)))
|
||||
('(4) (notmuch-search (plist-get (cl-find (completing-read "Saved Search: "
|
||||
(mapcar (lambda (el)
|
||||
(plist-get el :name))
|
||||
notmuch-saved-searches))
|
||||
notmuch-saved-searches
|
||||
:key (lambda (el) (plist-get el :name))
|
||||
:test #'equal)
|
||||
:query)))
|
||||
(_ (notmuch-search))))
|
||||
|
||||
;; Don't add an initial input when completing addresses
|
||||
(el-patch-feature notmuch)
|
||||
(with-eval-after-load 'notmuch
|
||||
(el-patch-defun notmuch-address-selection-function (prompt collection initial-input)
|
||||
"Call (`completing-read'
|
||||
PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
|
||||
(completing-read
|
||||
prompt collection nil nil
|
||||
(el-patch-swap initial-input
|
||||
nil)
|
||||
'notmuch-address-history)))
|
||||
|
||||
(provide '+notmuch)
|
||||
;;; +notmuch.el ends here
|
42
lisp/+nyan-mode.el
Normal file
42
lisp/+nyan-mode.el
Normal file
|
@ -0,0 +1,42 @@
|
|||
;;; +nyan-mode.el --- Extras for nyan-mode -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;; Update even without line number in the mode line.
|
||||
|
||||
(defcustom +nyan-mode-update-functions
|
||||
'( end-of-buffer beginning-of-buffer
|
||||
next-line previous-line
|
||||
org-next-visible-heading org-previous-visible-heading)
|
||||
"Functions after which to force a mode-line update."
|
||||
:type '(repeat function))
|
||||
|
||||
(defun +nyan-mode--fmlu (&rest _)
|
||||
"Update the mode-line, advice-style."
|
||||
(force-mode-line-update))
|
||||
|
||||
(defun +nyan-mode-advice (&rest _)
|
||||
"Advise line-moving functions when in `nyan-mode'."
|
||||
(dolist (fn +nyan-mode-update-functions)
|
||||
(if nyan-mode
|
||||
(advice-add fn :after #'+nyan-mode--fmlu)
|
||||
(advice-remove fn #'+nyan-mode--fmlu))))
|
||||
|
||||
(defface +nyan-mode-line nil
|
||||
"Face for the nyan-mode mode-line indicator.")
|
||||
|
||||
(define-minor-mode +nyan-local-mode
|
||||
"My very own `nyan-mode' that isn't global and doesn't update the mode-line."
|
||||
:global nil
|
||||
:group 'nyan
|
||||
(dolist (fn +nyan-mode-update-functions)
|
||||
(if +nyan-local-mode
|
||||
(advice-add fn :after #'+nyan-mode--fmlu)
|
||||
(advice-remove fn #'+nyan-mode--fmlu))))
|
||||
|
||||
(define-globalized-minor-mode +nyan-mode +nyan-local-mode +nyan-local-mode)
|
||||
|
||||
(provide '+nyan-mode)
|
||||
;;; +nyan-mode.el ends here
|
60
lisp/+orderless.el
Normal file
60
lisp/+orderless.el
Normal file
|
@ -0,0 +1,60 @@
|
|||
;;; +orderless.el --- Mostly from minad -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; See https://github.com/minad/consult/wiki#minads-orderless-configuration
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'orderless)
|
||||
|
||||
;;; Dispataching
|
||||
|
||||
(defvar +orderless-dispatch-alist '((?% . char-fold-to-regexp)
|
||||
(?! . orderless-without-literal)
|
||||
(?` . orderless-initialism)
|
||||
(?= . orderless-literal)
|
||||
(?~ . orderless-flex))
|
||||
"Charcters to dispatch styles on orderless segments.")
|
||||
|
||||
(defun +orderless-dispatch (pattern index _total)
|
||||
"Dispatch orderless segments of a search string.
|
||||
Dispatchers are taken from `+orderless-dispatch-alist', and added
|
||||
to the following defaults:
|
||||
|
||||
- regexp$ :: matches REGEXP at the end of the pattern.
|
||||
- .ext :: matches EXT (at end of pattern)
|
||||
|
||||
Dispatch characters can be added at the beginning or ending of a
|
||||
segment to make that segment match accordingly."
|
||||
(cond
|
||||
;; Ensure that $ works with Consult commands, which add disambiguation
|
||||
;; suffixes
|
||||
((string-suffix-p "$" pattern)
|
||||
(cons 'orderless-regexp
|
||||
(concat (substring pattern 0 -1) "[\x100000-\x10FFFD]*$")))
|
||||
;; File extensions
|
||||
((and
|
||||
;; Completing filename or eshell
|
||||
(or minibuffer-completing-file-name
|
||||
(derived-mode-p 'eshell-mode))
|
||||
;; File extension
|
||||
(string-match-p "\\`\\.." pattern))
|
||||
(cons 'orderless-regexp
|
||||
(concat "\\." (substring pattern 1) "[\x100000-\x10FFFD]*$")))
|
||||
;; Ignore single !
|
||||
((string= "!" pattern) `(orderless-literal . ""))
|
||||
;; Prefix and suffix
|
||||
((if-let (x (assq (aref pattern 0) +orderless-dispatch-alist))
|
||||
(cons (cdr x) (substring pattern 1))
|
||||
(when-let (x (assq (aref pattern (1- (length pattern)))
|
||||
+orderless-dispatch-alist))
|
||||
(cons (cdr x) (substring pattern 0 -1)))))))
|
||||
|
||||
(orderless-define-completion-style +orderless-with-initialism
|
||||
(orderless-matching-styles '(orderless-initialism
|
||||
orderless-literal
|
||||
orderless-regexp)))
|
||||
|
||||
(provide '+orderless)
|
||||
;;; +orderless.el ends here
|
29
lisp/+org-attach.el
Normal file
29
lisp/+org-attach.el
Normal file
|
@ -0,0 +1,29 @@
|
|||
;;; +org-attach.el --- Fixes for org-attach -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; `org-attach-attach' doesn't fix the path name. Before I submit a bug, I'm
|
||||
;; just fixing it by advising `org-attach-attach'.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defun +org-attach-attach-fix-args (args)
|
||||
"ADVICE for `org-attach-attach' to normalize FILE first.
|
||||
VISIT-DIR and METHOD are passed through unchanged.
|
||||
|
||||
This should be applied as `:filter-args' advice."
|
||||
(cons (expand-file-name (car args)) (cdr args)))
|
||||
|
||||
(define-minor-mode +org-attach-fix-args-mode
|
||||
"Fix the arguments passed to `org-attach-attach'.
|
||||
This mode normalizes the filename passed to `org-attach-attach'
|
||||
so that links can be properly made."
|
||||
:lighter ""
|
||||
:keymap nil
|
||||
:global t ; I figure, what does this hurt?
|
||||
(if +org-attach-fix-args-mode
|
||||
(advice-add 'org-attach-attach :filter-args #'+org-attach-attach-fix-args)
|
||||
(advice-remove 'org-attach-attach #'+org-attach-attach-fix-args)))
|
||||
|
||||
(provide '+org-attach)
|
||||
;;; +org-attach.el ends here
|
|
@ -85,5 +85,80 @@ properly process the variable."
|
|||
;; Sort after, maybe
|
||||
(when sort-after (+org-capture-sort list))))
|
||||
|
||||
(defun +org-template--ensure-path (keys &optional list)
|
||||
"Ensure path of keys exists in `org-capture-templates'."
|
||||
(unless list (setq list 'org-capture-templates))
|
||||
(when (> (length key) 1)
|
||||
;; Check for existence of groups.
|
||||
(let ((expected (cl-loop for i from 1 to (1- (length key))
|
||||
collect (substring key 0 i) into keys
|
||||
finally return keys)))
|
||||
(cl-loop for ek in expected
|
||||
if (not (+org-capture--get ek (symbol-value list))) do
|
||||
(setf (+org-capture--get ek (symbol-value list))
|
||||
(list (format "(Group %s)" ek)))))))
|
||||
|
||||
(defcustom +org-capture-default-type 'entry
|
||||
"Default template for `org-capture-templates'."
|
||||
:type '(choice (const :tag "Entry" entry)
|
||||
(const :tag "Item" item)
|
||||
(const :tag "Check Item" checkitem)
|
||||
(const :tag "Table Line" table-line)
|
||||
(const :tag "Plain Text" plain)))
|
||||
|
||||
(defcustom +org-capture-default-target ""
|
||||
"Default target for `org-capture-templates'."
|
||||
;; TODO: type
|
||||
)
|
||||
|
||||
(defcustom +org-capture-default-template nil
|
||||
"Default template for `org-capture-templates'."
|
||||
;; TODO: type
|
||||
)
|
||||
|
||||
(defun +org-define-capture-templates-group (keys description)
|
||||
"Add a group title to `org-capture-templates'."
|
||||
(setf (+org-capture--get keys org-capture-templates)
|
||||
(list description)))
|
||||
|
||||
;; [[https://github.com/cadadr/configuration/blob/39813a771286e542af3aa333172858532c3bb257/emacs.d/gk/gk-org.el#L1573][from cadadr]]
|
||||
(defun +org-define-capture-template (keys description &rest args)
|
||||
"Define a capture template and necessary antecedents.
|
||||
ARGS is a plist, which in addition to the additional options
|
||||
`org-capture-templates' accepts, takes the following and places
|
||||
them accordingly: :type, :target, and :template. Each of these
|
||||
corresponds to the same field in `org-capture-templates's
|
||||
docstring, which see. Likewise with KEYS and DESCRIPTION, which
|
||||
are passed separately to the function.
|
||||
|
||||
This function will also create all the necessary intermediate
|
||||
capture keys needed for `org-capture'; that is, if KEYS is
|
||||
\"wcp\", entries for \"w\" and \"wc\" will both be ensured in
|
||||
`org-capture-templates'."
|
||||
(declare (indent 2))
|
||||
;; Check for existence of parent groups
|
||||
(when (> (length keys) 1)
|
||||
(let ((expected (cl-loop for i from 1 to (1- (length keys))
|
||||
collect (substring 0 i) into keys
|
||||
finally return keys)))
|
||||
(cl-loop
|
||||
for ek in expected
|
||||
if (not (+org-capture--get ek org-capture-templates))
|
||||
do (+org-define-capture-templates-group ek (format "(Group %s)" ek)))))
|
||||
(if (null args)
|
||||
;; Add the title
|
||||
(+org-define-capture-templates-group keys description)
|
||||
;; Add the capture template.
|
||||
(setf (+org-capture--get keys org-capture-templates)
|
||||
(append (list (or (plist-get args :type)
|
||||
+org-capture-default-type)
|
||||
(or ( plist-get args :target)
|
||||
+org-capture-default-target)
|
||||
(or (plist-get args :template)
|
||||
+org-capture-default-template))
|
||||
(cl-loop for (key val) on args by #'cddr
|
||||
unless (member key '(:type :target :template))
|
||||
append (list key val))))))
|
||||
|
||||
(provide '+org-capture)
|
||||
;;; +org-capture.el ends here
|
||||
|
|
47
lisp/+org-drawer-list.el
Normal file
47
lisp/+org-drawer-list.el
Normal file
|
@ -0,0 +1,47 @@
|
|||
;;; +org-drawer-list.el --- Add stuff to org drawers easy-style -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'org)
|
||||
(require '+org)
|
||||
(require 'ol)
|
||||
(require 'org-drawer-list)
|
||||
|
||||
(defcustom +org-drawer-list-resources-drawer "RESOURCES"
|
||||
"Where to add links with `+org-drawer-list-add-resource'.")
|
||||
|
||||
(defun +org-drawer-list-add-resource (url &optional title)
|
||||
"Add URL to the resource drawer of the current tree.
|
||||
The resource drawer is given by the variable
|
||||
`+org-drawer-list-resources-drawer'. If optional TITLE is given,
|
||||
format the list item as an Org link."
|
||||
(interactive
|
||||
(let* ((clipboard-url (if (string-match-p (rx (sequence bos
|
||||
(or "http"
|
||||
"gemini"
|
||||
"gopher"
|
||||
"tel"
|
||||
"mailto")))
|
||||
(current-kill 0))
|
||||
(string-trim (current-kill 0))
|
||||
(read-string "Resource URL: ")))
|
||||
(url-title (let ((clipboard-headings
|
||||
(+org-insert--get-title-and-headings clipboard-url)))
|
||||
(read-string "title (edit): "
|
||||
(completing-read
|
||||
"title: " clipboard-headings
|
||||
nil nil nil nil (car clipboard-headings))))))
|
||||
(list clipboard-url url-title)))
|
||||
(let (current-visible-mode visible-mode)
|
||||
;; XXX: This is not the "proper" way to fix the issue I was having --- I've
|
||||
;; isolated the bug to somewhere in `org-insert-item', but this fix works
|
||||
;; well enough™ for now.
|
||||
(visible-mode +1)
|
||||
(org-drawer-list-add +org-drawer-list-resources-drawer
|
||||
(org-link-make-string url title))
|
||||
(visible-mode (if current-visible-mode +1 -1))))
|
||||
|
||||
(provide '+org-drawer-list)
|
||||
;;; +org-drawer-list.el ends here
|
112
lisp/+org-wc.el
Normal file
112
lisp/+org-wc.el
Normal file
|
@ -0,0 +1,112 @@
|
|||
;;; +org-wc.el --- org-wc in the modeline -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'org-wc)
|
||||
(require '+modeline)
|
||||
(require 'cl-lib)
|
||||
|
||||
(defgroup +org-wc nil
|
||||
"Extra fast word-counting in `org-mode'"
|
||||
:group 'org-wc
|
||||
:group 'org)
|
||||
|
||||
(defvar-local +org-wc-word-count nil
|
||||
"Running total of words in this buffer.")
|
||||
|
||||
(defcustom +org-wc-update-after-funcs '(org-narrow-to-subtree
|
||||
org-narrow-to-block
|
||||
org-narrow-to-element
|
||||
org-capture-narrow)
|
||||
"Functions after which to update the word count."
|
||||
:type '(repeat function))
|
||||
|
||||
(defcustom +org-wc-deletion-idle-timer 0.25
|
||||
"Length of time, in seconds, to wait before updating word-count."
|
||||
:type 'number)
|
||||
|
||||
(defcustom +org-wc-huge-change 5000
|
||||
"Number of characters that constitute a \"huge\" insertion."
|
||||
:type 'number)
|
||||
|
||||
(defcustom +org-wc-huge-buffer 10000
|
||||
"Number of words past which we're not going to try to count."
|
||||
:type 'number)
|
||||
|
||||
(defvar +org-wc-correction -5
|
||||
"Number to add to `+org-wc-word-count', for some reason?
|
||||
`+org-wc-word-count' seems to consistently be off by 5. Thus
|
||||
this correction. (At some point I should correct the underlying
|
||||
code... probably).")
|
||||
|
||||
(defvar-local +org-wc-update-timer nil)
|
||||
|
||||
(defun +org-wc-delayed-update (&rest _)
|
||||
(if +org-wc-update-timer
|
||||
(setq +org-wc-update-timer nil)
|
||||
(setq +org-wc-update-timer
|
||||
(run-with-idle-timer +org-wc-deletion-idle-timer nil #'+org-wc-update))))
|
||||
|
||||
(defun +org-wc-force-update ()
|
||||
(interactive)
|
||||
(message "Counting words...")
|
||||
(when (timerp +org-wc-update-timer)
|
||||
(cancel-timer +org-wc-update-timer))
|
||||
(+org-wc-update)
|
||||
(message "Counting words...done"))
|
||||
|
||||
(defun +org-wc-update (&rest _) ; Needs variadic parameters, since it's advice
|
||||
(dlet ((+org-wc-counting t))
|
||||
(+org-wc-buffer)
|
||||
(force-mode-line-update)
|
||||
(setq +org-wc-update-timer nil)))
|
||||
|
||||
(defun +org-wc-changed (start end length)
|
||||
(+org-wc-delayed-update))
|
||||
|
||||
(defun +org-wc-buffer ()
|
||||
"Count the words in the buffer."
|
||||
(when (and (derived-mode-p 'org-mode)
|
||||
(not (eq +org-wc-word-count 'huge)))
|
||||
(setq +org-wc-word-count
|
||||
(cond
|
||||
((> (count-words (point-min) (point-max))
|
||||
+org-wc-huge-buffer)
|
||||
'huge)
|
||||
(t (org-word-count-aux (point-min) (point-max)))))))
|
||||
|
||||
(defvar +org-wc-counting nil
|
||||
"Are we currently counting?")
|
||||
|
||||
(defun +org-wc-recount-widen (&rest _)
|
||||
(when (and (not +org-wc-counting))
|
||||
(+org-wc-update)))
|
||||
|
||||
(defun +org-wc-modeline ()
|
||||
(cond
|
||||
((eq +org-wc-word-count 'huge) "huge")
|
||||
(+org-wc-word-count (format "%sw" (max 0 (+ +org-wc-word-count +org-wc-correction))))))
|
||||
|
||||
(define-minor-mode +org-wc-mode
|
||||
"Count words in `org-mode' buffers in the mode-line."
|
||||
:lighter ""
|
||||
:keymap (let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-c C-.") #'+org-wc-force-update)
|
||||
map)
|
||||
(if +org-wc-mode
|
||||
(progn ; turn on
|
||||
(+org-wc-buffer)
|
||||
(add-hook 'after-change-functions #'+org-wc-delayed-update nil t)
|
||||
(setq-local +modeline-position-function #'+org-wc-modeline)
|
||||
(dolist (fn +org-wc-update-after-funcs)
|
||||
(advice-add fn :after #'+org-wc-update)))
|
||||
(progn ; turn off
|
||||
(remove-hook 'after-change-functions #'+org-wc-delayed-update t)
|
||||
(kill-local-variable '+modeline-position-function)
|
||||
(dolist (fn +org-wc-update-after-funcs)
|
||||
(advice-remove fn #'+org-wc-update)))))
|
||||
|
||||
(provide '+org-wc)
|
||||
;;; +org-wc.el ends here
|
540
lisp/+org.el
540
lisp/+org.el
|
@ -2,12 +2,12 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'el-patch)
|
||||
(require 'org)
|
||||
(require 'org-element)
|
||||
(require 'ox)
|
||||
|
||||
;;; org-return-dwim - https://github.com/alphapapa/unpackaged.el
|
||||
;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
|
||||
;;; org-return-dwim - [[https://github.com/alphapapa/unpackaged.el][unpackaged]] and [[http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/][kitchin]]
|
||||
|
||||
(defun +org-element-descendant-of (type element)
|
||||
"Return non-nil if ELEMENT is a descendant of TYPE.
|
||||
|
@ -80,8 +80,13 @@ appropriate. In tables, insert a new row or end the table."
|
|||
(let* ((context (org-element-context))
|
||||
(first-item-p (eq 'plain-list (car context)))
|
||||
(itemp (eq 'item (car context)))
|
||||
(emptyp (eq (org-element-property :contents-begin context)
|
||||
(org-element-property :contents-end context)))
|
||||
(emptyp (or
|
||||
;; Empty list item (regular)
|
||||
(eq (org-element-property :contents-begin context)
|
||||
(org-element-property :contents-end context))
|
||||
;; Empty list item (definition)
|
||||
;; This seems to work, with minimal testing. -- 2022-02-17
|
||||
(looking-at " *::")))
|
||||
(item-child-p
|
||||
(+org-element-descendant-of 'item context)))
|
||||
;; The original function from unpackaged just tested the (or ...) test
|
||||
|
@ -92,7 +97,7 @@ appropriate. In tables, insert a new row or end the table."
|
|||
;; for now, it works well enough.
|
||||
(cond ((and itemp emptyp)
|
||||
(delete-region (line-beginning-position) (line-end-position))
|
||||
(insert "\n\n"))
|
||||
(insert "\n"))
|
||||
((or first-item-p
|
||||
(and itemp (not emptyp))
|
||||
item-child-p)
|
||||
|
@ -139,38 +144,41 @@ N is passed to the functions."
|
|||
Optional PREFIX argument operates on the entire buffer.
|
||||
Drawers are included with their headings."
|
||||
(interactive "P")
|
||||
(org-map-entries (lambda ()
|
||||
(org-with-wide-buffer
|
||||
;; `org-map-entries' narrows the buffer, which
|
||||
;; prevents us from seeing newlines before the
|
||||
;; current heading, so we do this part widened.
|
||||
(while (not (looking-back "\n\n" nil))
|
||||
;; Insert blank lines before heading.
|
||||
(insert "\n")))
|
||||
(let ((end (org-entry-end-position)))
|
||||
;; Insert blank lines before entry content
|
||||
(forward-line)
|
||||
(while (and (org-at-planning-p)
|
||||
(< (point) (point-max)))
|
||||
;; Skip planning lines
|
||||
(forward-line))
|
||||
(while (re-search-forward
|
||||
org-drawer-regexp end t)
|
||||
;; Skip drawers. You might think that
|
||||
;; `org-at-drawer-p' would suffice, but for
|
||||
;; some reason it doesn't work correctly when
|
||||
;; operating on hidden text. This works, taken
|
||||
;; from `org-agenda-get-some-entry-text'.
|
||||
(re-search-forward "^[ \t]*:END:.*\n?" end t)
|
||||
(goto-char (match-end 0)))
|
||||
(unless (or (= (point) (point-max))
|
||||
(org-at-heading-p)
|
||||
(looking-at-p "\n"))
|
||||
(insert "\n"))))
|
||||
t
|
||||
(if prefix
|
||||
nil
|
||||
'tree)))
|
||||
(let ((org-element-use-cache nil))
|
||||
(org-map-entries (lambda ()
|
||||
(let ((beg (org-entry-beginning-position))
|
||||
(end (org-entry-end-position)))
|
||||
(org-with-wide-buffer
|
||||
;; `org-map-entries' narrows the buffer, which
|
||||
;; prevents us from seeing newlines before the
|
||||
;; current heading, so we do this part widened.
|
||||
(while (not (looking-back "\n\n" nil))
|
||||
;; Insert blank lines before heading.
|
||||
(insert "\n")))
|
||||
|
||||
;; Insert blank lines before entry content
|
||||
(forward-line)
|
||||
(while (and (org-at-planning-p)
|
||||
(< (point) (point-max)))
|
||||
;; Skip planning lines
|
||||
(forward-line))
|
||||
(while (re-search-forward
|
||||
org-drawer-regexp end t)
|
||||
;; Skip drawers. You might think that
|
||||
;; `org-at-drawer-p' would suffice, but for
|
||||
;; some reason it doesn't work correctly when
|
||||
;; operating on hidden text. This works, taken
|
||||
;; from `org-agenda-get-some-entry-text'.
|
||||
(re-search-forward "^[ \t]*:END:.*\n?" end t)
|
||||
(goto-char (match-end 0)))
|
||||
(unless (or (= (point) (point-max))
|
||||
(org-at-heading-p)
|
||||
(looking-at-p "\n"))
|
||||
(insert "\n"))))
|
||||
t
|
||||
(if prefix
|
||||
nil
|
||||
'tree))))
|
||||
|
||||
;;; org-count-words
|
||||
|
||||
|
@ -245,46 +253,61 @@ instead of the true count."
|
|||
((use-region-p)
|
||||
(message "%d words in region"
|
||||
(+org-count-words-stupidly (region-beginning)
|
||||
(region-end))))
|
||||
(region-end))))
|
||||
(t
|
||||
(message "%d words in buffer"
|
||||
(+org-count-words-stupidly (point-min)
|
||||
(point-max))))))
|
||||
(point-max))))))
|
||||
|
||||
;;; org-insert-link-dwim - https://xenodium.com/emacs-dwim-do-what-i-mean/
|
||||
|
||||
(defun +org-insert-link-dwim ()
|
||||
(defun +org-insert--get-title-and-headings (url)
|
||||
"Retrieve title and headings from URL.
|
||||
Return as a list."
|
||||
(with-current-buffer (url-retrieve-synchronously url)
|
||||
(let ((dom (libxml-parse-html-region (point-min) (point-max))))
|
||||
(cl-remove-if
|
||||
(lambda (i) (string= i ""))
|
||||
(apply #'append (mapcar (lambda (tag)
|
||||
(mapcar #'dom-text
|
||||
(dom-by-tag dom tag)))
|
||||
'(title h1 h2 h3 h4 h5 h6)))))))
|
||||
|
||||
(defun +org-insert-link-dwim (&optional interactivep)
|
||||
"Like `org-insert-link' but with personal dwim preferences."
|
||||
(interactive)
|
||||
(interactive '(t))
|
||||
(let* ((point-in-link (org-in-regexp org-link-any-re 1))
|
||||
(clipboard-url (when (string-match-p
|
||||
(rx (sequence bos
|
||||
(or "http"
|
||||
"gemini"
|
||||
"gopher")))
|
||||
"gopher"
|
||||
"tel"
|
||||
"mailto")))
|
||||
(current-kill 0))
|
||||
(current-kill 0)))
|
||||
(region-content (when (region-active-p)
|
||||
(buffer-substring-no-properties (region-beginning)
|
||||
(region-end)))))
|
||||
(cond ((and region-content clipboard-url (not point-in-link))
|
||||
(delete-region (region-beginning) (region-end))
|
||||
(insert (org-link-make-string clipboard-url region-content)))
|
||||
((and clipboard-url (not point-in-link))
|
||||
(insert (org-link-make-string
|
||||
clipboard-url
|
||||
(read-string "title: "
|
||||
(with-current-buffer
|
||||
(url-retrieve-synchronously
|
||||
clipboard-url)
|
||||
(dom-text
|
||||
(car
|
||||
(dom-by-tag (libxml-parse-html-region
|
||||
(point-min)
|
||||
(point-max))
|
||||
'title))))))))
|
||||
(t
|
||||
(call-interactively 'org-insert-link)))))
|
||||
(region-end))))
|
||||
(org-link (when (and clipboard-url (not point-in-link))
|
||||
(org-link-make-string
|
||||
(string-trim clipboard-url)
|
||||
(or region-content
|
||||
(let ((clipboard-headings
|
||||
(+org-insert--get-title-and-headings clipboard-url)))
|
||||
(read-string "title (edit): "
|
||||
(completing-read
|
||||
"title: " clipboard-headings
|
||||
nil nil nil nil (car clipboard-headings)))))))))
|
||||
(if interactivep
|
||||
(cond ((and region-content clipboard-url (not point-in-link))
|
||||
(delete-region (region-beginning) (region-end))
|
||||
(insert org-link))
|
||||
((and clipboard-url (not point-in-link))
|
||||
(insert org-link))
|
||||
(t
|
||||
(call-interactively 'org-insert-link)))
|
||||
org-link)))
|
||||
|
||||
;;; Navigate headings with widening
|
||||
|
||||
|
@ -309,12 +332,21 @@ instead of the true count."
|
|||
|
||||
;;; Hooks & Advice
|
||||
|
||||
(defvar +org-before-save-prettify-buffer t
|
||||
"Prettify org buffers before saving.")
|
||||
|
||||
(put '+org-before-save-prettify-buffer 'safe-local-variable #'booleanp)
|
||||
|
||||
(defun +org-before-save@prettify-buffer ()
|
||||
(save-mark-and-excursion
|
||||
(mark-whole-buffer)
|
||||
;;(org-fill-paragraph nil t)
|
||||
(+org-fix-blank-lines t)
|
||||
(org-align-tags t)))
|
||||
(when +org-before-save-prettify-buffer
|
||||
(save-mark-and-excursion
|
||||
(+org-unsmartify)
|
||||
(+org-fix-blank-lines t)
|
||||
(org-align-tags t)
|
||||
(when (buffer-narrowed-p)
|
||||
(goto-char (point-min))
|
||||
(forward-line 1)
|
||||
(org-narrow-to-subtree)))))
|
||||
|
||||
(defun +org-delete-backward-char (N)
|
||||
"Keep tables aligned while deleting N characters backward.
|
||||
|
@ -343,7 +375,7 @@ the deletion might narrow the column."
|
|||
;; `org-pretty-entities-include-sub-superscripts', which really does exactly
|
||||
;; what I wanted.
|
||||
|
||||
(defface +org-script-markers '((t :inherit shadow))
|
||||
(defface +org-script-markers '((t (:inherit shadow)))
|
||||
"Face to be used for sub/superscripts markers i.e., ^, _, {, }.")
|
||||
|
||||
;; Hiding the super and subscript markers is extremely annoying
|
||||
|
@ -383,7 +415,7 @@ the deletion might narrow the column."
|
|||
;; (nth (if table-p 2 0) org-script-display)
|
||||
(nth 2 org-script-display)))
|
||||
(put-text-property (match-beginning 2) (match-end 2)
|
||||
'face 'vz/org-script-markers)
|
||||
'face '+org-script-markers)
|
||||
(when (and (eq (char-after (match-beginning 3)) ?{)
|
||||
(eq (char-before (match-end 3)) ?}))
|
||||
(put-text-property (match-beginning 3) (1+ (match-beginning 3))
|
||||
|
@ -398,5 +430,377 @@ the deletion might narrow the column."
|
|||
"Notify the user of what phone NUMBER to call."
|
||||
(message "Call: %s" number))
|
||||
|
||||
(defun +org-sms-open (number _)
|
||||
"Notify the user of what phone NUMBER to text."
|
||||
(message "SMS: %s" number))
|
||||
|
||||
;; Make a horizontal rule!
|
||||
|
||||
(defun +org-horizontal-rule ()
|
||||
"Make a horizontal rule after the current line."
|
||||
(interactive nil org-mode)
|
||||
(unless (eq (line-beginning-position) (line-end-position))
|
||||
(end-of-line)
|
||||
(newline))
|
||||
(dotimes (_ fill-column)
|
||||
(insert "-")))
|
||||
|
||||
;; Follow links, DWIM style
|
||||
|
||||
(defun +org-open-at-point-dwim (&optional arg)
|
||||
"Open thing at point, or if there isn't something, list things."
|
||||
(interactive "P")
|
||||
(save-excursion
|
||||
(let* ((this-char-type (org-element-type (org-element-context)))
|
||||
(prev-char-type (ignore-errors
|
||||
(save-excursion
|
||||
(backward-char)
|
||||
(org-element-type (org-element-context)))))
|
||||
(types '(citation citation-reference clock comment comment-block
|
||||
footnote-definition footnote-reference headline
|
||||
inline-src-block inlinetask keyword link
|
||||
node-property planning src-block timestamp))
|
||||
(type this-char-type))
|
||||
(when (and (memq this-char-type types) (memq prev-char-type types))
|
||||
(backward-char)
|
||||
(setq type prev-char-type)) ; what the fuckckckckck
|
||||
;; Okay, so this ^ is pretty janky and doesn't /really/ work that well,
|
||||
;; especially on DEADLINE (and probably SCHEDULED) lines. However, since
|
||||
;; I really just want to open the list of URLs /most of the time/, I'm
|
||||
;; fixing it like this instead.
|
||||
(unless (and (memq type types)
|
||||
(ignore-errors (org-open-at-point arg)
|
||||
t))
|
||||
(while (not
|
||||
(progn
|
||||
(org-back-to-heading)
|
||||
(car (org-offer-links-in-entry (current-buffer) (point) 1))))
|
||||
(org-up-heading-all 1))
|
||||
(org-open-at-point arg)))))
|
||||
|
||||
;;; Open local HTML files with `browse-url'
|
||||
|
||||
(defun +org-open-html (file-path link-string)
|
||||
"Open FILE-PATH with `browse-url'.
|
||||
This function is intended to use with `org-file-apps'. See the
|
||||
documentation of that function for a description of the two
|
||||
arguments here, FILE-PATH and LINK-STRING."
|
||||
(message "Opening %s (%s)..." file-path link-string)
|
||||
(browse-url file-path))
|
||||
|
||||
(defun +org-insert-horizontal-rule (prefix)
|
||||
"Insert a horizontal rule (-----) after the current line.
|
||||
With PREFIX, insert before the current line."
|
||||
(interactive "P")
|
||||
(if prefix
|
||||
(move-beginning-of-line nil)
|
||||
(move-end-of-line nil)
|
||||
(forward-line 1))
|
||||
(insert "-----\n"))
|
||||
|
||||
;;; Make code snippets in org-mode easier to type
|
||||
;; http://mbork.pl/2022-01-17_Making_code_snippets_in_Org-mode_easier_to_type
|
||||
|
||||
(defun +org-insert-backtick ()
|
||||
"Insert a backtick using `org-self-insert-command'."
|
||||
(interactive)
|
||||
(setq last-command-event ?`)
|
||||
(call-interactively #'org-self-insert-command))
|
||||
|
||||
(defvar-local +org-insert-tilde-language nil
|
||||
"Default language name in the current Org file.
|
||||
If nil, `org-insert-tilde' after 2 tildes inserts an \"example\"
|
||||
block. If a string, it inserts a \"src\" block with the given
|
||||
language name.")
|
||||
|
||||
(defun +org-insert-tilde ()
|
||||
"Insert a tilde using `org-self-insert-command'."
|
||||
(interactive)
|
||||
(if (string= (buffer-substring-no-properties (- (point) 3) (point))
|
||||
"\n~~")
|
||||
(progn (delete-char -2)
|
||||
(if +org-insert-tilde-language
|
||||
(insert (format "#+begin_src %s\n#+end_src"
|
||||
+org-insert-tilde-language))
|
||||
(insert "#+begin_example\n#+end_example"))
|
||||
(forward-line -1)
|
||||
(if (string= +org-insert-tilde-language "")
|
||||
(move-end-of-line nil)
|
||||
;;(org-edit-special) ; Useful really only with splits.
|
||||
))
|
||||
(setq last-command-event ?~)
|
||||
(call-interactively #'org-self-insert-command)))
|
||||
|
||||
;;; Better org faces
|
||||
;; see `org-emphasis-alist'
|
||||
|
||||
(defface org-bold '((t (:weight bold)))
|
||||
"Bold face in `org-mode' documents.")
|
||||
|
||||
(defface org-italic '((t (:slant italic)))
|
||||
"Italic face in `org-mode' documents.")
|
||||
|
||||
(defface org-underline '((t (:underline t)))
|
||||
"Underline face in `org-mode' documents.")
|
||||
|
||||
(defface org-strikethrough '((t (:strike-through t)))
|
||||
"Strike-through face for `org-mode' documents.")
|
||||
|
||||
;; `org-verbatim' and `org-code' are apparently already things, so we skip them
|
||||
;; here.
|
||||
|
||||
;;; Copy org trees as HTML
|
||||
|
||||
;; Thanks to Oleh Krehel, via [[https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard][this StackExchange question]].
|
||||
(defun +org-export-clip-to-html
|
||||
(&optional async subtreep visible-only body-only ext-plist post-process)
|
||||
"Export region to HTML, and copy it to the clipboard.
|
||||
Arguments ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST,
|
||||
and POST-PROCESS are passed to `org-export-to-file'."
|
||||
(interactive) ; XXX: hould this be interactive?
|
||||
(message "Exporting Org to HTML...")
|
||||
(let ((org-tmp-file "/tmp/org.html"))
|
||||
(org-export-to-file 'html org-tmp-file
|
||||
async subtreep visible-only body-only ext-plist post-process)
|
||||
(start-process "xclip" "*xclip*"
|
||||
"xclip" "-verbose"
|
||||
"-i" org-tmp-file
|
||||
"-t" "text/html"
|
||||
"-selection" "clipboard"))
|
||||
(message "Exporting Org to HTML...done."))
|
||||
|
||||
;; Specialized functions
|
||||
(defun +org-export-clip-subtree-to-html ()
|
||||
"Export current subtree to HTML."
|
||||
(interactive)
|
||||
(+org-export-clip-to-html nil :subtree))
|
||||
|
||||
;;; Unsmartify quotes and dashes and stuff.
|
||||
(defun +org-unsmartify ()
|
||||
"Replace \"smart\" punctuation with their \"dumb\" counterparts."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "[“”‘’–—]" nil t)
|
||||
(let ((replace (pcase (match-string 0)
|
||||
((or "“" "”") "\"")
|
||||
((or "‘" "’") "'")
|
||||
("–" "--")
|
||||
("—" "---"))))
|
||||
(replace-match replace nil nil)))))
|
||||
|
||||
;;; go forward and backward in the tree, ~ cleanly ~
|
||||
;; https://stackoverflow.com/a/25201697/10756297
|
||||
|
||||
(defun +org-show-next-heading-tidily ()
|
||||
"Show next entry, keeping other entries closed."
|
||||
(interactive)
|
||||
(if (save-excursion (end-of-line) (outline-invisible-p))
|
||||
(progn (org-show-entry) (show-children))
|
||||
(outline-next-heading)
|
||||
(unless (and (bolp) (org-on-heading-p))
|
||||
(org-up-heading-safe)
|
||||
(hide-subtree)
|
||||
(user-error "Boundary reached"))
|
||||
(org-overview)
|
||||
(org-reveal t)
|
||||
(org-show-entry)
|
||||
(recenter-top-bottom)
|
||||
(show-children)
|
||||
(recenter-top-bottom 1)))
|
||||
|
||||
(defun +org-show-previous-heading-tidily ()
|
||||
"Show previous entry, keeping other entries closed."
|
||||
(interactive)
|
||||
(let ((pos (point)))
|
||||
(outline-previous-heading)
|
||||
(unless (and (< (point) pos) (bolp) (org-on-heading-p))
|
||||
(goto-char pos)
|
||||
(hide-subtree)
|
||||
(user-error "Boundary reached"))
|
||||
(org-overview)
|
||||
(org-reveal t)
|
||||
(org-show-entry)
|
||||
(recenter-top-bottom)
|
||||
(show-children)
|
||||
(recenter-top-bottom 1)))
|
||||
|
||||
;;; Make `org-flag-region' (which folds subtrees) recognize
|
||||
;; [[https://teddit.net/r/orgmode/comments/u3du0v/how_to_make_orgcycle_respect_and_always_show_the/][from u/yantar92]]
|
||||
|
||||
;; (advice-add 'org-flag-region :around #'org-flag-region@unfold-page-breaks)
|
||||
(defun org-flag-region@unfold-page-breaks (oldfun from to flag &optional spec)
|
||||
"ADVICE to unfold all the page-break lines inside a folded region."
|
||||
(funcall oldfun from to flag spec)
|
||||
(when (and flag (not (eq 'visible spec)))
|
||||
(org-with-point-at from
|
||||
(while (re-search-forward "\n\u000c\n" to t)
|
||||
(org-flag-region (match-beginning 0) (match-end 0) t 'visible)))))
|
||||
|
||||
;;; Emacs 28+: wrap on hyphens
|
||||
;; https://emacs.stackexchange.com/a/71342/37239
|
||||
|
||||
(defcustom +org-category-table (let ((table (copy-category-table)))
|
||||
(modify-category-entry ?- ?| table)
|
||||
table)
|
||||
"Character category table for `org-mode'."
|
||||
:type 'sexp)
|
||||
|
||||
(defun +org-wrap-on-hyphens ()
|
||||
"Soft-wrap `org-mode' buffers on spaces and hyphens."
|
||||
(set-category-table +org-category-table)
|
||||
(setq-local word-wrap-by-category t))
|
||||
|
||||
|
||||
;;; Inhibit hooks on `org-agenda'
|
||||
;; It's really annoying when I call `org-agenda' and five hundred Ispell
|
||||
;; processes are created because I have `flyspell-mode' in the hook. This mode
|
||||
;; inhibits those hooks when entering the agenda, but runs them when opening the
|
||||
;; actual buffer.
|
||||
|
||||
(defun +org-agenda-inhibit-hooks (fn &rest r)
|
||||
"Advice to inhibit hooks when entering `org-agenda'."
|
||||
(dlet ((org-mode-hook nil)) ; I'm not sure if `dlet' is strictly needed
|
||||
(apply fn r)))
|
||||
|
||||
(defvar-local +org-hook-has-run-p nil
|
||||
"Whether `org-mode-hook' has run in the current buffer.")
|
||||
|
||||
(defun +org-agenda-switch-run-hooks (&rest _)
|
||||
"Advice to run `org-mode-hook' when entering org-mode.
|
||||
This should only fire when switching to a buffer from `org-agenda'."
|
||||
(unless +org-hook-has-run-p
|
||||
(run-mode-hooks 'org-mode-hook)
|
||||
(setq +org-hook-has-run-p t)))
|
||||
|
||||
(define-minor-mode +org-agenda-inhibit-hooks-mode
|
||||
"Inhibit `org-mode-hook' when opening `org-agenda'."
|
||||
:lighter ""
|
||||
:global t
|
||||
(if +org-agenda-inhibit-hooks-mode
|
||||
(progn ; Enable
|
||||
(advice-add 'org-agenda :around #'+org-agenda-inhibit-hooks)
|
||||
(advice-add 'org-agenda-switch-to :after #'+org-agenda-switch-run-hooks))
|
||||
(progn ; Disable
|
||||
(advice-remove 'org-agenda #'+org-agenda-inhibit-hooks)
|
||||
(advice-remove 'org-agenda-switch-to #'+org-agenda-switch-run-hooks))))
|
||||
|
||||
|
||||
;;; "Fix" `org-align-tags'
|
||||
|
||||
(el-patch-defun org-align-tags (&optional all)
|
||||
"Align tags in current entry.
|
||||
When optional argument ALL is non-nil, align all tags in the
|
||||
visible part of the buffer."
|
||||
(let ((get-indent-column
|
||||
(lambda ()
|
||||
(let ((offset (el-patch-swap
|
||||
(if (bound-and-true-p org-indent-mode)
|
||||
(* (1- org-indent-indentation-per-level)
|
||||
(1- (org-current-level)))
|
||||
0)
|
||||
0)))
|
||||
(+ org-tags-column
|
||||
(if (> org-tags-column 0) (- offset) offset))))))
|
||||
(if (and (not all) (org-at-heading-p))
|
||||
(org--align-tags-here (funcall get-indent-column))
|
||||
(save-excursion
|
||||
(if all
|
||||
(progn
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-tag-line-re nil t)
|
||||
(org--align-tags-here (funcall get-indent-column))))
|
||||
(org-back-to-heading t)
|
||||
(org--align-tags-here (funcall get-indent-column)))))))
|
||||
|
||||
;;; Meta-return
|
||||
|
||||
(defun +org-meta-return (&optional arg)
|
||||
"Insert a new line, or wrap a region in a table.
|
||||
See `org-meta-return', but `+org-return-dwim' does most of the
|
||||
stuff I would want out of that function already.
|
||||
|
||||
When called with a prefix ARG, will still unconditionally call
|
||||
`org-insert-heading'."
|
||||
(interactive "P")
|
||||
(org-fold-check-before-invisible-edit 'insert)
|
||||
(or (run-hook-with-args-until-success 'org-metareturn-hook) ; Allow customizations
|
||||
(call-interactively (cond (arg #'org-insert-heading)
|
||||
((org-at-table-p) #'org-table-wrap-region)
|
||||
(t #'org-return)))))
|
||||
|
||||
|
||||
;;; move org archives to a dedicated file
|
||||
(defun +org-archive-monthwise (archive-file)
|
||||
(if (file-exists-p archive-file)
|
||||
(with-current-buffer (find-file-noselect archive-file)
|
||||
(let ((dir (file-name-directory (file-truename archive-file)))
|
||||
(prog (make-progress-reporter (format "Archiving from %s..." archive-file)))
|
||||
(keep-going t))
|
||||
(goto-char (point-min))
|
||||
(while keep-going
|
||||
(when-let* ((time (or (org-entry-get (point) "ARCHIVE_TIME")
|
||||
(org-get-deadline-time (point))))
|
||||
(parsed-time (and time
|
||||
(org-parse-time-string time)))
|
||||
(refile-target (format "%s%02d-%02d.org"
|
||||
dir
|
||||
(decoded-time-year parsed-time)
|
||||
(decoded-time-month parsed-time)))
|
||||
(title-str (format "#+title: Archive for %02d-%02d (%s)\n\n"
|
||||
(decoded-time-year parsed-time)
|
||||
(decoded-time-month parsed-time)
|
||||
(file-truename archive-file))))
|
||||
(unless (file-exists-p refile-target)
|
||||
(with-current-buffer (find-file-noselect refile-target)
|
||||
(insert title-str)
|
||||
(save-buffer)))
|
||||
(org-refile nil nil (list ""
|
||||
refile-target
|
||||
nil
|
||||
0)))
|
||||
(progress-reporter-update prog)
|
||||
(org-next-visible-heading 1)
|
||||
(when (>= (point) (point-max))
|
||||
(setq keep-going nil)))))
|
||||
(message "Archive file %s does not exist!" archive-file)))
|
||||
|
||||
|
||||
;;; el-patch
|
||||
|
||||
(el-patch-defun org-format-outline-path (path &optional width prefix separator)
|
||||
"Format the outline path PATH for display.
|
||||
WIDTH is the maximum number of characters that is available.
|
||||
PREFIX is a prefix to be included in the returned string,
|
||||
such as the file name.
|
||||
SEPARATOR is inserted between the different parts of the path,
|
||||
the default is \"/\"."
|
||||
(setq width (or width 79))
|
||||
(setq path (delq nil path))
|
||||
(unless (> width 0)
|
||||
(user-error "Argument `width' must be positive"))
|
||||
(setq separator (or separator "/"))
|
||||
(let* ((org-odd-levels-only nil)
|
||||
(fpath (concat
|
||||
prefix (and prefix path separator)
|
||||
(mapconcat
|
||||
(lambda (s) (replace-regexp-in-string "[ \t]+\\'" "" s))
|
||||
(cl-loop for head in path
|
||||
for n from 0
|
||||
collect (el-patch-swap
|
||||
(org-add-props
|
||||
head nil 'face
|
||||
(nth (% n org-n-level-faces) org-level-faces))
|
||||
head))
|
||||
separator))))
|
||||
(when (> (length fpath) width)
|
||||
(if (< width 7)
|
||||
;; It's unlikely that `width' will be this small, but don't
|
||||
;; waste characters by adding ".." if it is.
|
||||
(setq fpath (substring fpath 0 width))
|
||||
(setf (substring fpath (- width 2)) "..")))
|
||||
fpath))
|
||||
|
||||
|
||||
(provide '+org)
|
||||
;;; +org.el ends here
|
||||
|
|
29
lisp/+ox.el
Normal file
29
lisp/+ox.el
Normal file
|
@ -0,0 +1,29 @@
|
|||
;;; +ox.el --- org-export helpers -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ox)
|
||||
|
||||
;;; Run hooks before doing any exporting at all
|
||||
|
||||
(defcustom +org-export-pre-hook nil
|
||||
"Functions to run /before/ `org-export-as' does anything.
|
||||
These will run on the buffer about to be exported, NOT a copy."
|
||||
:type 'hook)
|
||||
|
||||
(defun +org-export-pre-run-hooks (&rest _)
|
||||
"Run hooks in `+org-export-pre-hook'."
|
||||
(run-hooks '+org-export-pre-hook))
|
||||
|
||||
(defun +org-export-pre-hooks-insinuate ()
|
||||
"Advise `org-export-as' to run `+org-export-pre-hook'."
|
||||
(advice-add 'org-export-as :before #'+org-export-pre-run-hooks))
|
||||
|
||||
(defun +org-export-pre-hooks-remove ()
|
||||
"Remove pre-hook advice on `org-export-as'."
|
||||
(advice-remove 'org-export-as #'+org-export-pre-run-hooks))
|
||||
|
||||
(provide '+ox)
|
||||
;;; +ox.el ends here
|
26
lisp/+paredit.el
Normal file
26
lisp/+paredit.el
Normal file
|
@ -0,0 +1,26 @@
|
|||
;;; +paredit.el --- bespoke paredit stuffs -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require '+emacs) ; `+backward-kill-word-wrapper'
|
||||
|
||||
(defun +paredit--backward-kill-word (&optional n)
|
||||
"Perform `paredit-backward-kill-word' N times."
|
||||
(interactive "p")
|
||||
(dotimes (_ (or n 1))
|
||||
(paredit-backward-kill-word)))
|
||||
|
||||
(defun +paredit-backward-kill-word (&optional arg)
|
||||
"Kill a word backward using `paredit-backward-kill-word'.
|
||||
Wrapped in `+backward-kill-word-wrapper', which see.
|
||||
|
||||
Prefix ARG means to just call `paredit-backward-kill-word'."
|
||||
;; Of course, `paredit-backward-kill-word' doesn't TAKE an argument ... :///
|
||||
;; So I had to write the wrapper above.
|
||||
(interactive)
|
||||
(+backward-kill-word-wrapper #'+paredit--backward-kill-word arg))
|
||||
|
||||
(provide '+paredit)
|
||||
;;; +paredit.el ends here
|
38
lisp/+pdf-tools.el
Normal file
38
lisp/+pdf-tools.el
Normal file
|
@ -0,0 +1,38 @@
|
|||
;;; +pdf-tools.el --- Extras for the excellent pdf-tools' -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; XXX: The way I'm dispatching browsers here is /very/ down-and-dirty. It
|
||||
;; needs to be much improved.
|
||||
|
||||
(defun +pdf-view-open-all-pagelinks (&optional browse-url-func)
|
||||
"Open all the links on this page of a PDF.
|
||||
BROWSE-URL-FUNC overrides the default `browse-url'."
|
||||
(interactive)
|
||||
(let ((links (pdf-info-pagelinks (pdf-view-current-page)))
|
||||
(browse-url-func (or browse-url-func #'browse-url))
|
||||
(seen))
|
||||
(dolist (link links)
|
||||
(when-let* ((uri (alist-get 'uri link))
|
||||
(_ (not (member uri seen))))
|
||||
(push uri seen)
|
||||
(funcall browse-url-func uri)))))
|
||||
|
||||
(defun +pdf-view-open-links-in-chrome ()
|
||||
"Open all links on this PDF page in Chrome.
|
||||
See also `+pdf-view-open-all-pagelinks'."
|
||||
(interactive)
|
||||
(+pdf-view-open-all-pagelinks #'browse-url-chrome))
|
||||
|
||||
(defun +pdf-view-position (&optional spacer)
|
||||
"Return the page we're on for the modeline."
|
||||
(when (derived-mode-p 'pdf-view-mode)
|
||||
(format "%sp.%s/%s"
|
||||
(or spacer (bound-and-true-p +modeline-default-spacer) " ")
|
||||
(pdf-view-current-page)
|
||||
(pdf-info-number-of-pages))))
|
||||
|
||||
(provide '+pdf-tools)
|
||||
;;; +pdf-tools.el ends here
|
|
@ -5,9 +5,10 @@
|
|||
;;(require 'scratch)
|
||||
|
||||
(defun +scratch-immortal ()
|
||||
"Bury, don't kill \"*scratc*\" buffer.
|
||||
"Bury, don't kill \"*scratch*\" buffer.
|
||||
For `kill-buffer-query-functions'."
|
||||
(if (eq (current-buffer) (get-buffer "*scratch*"))
|
||||
(if (or (eq (current-buffer) (get-buffer "*scratch*"))
|
||||
(eq (current-buffer) (get-buffer "*text*")))
|
||||
(progn (bury-buffer)
|
||||
nil)
|
||||
t))
|
||||
|
@ -24,6 +25,53 @@ For `kill-buffer-query-functions'."
|
|||
(next-line 2))
|
||||
(rename-buffer (concat "*scratch<" mode ">*") t)))
|
||||
|
||||
(defun +scratch-fortune ()
|
||||
(let* ((fmt (if (executable-find "fmt")
|
||||
(format "| fmt -%d -s" (- fill-column 2))
|
||||
""))
|
||||
(s (string-trim
|
||||
(if (executable-find "fortune")
|
||||
(shell-command-to-string (concat "fortune -s" fmt))
|
||||
"ABANDON ALL HOPE YE WHO ENTER HERE"))))
|
||||
(concat (replace-regexp-in-string "^" ";; " s)
|
||||
"\n\n")))
|
||||
|
||||
;; [[https://old.reddit.com/r/emacs/comments/ui1q41/weekly_tips_tricks_c_thread/i7ef4xg/][u/bhrgunatha]]
|
||||
(defun +scratch-text-scratch ()
|
||||
"Create a \"*text*\" scratch buffer in Text mode."
|
||||
(with-current-buffer (get-buffer-create "*text*")
|
||||
(text-mode)))
|
||||
|
||||
(defcustom +scratch-buffers '("*text*" "*scratch*")
|
||||
"Scratch buffers.")
|
||||
|
||||
(defvar +scratch-last-non-scratch-buffer nil
|
||||
"Last buffer that wasn't a scratch buffer.")
|
||||
|
||||
(defun +scratch-toggle (buffer)
|
||||
"Switch to BUFFER, or to the previous (non-scratch) buffer."
|
||||
(if (or (null +scratch-last-non-scratch-buffer)
|
||||
(not (member (buffer-name (current-buffer)) +scratch-buffers)))
|
||||
;; Switch to a scratch buffer
|
||||
(progn
|
||||
(setq +scratch-last-non-scratch-buffer (current-buffer))
|
||||
(switch-to-buffer buffer))
|
||||
;; Switch away from scratch buffer ...
|
||||
(if (equal (get-buffer-create buffer) (current-buffer))
|
||||
;; to the original buffer
|
||||
(switch-to-buffer +scratch-last-non-scratch-buffer)
|
||||
;; to another scratch
|
||||
(switch-to-buffer buffer))))
|
||||
|
||||
(defun +scratch-switch-to-scratch ()
|
||||
"Switch to scratch buffer."
|
||||
(interactive)
|
||||
(+scratch-toggle "*scratch*"))
|
||||
|
||||
(defun +scratch-switch-to-text ()
|
||||
"Switch to text buffer."
|
||||
(interactive)
|
||||
(+scratch-toggle "*text*"))
|
||||
|
||||
(provide '+scratch)
|
||||
;;; +scratch.el ends here
|
||||
|
|
218
lisp/+setup.el
218
lisp/+setup.el
|
@ -23,58 +23,194 @@
|
|||
(require 'el-patch)
|
||||
(require 'setup)
|
||||
(require 'straight)
|
||||
(require 'cl-lib)
|
||||
|
||||
(defun +setup-warn (message &rest args)
|
||||
"Warn the user that something bad happened in `setup'."
|
||||
(display-warning 'setup (format message args)))
|
||||
|
||||
(defun +setup-wrap-to-demote-errors (body name)
|
||||
"Wrap BODY in a `with-demoted-errors' block.
|
||||
This behavior is prevented if `setup-attributes' contains the
|
||||
symbol `without-error-demotion'.
|
||||
|
||||
This function differs from `setup-wrap-to-demote-errors' in that
|
||||
it includes the NAME of the setup form in the warning output."
|
||||
(if (memq 'without-error-demotion setup-attributes)
|
||||
body
|
||||
`(with-demoted-errors ,(format "Error in setup form on line %d (%s): %%S"
|
||||
(line-number-at-pos)
|
||||
name)
|
||||
,body)))
|
||||
|
||||
|
||||
;;; New forms
|
||||
|
||||
(setup-define :quit
|
||||
'setup-quit
|
||||
:documentation "Quit the current `setup' form.
|
||||
Good for commenting.")
|
||||
|
||||
(setup-define :face
|
||||
(lambda (face spec)
|
||||
`(custom-set-faces '(,face ,spec 'now "Customized by `setup'.")))
|
||||
(lambda (face spec)
|
||||
`(custom-set-faces (list ,face ,spec 'now "Customized by `setup'.")))
|
||||
:documentation "Customize FACE with SPEC using `custom-set-faces'."
|
||||
:repeatable t)
|
||||
|
||||
(setup-define :load-after
|
||||
(lambda (&rest features)
|
||||
(let ((body `(require ',(setup-get 'feature))))
|
||||
(dolist (feature (nreverse features))
|
||||
(setq body `(with-eval-after-load ',feature ,body)))
|
||||
body))
|
||||
(lambda (&rest features)
|
||||
(let ((body `(require ',(setup-get 'feature))))
|
||||
(dolist (feature (nreverse features))
|
||||
(setq body `(with-eval-after-load ',feature ,body)))
|
||||
body))
|
||||
:documentation "Load the current feature after FEATURES.")
|
||||
|
||||
(setup-define :also-straight
|
||||
(lambda (recipe) `(setup (:straight ,recipe)))
|
||||
:documentation
|
||||
"Install RECIPE with `straight-use-package', after loading FEATURE."
|
||||
:repeatable t
|
||||
:after-loaded t)
|
||||
(setup-define :load-from
|
||||
(lambda (path)
|
||||
`(let ((path* (expand-file-name ,path)))
|
||||
(if (file-exists-p path*)
|
||||
(add-to-list 'load-path path*)
|
||||
,(setup-quit))))
|
||||
:documentation "Add PATH to load path.
|
||||
This macro can be used as NAME, and it will replace itself with
|
||||
the nondirectory part of PATH.
|
||||
If PATH does not exist, abort the evaluation."
|
||||
:shorthand (lambda (args)
|
||||
(intern
|
||||
(file-name-nondirectory
|
||||
(directory-file-name (cadr args))))))
|
||||
|
||||
(setup-define :straight
|
||||
(lambda (recipe)
|
||||
`(unless (ignore-errors (straight-use-package ',recipe))
|
||||
,(setup-quit)))
|
||||
:documentation
|
||||
"Install RECIPE with `straight-use-package'.
|
||||
This macro can be used as HEAD, and will replace itself with the
|
||||
first RECIPE's package."
|
||||
:repeatable t
|
||||
:shorthand (lambda (sexp)
|
||||
(let ((recipe (cadr sexp)))
|
||||
(if (consp recipe)
|
||||
(car recipe)
|
||||
recipe))))
|
||||
|
||||
(setup-define :straight-when
|
||||
(lambda (recipe condition)
|
||||
`(unless (and ,condition
|
||||
(straight-use-package ',recipe))
|
||||
(setup-define :needs
|
||||
(lambda (executable)
|
||||
`(unless (executable-find ,executable)
|
||||
,(setup-quit)))
|
||||
:documentation "If EXECUTABLE is not in the path, stop here."
|
||||
:repeatable 1)
|
||||
|
||||
|
||||
;;; Package integrations
|
||||
|
||||
;;; Straight.el
|
||||
|
||||
(defun setup--straight-handle-arg (arg var)
|
||||
(cond
|
||||
((and (boundp var) (symbol-value var)) t)
|
||||
((keywordp arg) (set var t))
|
||||
((functionp arg) (set var nil) (funcall arg))
|
||||
((listp arg) (set var nil) arg)))
|
||||
|
||||
(with-eval-after-load 'straight
|
||||
(setup-define :straight
|
||||
(lambda (recipe &rest predicates)
|
||||
(let* ((skp (make-symbol "straight-keyword-p"))
|
||||
(straight-use-p
|
||||
(cl-mapcar
|
||||
(lambda (f) (setup--straight-handle-arg f skp))
|
||||
predicates))
|
||||
(form `(unless (and ,@straight-use-p
|
||||
(condition-case e
|
||||
(straight-use-package ',recipe)
|
||||
(error
|
||||
(+setup-warn ":straight error: %S"
|
||||
',recipe)
|
||||
,(setup-quit))
|
||||
(:success t)))
|
||||
,(setup-quit))))
|
||||
;; Keyword arguments --- :quit is special and should short-circuit
|
||||
(if (memq :quit predicates)
|
||||
(setq form `,(setup-quit))
|
||||
;; Otherwise, handle the rest of them ...
|
||||
(when-let ((after (cadr (memq :after predicates))))
|
||||
(setq form `(with-eval-after-load ,(if (eq after t)
|
||||
(setup-get 'feature)
|
||||
after)
|
||||
,form))))
|
||||
;; Finally ...
|
||||
form))
|
||||
:documentation "Install RECIPE with `straight-use-package'.
|
||||
If PREDICATES are given, only install RECIPE if all of them return non-nil.
|
||||
The following keyword arguments are also recognized:
|
||||
- :quit --- immediately stop evaluating. Good for commenting.
|
||||
- :after FEATURE --- only install RECIPE after FEATURE is loaded.
|
||||
If FEATURE is t, install RECIPE after the current feature."
|
||||
:repeatable nil
|
||||
:indent 1
|
||||
:shorthand (lambda (sexp)
|
||||
(let ((recipe (cadr sexp)))
|
||||
(or (car-safe recipe) recipe)))))
|
||||
|
||||
;;; Apheleia
|
||||
|
||||
(setup-define :apheleia
|
||||
(lambda (name formatter &optional mode -pend)
|
||||
(let* ((mode (or mode (setup-get 'mode)))
|
||||
(current-formatters (and -pend
|
||||
(alist-get mode apheleia-formatters))))
|
||||
`(with-eval-after-load 'apheleia
|
||||
(setf (alist-get ',name apheleia-formatters)
|
||||
,formatter)
|
||||
(setf (alist-get ',mode apheleia-mode-alist)
|
||||
',(pcase -pend
|
||||
(:append (append (ensure-list current-formatters)
|
||||
(list name)))
|
||||
(:prepend (cons name (ensure-list current-formatters)))
|
||||
('nil name)
|
||||
(_ (error "Improper `:apheleia' -PEND argument")))))))
|
||||
:documentation
|
||||
"Install RECIPE with `straight-use-package' when CONDITION is met.
|
||||
If CONDITION is false, or if `straight-use-package' fails, stop
|
||||
evaluating the body. This macro can be used as HEAD, and will
|
||||
replace itself with the RECIPE's package."
|
||||
:repeatable 2
|
||||
:indent 1
|
||||
:shorthand (lambda (sexp)
|
||||
(let ((recipe (cadr sexp)))
|
||||
(if (consp recipe) (car recipe) recipe))))
|
||||
"Register a formatter to `apheleia''s lists.
|
||||
NAME is the name given to the formatter in `apheleia-formatters'
|
||||
and `apheleia-mode-alist'. FORMATTER is the command paired with
|
||||
NAME in `apheleia-formatters'. MODE is the mode or modes to add
|
||||
NAME to in `apheleia-mode-alist'. If MODE is not given or nil,
|
||||
use the setup form's MODE. Optional argument -PEND can be one of
|
||||
`:append' or `:prepend', and if given will append or prepend the
|
||||
given NAME to the current formatters for the MODE in
|
||||
`apheleia-mode-alist', rather than replace them (the default).
|
||||
|
||||
Example:
|
||||
(setup
|
||||
(:apheleia isort (\"isort\" \"--stdout\" \"-\")
|
||||
python-mode))
|
||||
; =>
|
||||
(progn
|
||||
(setf (alist-get 'isort apheleia-formatters)
|
||||
'(\"isort\" \"--stdout\" \"-\"))
|
||||
(setf (alist-get 'python-mode apheleia-mode-alist)
|
||||
'isort))
|
||||
|
||||
This form cannot be repeated, and it cannot be used as HEAD.")
|
||||
|
||||
|
||||
;;; Redefines of `setup' forms
|
||||
|
||||
(setup-define :bind-into
|
||||
(lambda (feature-or-map &rest rest)
|
||||
(cl-loop for f/m in (ensure-list feature-or-map)
|
||||
collect (if (string-match-p "-map\\'" (symbol-name f/m))
|
||||
`(:with-map ,f/m (:bind ,@rest))
|
||||
`(:with-feature ,f/m (:bind ,@rest)))
|
||||
into forms
|
||||
finally return `(progn ,@forms)))
|
||||
:documentation "Bind into keys into the map(s) of FEATURE-OR-MAP.
|
||||
FEATURE-OR-MAP can be a feature or map name or a list of them.
|
||||
The arguments REST are handled as by `:bind'."
|
||||
:debug '(sexp &rest form sexp)
|
||||
:indent 1)
|
||||
|
||||
(setup-define :require
|
||||
(lambda (&rest features)
|
||||
(require 'cl-lib)
|
||||
(if features
|
||||
`(progn ,@(cl-loop for feature in features collect
|
||||
`(unless (require ',feature nil t)
|
||||
,(setup-quit))))
|
||||
`(unless (require ',(setup-get 'feature) nil t)
|
||||
,(setup-quit))))
|
||||
:documentation "Try to require FEATURE, or stop evaluating body.
|
||||
This macro can be used as NAME, and it will replace itself with
|
||||
the first FEATURE."
|
||||
:repeatable nil
|
||||
:shorthand #'cadr)
|
||||
|
||||
(provide '+setup)
|
||||
;;; +setup.el ends here
|
||||
|
|
51
lisp/+shr.el
Normal file
51
lisp/+shr.el
Normal file
|
@ -0,0 +1,51 @@
|
|||
;;; +shr.el --- SHR extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;; [[https://github.com/oantolin/emacs-config/blob/master/my-lisp/shr-heading.el][shr-heading]], by oantolin
|
||||
|
||||
(defun +shr-heading-next (&optional arg)
|
||||
"Move forward by ARG headings (any h1-h4).
|
||||
If ARG is negative move backwards, ARG defaults to 1."
|
||||
(interactive "p")
|
||||
(unless arg (setq arg 1))
|
||||
(catch 'return
|
||||
(dotimes (_ (abs arg))
|
||||
(when (> arg 0) (end-of-line))
|
||||
(if-let ((match
|
||||
(funcall (if (> arg 0)
|
||||
#'text-property-search-forward
|
||||
#'text-property-search-backward)
|
||||
'face '(shr-h1 shr-h2 shr-h3 shr-h4)
|
||||
(lambda (tags face)
|
||||
(cl-loop for x in (if (consp face) face (list face))
|
||||
thereis (memq x tags))))))
|
||||
(goto-char
|
||||
(if (> arg 0) (prop-match-beginning match) (prop-match-end match)))
|
||||
(throw 'return nil))
|
||||
(when (< arg 0) (beginning-of-line)))
|
||||
(beginning-of-line)
|
||||
(point)))
|
||||
|
||||
(defun +shr-heading-previous (&optional arg)
|
||||
"Move backward by ARG headings (any h1-h4).
|
||||
If ARG is negative move forwards instead, ARG defaults to 1."
|
||||
(interactive "p")
|
||||
(+shr-heading-next (- (or arg 1))))
|
||||
|
||||
(defun +shr-heading--line-at-point ()
|
||||
"Return the current line."
|
||||
(buffer-substring (line-beginning-position) (line-end-position)))
|
||||
|
||||
(defun +shr-heading-setup-imenu ()
|
||||
"Setup imenu for h1-h4 headings in eww buffer.
|
||||
Add this function to appropriate major mode hooks such as
|
||||
`eww-mode-hook' or `elfeed-show-mode-hook'."
|
||||
(setq-local
|
||||
imenu-prev-index-position-function #'+shr-heading-previous
|
||||
imenu-extract-index-name-function #'+shr-heading--line-at-point))
|
||||
|
||||
(provide '+shr)
|
||||
;;; +shr.el ends here
|
27
lisp/+slack.el
Normal file
27
lisp/+slack.el
Normal file
|
@ -0,0 +1,27 @@
|
|||
;;; +slack.el --- Slack customizations and extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'slack)
|
||||
|
||||
(defgroup +slack nil
|
||||
"Extra slack customizations."
|
||||
:group 'slack
|
||||
:prefix "+slack-")
|
||||
|
||||
(defcustom +slack-teams nil
|
||||
"Teams to register using `slack-register-team'.
|
||||
This is a list of plists that are passed directly to
|
||||
`slack-register-team'."
|
||||
;;TODO: type
|
||||
)
|
||||
|
||||
(defun +slack-register-teams ()
|
||||
"Register teams in `+slack-teams'."
|
||||
(dolist (team +slack-teams)
|
||||
(apply #'slack-register-team team)))
|
||||
|
||||
(provide '+slack)
|
||||
;;; +slack.el ends here
|
18
lisp/+sly.el
Normal file
18
lisp/+sly.el
Normal file
|
@ -0,0 +1,18 @@
|
|||
;;; +sly.el --- Sly customizations -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'sly)
|
||||
|
||||
(defun sly-mrepl-return-at-end ()
|
||||
(interactive)
|
||||
(if (<= (point-max) (point))
|
||||
(sly-mrepl-return)
|
||||
(if (bound-and-true-p paredit-mode)
|
||||
(paredit-newline)
|
||||
(electric-newline-and-maybe-indent))))
|
||||
|
||||
(provide '+sly)
|
||||
;;; +sly.el ends here
|
42
lisp/+straight.el
Normal file
42
lisp/+straight.el
Normal file
|
@ -0,0 +1,42 @@
|
|||
;;; +straight.el --- Straight.el extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defun +straight-update-package (package &optional recursive)
|
||||
"Update PACKAGE using straight.
|
||||
This pulls, rebuilds, and loads the updated PACKAGE."
|
||||
(interactive (list (straight--select-package "Update package"
|
||||
#'straight--installed-p)
|
||||
current-prefix-arg))
|
||||
(+with-message (format "Pulling package `%s'%s" package
|
||||
(if recursive " and deps" ""))
|
||||
(funcall (if recursive
|
||||
#'straight-pull-package-and-deps
|
||||
#'straight-pull-package)
|
||||
package
|
||||
:from-upstream))
|
||||
(+with-message (format "Rebuilding package `%s'%s" package
|
||||
(if recursive " and deps" ""))
|
||||
(straight-rebuild-package package recursive))
|
||||
(+with-message (format "Loading package `%s'%s" package
|
||||
(if recursive " and deps" ""))
|
||||
(ignore-errors (load-library (symbol-name package)))
|
||||
(when recursive
|
||||
(dolist (dep (straight--get-transitive-dependencies package))
|
||||
(ignore-errors (load-library (symbol-name package)))))))
|
||||
|
||||
(defun +straight-update-all (from-upstream)
|
||||
"Update all installed packages using straight.
|
||||
This pulls and rebuilds all packages at once. It does not reload
|
||||
all of them, for reasons that should be obvious.
|
||||
|
||||
With a prefix argument, it also pulls the packages FROM-UPSTREAM."
|
||||
(interactive "P")
|
||||
(straight-pull-recipe-repositories)
|
||||
(straight-pull-all from-upstream)
|
||||
(straight-rebuild-all))
|
||||
|
||||
(provide '+straight)
|
||||
;;; +straight.el ends here
|
381
lisp/+tab-bar.el
Normal file
381
lisp/+tab-bar.el
Normal file
|
@ -0,0 +1,381 @@
|
|||
;;; +tab-bar.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Emacs 28 comes with an easy-to-use `tab-bar-format' option, but I still use
|
||||
;; Emacs 27 on my Windows machine. Thus, the code in this file.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'acdw)
|
||||
(require 'tab-bar)
|
||||
|
||||
(defface +tab-bar-extra
|
||||
'((t :inherit (tab-bar font-lock-comment-face)))
|
||||
"Tab bar face for extra information, like the menu-bar and time."
|
||||
:group 'basic-faces)
|
||||
|
||||
|
||||
;; Common
|
||||
|
||||
(defun +tab-bar-space (&optional n)
|
||||
"Display a space N characters long, or 1."
|
||||
`((space menu-item ,(+string-repeat (or n 1) " ") ignore)))
|
||||
|
||||
(defun +tab-bar-misc-info ()
|
||||
"Display `mode-line-misc-info', formatted for the tab-bar."
|
||||
`((misc-info menu-item ,(string-trim-right
|
||||
(format-mode-line mode-line-misc-info))
|
||||
ignore)))
|
||||
|
||||
(defcustom +tracking-hide-when-org-clocking nil
|
||||
"Hide the `tracking-mode' information when clocked in."
|
||||
:type 'boolean)
|
||||
|
||||
(defun +tab-bar-tracking-mode ()
|
||||
"Display `tracking-mode-line-buffers' in the tab-bar."
|
||||
;; TODO: write something to convert a mode-line construct to a tab-bar
|
||||
;; construct.
|
||||
(when (and (bound-and-true-p tracking-mode)
|
||||
(not (and +tracking-hide-when-org-clocking
|
||||
(bound-and-true-p org-clock-current-task))))
|
||||
(cons (when (> (length tracking-mode-line-buffers) 0)
|
||||
'(track-mode-line-separator menu-item " " ignore))
|
||||
(cl-loop for i from 0 below (length tracking-mode-line-buffers)
|
||||
as item = (nth i tracking-mode-line-buffers)
|
||||
collect (append (list (intern (format "tracking-mode-line-%s" i))
|
||||
'menu-item
|
||||
(string-trim (format-mode-line item)))
|
||||
(if-let ((keymap (plist-get item 'keymap)))
|
||||
(list (alist-get 'down-mouse-1 (cdadr keymap)))
|
||||
(list #'ignore))
|
||||
(when-let ((help (plist-get item 'help-echo)))
|
||||
(list :help help)))))))
|
||||
|
||||
(defun +tab-bar-timer ()
|
||||
"Display `+timer-string' in the tab-bar."
|
||||
(when (> (length (bound-and-true-p +timer-string)) 0)
|
||||
`((timer-string menu-item
|
||||
,(concat " " +timer-string)
|
||||
(lambda (ev)
|
||||
(interactive "e")
|
||||
(cond ((not +timer-timer) nil)
|
||||
((equal +timer-string +timer-running-string)
|
||||
(popup-menu
|
||||
'("Running timer"
|
||||
["Cancel timer" +timer-cancel t])
|
||||
ev))
|
||||
(t (setq +timer-string ""))))))))
|
||||
|
||||
(defun +tab-bar-date ()
|
||||
"Display `display-time-string' in the tab-bar."
|
||||
(when display-time-mode
|
||||
`((date-time-string menu-item
|
||||
,(substring-no-properties (concat " " (string-trim display-time-string)))
|
||||
(lambda (ev)
|
||||
(interactive "e")
|
||||
(popup-menu
|
||||
(append '("Timer")
|
||||
(let (r)
|
||||
(dolist (time '(3 5 10))
|
||||
(push (vector (format "Timer for %d minutes" time)
|
||||
`(lambda () (interactive)
|
||||
(+timer ,time))
|
||||
:active t)
|
||||
r))
|
||||
(nreverse r))
|
||||
'(["Timer for ..." +timer t]))
|
||||
ev))
|
||||
:help (discord-date-string)))))
|
||||
|
||||
(defun +tab-bar-notmuch-count ()
|
||||
"Display a notmuch count in the tab-bar."
|
||||
(when (and (executable-find "notmuch")
|
||||
(featurep 'notmuch))
|
||||
(let* ((counts (ignore-errors (notmuch-hello-query-counts notmuch-saved-searches)))
|
||||
(next (cl-find "inbox+unread" counts :key (lambda (l) (plist-get l :name)) :test 'equal))
|
||||
(next-count (plist-get next :count)))
|
||||
(when (and next-count (> next-count 0))
|
||||
`((notmuch-count menu-item
|
||||
,(format " |%s|" next-count)
|
||||
ignore
|
||||
:help ,(format "%s mails requiring attention." next-count)))))))
|
||||
|
||||
(defun +tab-bar-org-clock ()
|
||||
"Display `org-mode-line-string' in the tab-bar."
|
||||
(when (and (fboundp 'org-clocking-p)
|
||||
(org-clocking-p))
|
||||
;; org-mode-line-string
|
||||
`((org-clocking menu-item
|
||||
,org-mode-line-string
|
||||
(lambda (ev)
|
||||
(interactive "e")
|
||||
(let ((menu (make-sparse-keymap
|
||||
(or org-clock-current-task "Org-Clock"))))
|
||||
(map-keymap (lambda (key binding)
|
||||
(when (consp binding)
|
||||
(define-key-after menu (vector key)
|
||||
(copy-sequence binding))))
|
||||
(org-clock-menu))
|
||||
(message "%S" ev)
|
||||
(popup-menu menu ev)))
|
||||
:help ,(or (replace-regexp-in-string
|
||||
(rx "[[" (group (* (not "]")))
|
||||
"][" (group (* (not "]")))
|
||||
"]]")
|
||||
"\\2"
|
||||
org-clock-current-task)
|
||||
"Org-Clock")))))
|
||||
|
||||
(defcustom +tab-bar-emms-max-length 24
|
||||
"Maximum length of `+tab-bar-emms'."
|
||||
:type 'number)
|
||||
|
||||
(defun +tab-bar-emms ()
|
||||
"Display EMMS now playing information."
|
||||
(when (and (bound-and-true-p emms-mode-line-mode)
|
||||
emms-player-playing-p)
|
||||
(let ((now-playing (+string-truncate (emms-mode-line-playlist-current)
|
||||
(- +tab-bar-emms-max-length 2))))
|
||||
`(emms-now-playing menu-item
|
||||
,(concat "{" now-playing "}" " ")
|
||||
emms-pause
|
||||
( :help ,(emms-mode-line-playlist-current))))))
|
||||
|
||||
(defun +tab-bar-bongo ()
|
||||
"Display Bongo now playing information."
|
||||
(when-let ((modep (bound-and-true-p bongo-mode-line-indicator-mode))
|
||||
(buf (cl-some (lambda (b)
|
||||
(with-current-buffer b
|
||||
(when-let* ((modep (derived-mode-p 'bongo-playlist-mode))
|
||||
(bongo-playlist-buffer b)
|
||||
(playingp (bongo-playing-p)))
|
||||
b)))
|
||||
(buffer-list))))
|
||||
`((bongo-now-playing menu-item
|
||||
,(concat "{"
|
||||
(let ((bongo-field-separator ""))
|
||||
(+string-truncate (replace-regexp-in-string
|
||||
"\\(.*\\)\\(.*\\)\\(.*\\)"
|
||||
"\\1: \\3"
|
||||
(bongo-formatted-infoset))
|
||||
;; This isn't right
|
||||
(- (min 50 (/ (frame-width) 3 )) 2)))
|
||||
"}")
|
||||
(lambda () (interactive)
|
||||
(let ((bongo-playlist-buffer
|
||||
;; XXX: I'm sure this is terribly inefficient
|
||||
(cl-some (lambda (b)
|
||||
(with-current-buffer b
|
||||
(when-let* ((modep (derived-mode-p
|
||||
'bongo-playlist-mode))
|
||||
(bongo-playlist-buffer b)
|
||||
(playingp (bongo-playing-p)))
|
||||
b)))
|
||||
(buffer-list))))
|
||||
(with-bongo-playlist-buffer
|
||||
(bongo-pause/resume))))
|
||||
:help ,(funcall bongo-header-line-function)))))
|
||||
|
||||
(defvar +tab-bar-show-original nil
|
||||
"Original value of `tab-bar-show'.")
|
||||
|
||||
(defun +tab-bar-basename ()
|
||||
"Generate the tab name from the basename of the buffer of the
|
||||
selected window."
|
||||
(let* ((tab-file-name (buffer-file-name (window-buffer
|
||||
(minibuffer-selected-window)))))
|
||||
(concat " "
|
||||
(if tab-file-name
|
||||
(file-name-nondirectory tab-file-name)
|
||||
(+tab-bar-tab-name-truncated-left)))))
|
||||
|
||||
;;; FIXME this doesn't work...
|
||||
;; (defvar +tab-bar-tab-min-width 8
|
||||
;; "Minimum width of a tab on the tab bar.")
|
||||
|
||||
;; (defvar +tab-bar-tab-max-width 24
|
||||
;; "Maximum width of a tab on the tab bar.")
|
||||
|
||||
;; (defun +tab-bar-fluid-calculate-width ()
|
||||
;; "Calculate the width of each tab in the tab-bar."
|
||||
;; (let* ((tab-bar-list (cdr (tab-bar-make-keymap-1)))
|
||||
;; (tab-bar-avail-width (frame-width))
|
||||
;; (tab-bar-tab-count (length (tab-bar-tabs)))
|
||||
;; (tab-bar-close-button-char-width 1)
|
||||
;; (tab-bar-add-tab-button-char-width 1)
|
||||
;; (tab-bar-total-width
|
||||
;; (length (mapconcat
|
||||
;; (lambda (el)
|
||||
;; (when-let ((str (car-safe (cdr-safe (cdr-safe el)))))
|
||||
;; (substring-no-properties (eval str))))
|
||||
;; tab-bar-list)))
|
||||
;; (tab-bar-total-tab-width
|
||||
;; (+ (* tab-bar-tab-count tab-bar-close-button-char-width)
|
||||
;; tab-bar-add-tab-button-char-width
|
||||
;; (length (mapconcat
|
||||
;; (lambda (el)
|
||||
;; (substring-no-properties (alist-get 'name el)))
|
||||
;; (tab-bar-tabs)))))
|
||||
;; (tab-bar-total-nontab-width (- tab-bar-total-width
|
||||
;; tab-bar-total-tab-width)))
|
||||
;; (min +tab-bar-tab-max-width
|
||||
;; (max +tab-bar-tab-min-width
|
||||
;; (/ (- tab-bar-avail-width
|
||||
;; tab-bar-total-tab-width
|
||||
;; tab-bar-total-nontab-width)
|
||||
;; tab-bar-tab-count)))))
|
||||
|
||||
;; (defun +tab-bar-fluid-width ()
|
||||
;; "Generate the tab name to fluidly fit in the given space."
|
||||
;; (let* ((tab-file-name (buffer-file-name (window-buffer
|
||||
;; (minibuffer-selected-window)))))
|
||||
;; (format (format " %%s%%%ds" (+tab-bar-fluid-calculate-width))
|
||||
;; (if tab-file-name
|
||||
;; (file-name-nondirectory tab-file-name)
|
||||
;; (+tab-bar-tab-name-truncated-left))
|
||||
;; " ")))
|
||||
|
||||
(defun +tab-bar-tab-name-truncated-left ()
|
||||
"Generate the tab name from the buffer of the selected window.
|
||||
This is just like `tab-bar-tab-name-truncated', but truncates the
|
||||
name to the left."
|
||||
(let* ((tab-name (buffer-name (window-buffer (minibuffer-selected-window))))
|
||||
(ellipsis (cond
|
||||
(tab-bar-tab-name-ellipsis)
|
||||
((char-displayable-p ?…) "…")
|
||||
("...")))
|
||||
(l-ell (length ellipsis))
|
||||
(l-name (length tab-name)))
|
||||
(if (< (length tab-name) tab-bar-tab-name-truncated-max)
|
||||
tab-name
|
||||
(propertize (concat
|
||||
(when (> (+ l-name l-ell) tab-bar-tab-name-truncated-max)
|
||||
ellipsis)
|
||||
(truncate-string-to-width tab-name l-name
|
||||
(max 0 (- l-name tab-bar-tab-name-truncated-max l-ell))))
|
||||
'help-echo tab-name))))
|
||||
|
||||
(defun +tab-bar-format-align-right ()
|
||||
"Align the rest of tab bar items to the right, pixel-wise."
|
||||
;; XXX: ideally, wouldn't require `shr' here
|
||||
(require 'shr) ; `shr-string-pixel-width'
|
||||
(let* ((rest (cdr (memq '+tab-bar-format-align-right tab-bar-format)))
|
||||
(rest (tab-bar-format-list rest))
|
||||
(rest (mapconcat (lambda (item) (nth 2 item)) rest ""))
|
||||
(hpos (shr-string-pixel-width rest))
|
||||
(str (propertize " " 'display `(space :align-to (- right (,hpos))))))
|
||||
`((align-right menu-item ,str ignore))))
|
||||
|
||||
|
||||
;;; Menu bar
|
||||
;; stole from https://github.com/emacs-mirror/emacs/blob/master/lisp/tab-bar.el
|
||||
|
||||
(defun +tab-bar-menu-bar (event)
|
||||
"Pop up the same menu as displayed by the menu bar.
|
||||
Used by `tab-bar-format-menu-bar'."
|
||||
(interactive "e")
|
||||
(let ((menu (make-sparse-keymap (propertize "Menu Bar" 'hide t))))
|
||||
(run-hooks 'activate-menubar-hook 'menu-bar-update-hook)
|
||||
(map-keymap (lambda (key binding)
|
||||
(when (consp binding)
|
||||
(define-key-after menu (vector key)
|
||||
(copy-sequence binding))))
|
||||
(menu-bar-keymap))
|
||||
(popup-menu menu event)))
|
||||
|
||||
(defcustom +tab-bar-menu-bar-icon " Emacs "
|
||||
"The string to use for the tab-bar menu icon."
|
||||
:type 'string)
|
||||
|
||||
(defun +tab-bar-format-menu-bar ()
|
||||
"Produce the Menu button for the tab bar that shows the menu bar."
|
||||
`((menu-bar menu-item (propertize +tab-bar-menu-bar-icon 'face '+tab-bar-extra)
|
||||
+tab-bar-menu-bar :help "Menu Bar")))
|
||||
|
||||
|
||||
;;; Tab bar format tabs
|
||||
|
||||
(require 'el-patch)
|
||||
(el-patch-feature tab-bar)
|
||||
(with-eval-after-load 'tab-bar
|
||||
(el-patch-defun tab-bar--format-tab (tab i)
|
||||
"Format TAB using its index I and return the result as a keymap."
|
||||
(append
|
||||
(el-patch-remove
|
||||
`((,(intern (format "sep-%i" i)) menu-item ,(tab-bar-separator) ignore)))
|
||||
(cond
|
||||
((eq (car tab) 'current-tab)
|
||||
`((current-tab
|
||||
menu-item
|
||||
,(funcall tab-bar-tab-name-format-function tab i)
|
||||
ignore
|
||||
:help "Current tab")))
|
||||
(t
|
||||
`((,(intern (format "tab-%i" i))
|
||||
menu-item
|
||||
,(funcall tab-bar-tab-name-format-function tab i)
|
||||
,(alist-get 'binding tab)
|
||||
:help "Click to visit tab"))))
|
||||
(when (alist-get 'close-binding tab)
|
||||
`((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i)))
|
||||
menu-item ""
|
||||
,(alist-get 'close-binding tab)))))))
|
||||
|
||||
|
||||
;; Emacs 27
|
||||
|
||||
(defun +tab-bar-misc-info-27 (output &rest _)
|
||||
"Display `mode-line-misc-info' in the `tab-bar' on Emacs 27.
|
||||
This is :filter-return advice for `tab-bar-make-keymap-1'."
|
||||
(let* ((reserve (length (format-mode-line mode-line-misc-info)))
|
||||
(str (propertize " "
|
||||
'display `(space :align-to (- right (- 0 right-margin)
|
||||
,reserve)))))
|
||||
(prog1 (append output
|
||||
`((align-right menu-item ,str nil))
|
||||
(+tab-bar-misc-info)))))
|
||||
|
||||
|
||||
;; Emacs 28
|
||||
|
||||
(defvar +tab-bar-format-original nil
|
||||
"Original value of `tab-bar-format'.")
|
||||
|
||||
(defun +tab-bar-misc-info-28 ()
|
||||
"Display `mode-line-misc-info', right-aligned, on Emacs 28."
|
||||
(append (unless (memq 'tab-bar-format-align-right tab-bar-format)
|
||||
'(tab-bar-format-align-right))
|
||||
'(+tab-bar-misc-info)))
|
||||
|
||||
|
||||
|
||||
(define-minor-mode +tab-bar-misc-info-mode
|
||||
"Show the `mode-line-misc-info' in the `tab-bar'."
|
||||
:lighter ""
|
||||
:global t
|
||||
(if +tab-bar-misc-info-mode
|
||||
(progn ; Enable
|
||||
(setq +tab-bar-show-original tab-bar-show)
|
||||
(cond
|
||||
((boundp 'tab-bar-format) ; Emacs 28
|
||||
(setq +tab-bar-format-original tab-bar-format)
|
||||
(unless (memq '+tab-bar-misc-info tab-bar-format)
|
||||
(setq tab-bar-format
|
||||
(append tab-bar-format (+tab-bar-misc-info-28)))))
|
||||
((fboundp 'tab-bar-make-keymap-1) ; Emacs 27
|
||||
(advice-add 'tab-bar-make-keymap-1 :filter-return
|
||||
'+tab-bar-misc-info-27)))
|
||||
(setq tab-bar-show t))
|
||||
(progn ; Disable
|
||||
(setq tab-bar-show +tab-bar-show-original)
|
||||
(cond
|
||||
((boundp 'tab-bar-format) ; Emacs 28
|
||||
(setq tab-bar-format +tab-bar-format-original))
|
||||
((fboundp 'tab-bar-make-keymap-1) ; Emacs 27
|
||||
(advice-remove 'tab-bar-make-keymap-1 '+tab-bar-misc-info-27))))))
|
||||
|
||||
|
||||
|
||||
(provide '+tab-bar)
|
||||
;;; +tab-bar.el ends here
|
30
lisp/+titlecase.el
Normal file
30
lisp/+titlecase.el
Normal file
|
@ -0,0 +1,30 @@
|
|||
;;; +titlecase.el --- Titlecase extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defun +titlecase-sentence-style-dwim (&optional arg)
|
||||
"Titlecase a sentence.
|
||||
With prefix ARG, toggle the value of
|
||||
`titlecase-downcase-sentences' before sentence-casing."
|
||||
(interactive "P")
|
||||
(let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences)
|
||||
titlecase-downcase-sentences)))
|
||||
(titlecase-dwim 'sentence)))
|
||||
|
||||
(defun +titlecase-org-headings ()
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
;; See also `org-map-tree'. I'm not using that function because I want to
|
||||
;; skip the first headline. A better solution would be to patch
|
||||
;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc).
|
||||
(let ((level (funcall outline-level)))
|
||||
(while (and (progn (outline-next-heading)
|
||||
(> (funcall outline-level) level))
|
||||
(not (eobp)))
|
||||
(titlecase-line)))))
|
||||
|
||||
(provide '+titlecase)
|
||||
;;; +titlecase.el ends here
|
|
@ -12,5 +12,13 @@
|
|||
(unless (eq 1 (abs (- beg-index vertico--index)))
|
||||
(ding))))
|
||||
|
||||
(defun +vertico-widen-or-complete ()
|
||||
(interactive)
|
||||
(if (or vertico-unobtrusive-mode
|
||||
vertico-flat-mode)
|
||||
(progn (vertico-unobtrusive-mode -1)
|
||||
(vertico-flat-mode -1))
|
||||
(call-interactively #'vertico-insert)))
|
||||
|
||||
(provide '+vertico)
|
||||
;;; +vertico.el ends here
|
||||
|
|
19
lisp/+vterm.el
Normal file
19
lisp/+vterm.el
Normal file
|
@ -0,0 +1,19 @@
|
|||
;;; +vterm.el --- Vterm extras -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'vterm)
|
||||
|
||||
(defun +vterm-counsel-yank-pop-action (orig-fun &rest args)
|
||||
(if (equal major-mode 'vterm-mode)
|
||||
(let ((inhibit-read-only t)
|
||||
(yank-undo-function (lambda (_start _end) (vterm-undo))))
|
||||
(cl-letf (((symbol-function 'insert-for-yank)
|
||||
(lambda (str) (vterm-send-string str t))))
|
||||
(apply orig-fun args)))
|
||||
(apply orig-fun args)))
|
||||
|
||||
(provide '+vterm)
|
||||
;;; +vterm.el ends here
|
130
lisp/+window.el
Normal file
130
lisp/+window.el
Normal file
|
@ -0,0 +1,130 @@
|
|||
;;; +window.el --- Fixes for Emacs's window.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Do I want to propose this change in the Emacs ML?
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'window)
|
||||
|
||||
;;; Split windows based on `window-total-width', not `window-width'
|
||||
;; I have to just redefine these functions because the check is really deep in
|
||||
;; there.
|
||||
|
||||
(defun window-splittable-p (window &optional horizontal)
|
||||
"Return non-nil if `split-window-sensibly' may split WINDOW.
|
||||
Optional argument HORIZONTAL nil or omitted means check whether
|
||||
`split-window-sensibly' may split WINDOW vertically. HORIZONTAL
|
||||
non-nil means check whether WINDOW may be split horizontally.
|
||||
|
||||
WINDOW may be split vertically when the following conditions
|
||||
hold:
|
||||
- `window-size-fixed' is either nil or equals `width' for the
|
||||
buffer of WINDOW.
|
||||
- `split-height-threshold' is an integer and WINDOW is at least as
|
||||
high as `split-height-threshold'.
|
||||
- When WINDOW is split evenly, the emanating windows are at least
|
||||
`window-min-height' lines tall and can accommodate at least one
|
||||
line plus - if WINDOW has one - a mode line.
|
||||
|
||||
WINDOW may be split horizontally when the following conditions
|
||||
hold:
|
||||
- `window-size-fixed' is either nil or equals `height' for the
|
||||
buffer of WINDOW.
|
||||
- `split-width-threshold' is an integer and WINDOW is at least as
|
||||
wide as `split-width-threshold'.
|
||||
- When WINDOW is split evenly, the emanating windows are at least
|
||||
`window-min-width' or two (whichever is larger) columns wide."
|
||||
(when (and (window-live-p window)
|
||||
(not (window-parameter window 'window-side)))
|
||||
(with-current-buffer (window-buffer window)
|
||||
(if horizontal
|
||||
;; A window can be split horizontally when its width is not
|
||||
;; fixed, it is at least `split-width-threshold' columns wide
|
||||
;; and at least twice as wide as `window-min-width' and 2 (the
|
||||
;; latter value is hardcoded).
|
||||
(and (memq window-size-fixed '(nil height))
|
||||
;; Testing `window-full-width-p' here hardly makes any
|
||||
;; sense nowadays. This can be done more intuitively by
|
||||
;; setting up `split-width-threshold' appropriately.
|
||||
(numberp split-width-threshold)
|
||||
(>= (window-total-width window)
|
||||
(max split-width-threshold
|
||||
(* 2 (max window-min-width 2)))))
|
||||
;; A window can be split vertically when its height is not
|
||||
;; fixed, it is at least `split-height-threshold' lines high,
|
||||
;; and it is at least twice as high as `window-min-height' and 2
|
||||
;; if it has a mode line or 1.
|
||||
(and (memq window-size-fixed '(nil width))
|
||||
(numberp split-height-threshold)
|
||||
(>= (window-height window)
|
||||
(max split-height-threshold
|
||||
(* 2 (max window-min-height
|
||||
(if mode-line-format 2 1))))))))))
|
||||
|
||||
(defun split-window-sensibly (&optional window)
|
||||
"Split WINDOW in a way suitable for `display-buffer'.
|
||||
WINDOW defaults to the currently selected window.
|
||||
If `split-height-threshold' specifies an integer, WINDOW is at
|
||||
least `split-height-threshold' lines tall and can be split
|
||||
vertically, split WINDOW into two windows one above the other and
|
||||
return the lower window. Otherwise, if `split-width-threshold'
|
||||
specifies an integer, WINDOW is at least `split-width-threshold'
|
||||
columns wide and can be split horizontally, split WINDOW into two
|
||||
windows side by side and return the window on the right. If this
|
||||
can't be done either and WINDOW is the only window on its frame,
|
||||
try to split WINDOW vertically disregarding any value specified
|
||||
by `split-height-threshold'. If that succeeds, return the lower
|
||||
window. Return nil otherwise.
|
||||
|
||||
By default `display-buffer' routines call this function to split
|
||||
the largest or least recently used window. To change the default
|
||||
customize the option `split-window-preferred-function'.
|
||||
|
||||
You can enforce this function to not split WINDOW horizontally,
|
||||
by setting (or binding) the variable `split-width-threshold' to
|
||||
nil. If, in addition, you set `split-height-threshold' to zero,
|
||||
chances increase that this function does split WINDOW vertically.
|
||||
|
||||
In order to not split WINDOW vertically, set (or bind) the
|
||||
variable `split-height-threshold' to nil. Additionally, you can
|
||||
set `split-width-threshold' to zero to make a horizontal split
|
||||
more likely to occur.
|
||||
|
||||
Have a look at the function `window-splittable-p' if you want to
|
||||
know how `split-window-sensibly' determines whether WINDOW can be
|
||||
split."
|
||||
(let ((window (or window (selected-window))))
|
||||
(or (and (window-splittable-p window)
|
||||
;; Split window vertically.
|
||||
(with-selected-window window
|
||||
(split-window-below)))
|
||||
(and (window-splittable-p window t)
|
||||
;; Split window horizontally.
|
||||
(with-selected-window window
|
||||
(split-window-right)))
|
||||
(and
|
||||
;; If WINDOW is the only usable window on its frame (it is
|
||||
;; the only one or, not being the only one, all the other
|
||||
;; ones are dedicated) and is not the minibuffer window, try
|
||||
;; to split it vertically disregarding the value of
|
||||
;; `split-height-threshold'.
|
||||
(let ((frame (window-frame window)))
|
||||
(or
|
||||
(eq window (frame-root-window frame))
|
||||
(catch 'done
|
||||
(walk-window-tree (lambda (w)
|
||||
(unless (or (eq w window)
|
||||
(window-dedicated-p w))
|
||||
(throw 'done nil)))
|
||||
frame nil 'nomini)
|
||||
t)))
|
||||
(not (window-minibuffer-p window))
|
||||
(let ((split-height-threshold 0))
|
||||
(when (window-splittable-p window)
|
||||
(with-selected-window window
|
||||
(split-window-below))))))))
|
||||
|
||||
(provide '+window)
|
||||
;;; +window.el ends here
|
16
lisp/+xkcd.el
Normal file
16
lisp/+xkcd.el
Normal file
|
@ -0,0 +1,16 @@
|
|||
;;; +xkcd.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'xkcd)
|
||||
|
||||
(defun +xkcd-get-from-url (url &rest _)
|
||||
"Open XKCD from URL."
|
||||
(if (string-match "xkcd\\.com/\\([0-9]+\\)" url)
|
||||
(xkcd-get (string-to-number (match-string 1 url)))
|
||||
(funcall +browse-url-browser-function url)))
|
||||
|
||||
(provide '+xkcd)
|
||||
;;; +xkcd.el ends here
|
21
lisp/+ytdious.el
Normal file
21
lisp/+ytdious.el
Normal file
|
@ -0,0 +1,21 @@
|
|||
;;; +ytdious.el --- Ytdious customizations -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; https://github.com/spiderbit/ytdious
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defun +ytdious-watch ()
|
||||
"Stream video at point in mpv."
|
||||
(interactive)
|
||||
(let* ((video (ytdious-get-current-video))
|
||||
(id (ytdious-video-id-fun video)))
|
||||
(start-process "ytdious mpv" nil
|
||||
"mpv"
|
||||
(concat "https://www.youtube.com/watch?v=" id))
|
||||
"--ytdl-format=bestvideo[height<=?720]+bestaudio/best")
|
||||
(message "Starting streaming..."))
|
||||
|
||||
(provide '+ytdious)
|
||||
;;; +ytdious.el ends here
|
16
lisp/+zzz-to-char.el
Normal file
16
lisp/+zzz-to-char.el
Normal file
|
@ -0,0 +1,16 @@
|
|||
;;; +zzz-to-char.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defun +zzz-to-char (prefix)
|
||||
"Call `zzz-to-char' or `zzz-up-to-char' with PREFIX arg."
|
||||
(interactive "P")
|
||||
(call-interactively
|
||||
(if prefix #'zzz-up-to-char #'zzz-to-char)))
|
||||
|
||||
(provide '+zzz-to-char)
|
||||
;;; +zzz-to-char.el ends here
|
492
lisp/acdw.el
492
lisp/acdw.el
|
@ -19,13 +19,17 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'diary-lib)
|
||||
(require 'solar) ; for +sunrise-sunset
|
||||
|
||||
;;; Define a directory and an expanding function
|
||||
|
||||
(defmacro +define-dir (name directory &optional docstring inhibit-mkdir)
|
||||
"Define a variable and function NAME expanding to DIRECTORY.
|
||||
DOCSTRING is applied to the variable. Ensure DIRECTORY exists in
|
||||
the filesystem, unless INHIBIT-MKDIR is non-nil."
|
||||
(declare (indent 2))
|
||||
(declare (indent 2)
|
||||
(doc-string 3))
|
||||
(unless inhibit-mkdir
|
||||
(make-directory (eval directory) :parents))
|
||||
`(progn
|
||||
|
@ -37,12 +41,11 @@ the filesystem, unless INHIBIT-MKDIR is non-nil."
|
|||
"If MKDIR is non-nil, the directory is created.\n"
|
||||
"Defined by `/define-dir'.")
|
||||
(let ((file-name (expand-file-name (convert-standard-filename file)
|
||||
,name)))
|
||||
,name)))
|
||||
(when mkdir
|
||||
(make-directory (file-name-directory file-name) :parents))
|
||||
file-name))))
|
||||
|
||||
|
||||
(defun +suppress-messages (oldfn &rest args) ; from pkal
|
||||
"Advice wrapper for suppressing `message'.
|
||||
OLDFN is the wrapped function, that is passed the arguments
|
||||
|
@ -54,33 +57,6 @@ ARGS."
|
|||
(when msg
|
||||
(message "%s" msg)))))
|
||||
|
||||
(defun +sunrise-sunset (sunrise-command sunset-command &optional reset)
|
||||
"Run SUNRISE-COMMAND at sunrise, and SUNSET-COMMAND at sunset.
|
||||
With RESET, this function will call itself with its own
|
||||
arguments. That's really only useful within this function
|
||||
itself."
|
||||
(let* ((times-regex (rx (* nonl)
|
||||
(: (any ?s ?S) "unrise") " "
|
||||
(group (repeat 1 2 digit) ":"
|
||||
(repeat 1 2 digit)
|
||||
(: (any ?a ?A ?p ?P) (any ?m ?M)))
|
||||
(* nonl)
|
||||
(: (any ?s ?S) "unset") " "
|
||||
(group (repeat 1 2 digit) ":"
|
||||
(repeat 1 2 digit)
|
||||
(: (any ?a ?A ?p ?P) (any ?m ?M)))
|
||||
(* nonl)))
|
||||
(ss (+suppress-messages #'sunrise-sunset))
|
||||
(_m (string-match times-regex ss))
|
||||
(sunrise-time (match-string 1 ss))
|
||||
(sunset-time (match-string 2 ss)))
|
||||
(run-at-time sunrise-time nil sunrise-command)
|
||||
(run-at-time sunset-time nil sunset-command)
|
||||
(run-at-time "12:00am" nil sunset-command)
|
||||
;; Reset everything at midnight
|
||||
(run-at-time "12:00am" (* 60 60 24)
|
||||
#'sunrise-sunset sunrise-command sunset-command t)))
|
||||
|
||||
(defun +ensure-after-init (function)
|
||||
"Ensure FUNCTION runs after init, or now if already initialized.
|
||||
If Emacs is already started, run FUNCTION. Otherwise, add it to
|
||||
|
@ -89,11 +65,467 @@ If Emacs is already started, run FUNCTION. Otherwise, add it to
|
|||
(funcall function)
|
||||
(add-hook 'after-init-hook function)))
|
||||
|
||||
(defmacro +with-ensure-after-init (&rest body)
|
||||
"Ensure BODY forms run after init.
|
||||
Convenience macro wrapper around `+ensure-after-init'."
|
||||
(declare (indent 0) (debug (def-body)))
|
||||
`(+ensure-after-init (lambda () ,@body)))
|
||||
|
||||
(defun +remember-prefix-arg (p-arg P-arg)
|
||||
"Display prefix ARG, in \"p\" and \"P\" `interactive' types.
|
||||
I keep forgetting how they differ."
|
||||
(interactive "p\nP")
|
||||
(message "p: %S P: %S" p-arg P-arg))
|
||||
|
||||
(defmacro +defvar (var value &rest _)
|
||||
"Quick way to `setq' a variable from a `defvar' form."
|
||||
(declare (doc-string 3) (indent 2))
|
||||
`(setq ,var ,value))
|
||||
|
||||
(defmacro +with-message (message &rest body)
|
||||
"Execute BODY, with MESSAGE.
|
||||
If body executes without errors, MESSAGE...Done will be displayed."
|
||||
(declare (indent 1))
|
||||
(let ((msg (gensym)))
|
||||
`(let ((,msg ,message))
|
||||
(condition-case e
|
||||
(progn (message "%s..." ,msg)
|
||||
,@body)
|
||||
(:success (message "%s...done" ,msg))
|
||||
(t (signal (car e) (cdr e)))))))
|
||||
|
||||
(defun +mapc-some-buffers (func &optional predicate)
|
||||
"Perform FUNC on all buffers satisfied by PREDICATE.
|
||||
By default, act on all buffers.
|
||||
|
||||
Both PREDICATE and FUNC are called with no arguments, but within
|
||||
a `with-current-buffer' form on the currently-active buffer.
|
||||
|
||||
As a special case, if PREDICATE is a list, it will be interpreted
|
||||
as a list of major modes. In this case, FUNC will only be called
|
||||
on buffers derived from one of the modes in PREDICATE."
|
||||
(let ((pred (or predicate t)))
|
||||
(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when (cond ((functionp pred)
|
||||
(funcall pred))
|
||||
((listp pred)
|
||||
(apply #'derived-mode-p pred))
|
||||
(t pred))
|
||||
(funcall func))))))
|
||||
|
||||
;; https://github.com/cstby/emacs.d/blob/main/init.el#L67
|
||||
(defun +clean-empty-lines (&optional begin end)
|
||||
"Remove duplicate empty lines from BEGIN to END.
|
||||
Called interactively, this function acts on the region, if
|
||||
active, or else the entire buffer."
|
||||
(interactive "*r")
|
||||
(unless (region-active-p)
|
||||
(setq begin (point-min)
|
||||
end (save-excursion
|
||||
(goto-char (point-max))
|
||||
(skip-chars-backward "\n[:space:]")
|
||||
(point))))
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(narrow-to-region begin end)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\n\n\n+" nil :move)
|
||||
(replace-match "\n\n"))
|
||||
;; Insert a newline at the end.
|
||||
(goto-char (point-max))
|
||||
(unless (or (buffer-narrowed-p)
|
||||
(= (line-beginning-position) (line-end-position)))
|
||||
(insert "\n")))))
|
||||
|
||||
(defcustom +open-paragraph-ignore-modes '(special-mode lui-mode comint-mode)
|
||||
"Modes in which `+open-paragraph' makes no sense."
|
||||
:type '(repeat function))
|
||||
|
||||
(defun +open-paragraph (&optional arg)
|
||||
"Open a paragraph after paragraph at point.
|
||||
A paragraph is defined as continguous non-empty lines of text
|
||||
surrounded by empty lines, so opening a paragraph means to make
|
||||
three blank lines, then place the point on the second one.
|
||||
|
||||
Called with prefix ARG, open a paragraph before point."
|
||||
;; TODO: Take an integer as ARG, allowing for skipping paragraphs up and down.
|
||||
(interactive "*P")
|
||||
;; TODO: add `+open-paragraph-ignore-modes'
|
||||
(unless (apply #'derived-mode-p +open-paragraph-ignore-modes)
|
||||
;; Go to next blank line. This /isn't/ `end-of-paragraph-text' because
|
||||
;; that's weird with org, and I'm guessing other modes too.
|
||||
(unless (looking-at "^$") (forward-line (if arg -1 +1)))
|
||||
(while (and (not (looking-at "^$"))
|
||||
(= 0 (forward-line (if arg -1 +1)))))
|
||||
(newline)
|
||||
(when arg (newline) (forward-line -2))
|
||||
(delete-blank-lines)
|
||||
(newline 2)
|
||||
(previous-line)))
|
||||
|
||||
(defun +split-window-then (&optional where arg)
|
||||
"Split the window into a new buffer.
|
||||
With non-nil ARG (\\[universal-argument] interactively), don't
|
||||
prompt for a buffer to switch to. This function will split the
|
||||
window using `split-window-sensibly', or open the new window in
|
||||
the direction specified by WHERE. WHERE is ignored when called
|
||||
interactively; if you want specific splitting, use
|
||||
`+split-window-right-then' or `+split-window-below-then'."
|
||||
(interactive "i\nP")
|
||||
;; TODO: Canceling at the switching phase leaves the point in the other
|
||||
;; window. Ideally, the user would see this as one action, meaning a cancel
|
||||
;; would return to the original window.
|
||||
(pcase where
|
||||
;; These directions are 'backward' to the OG Emacs split-window commands,
|
||||
;; because by default Emacs leaves the cursor in the original window. Most
|
||||
;; users probably expect a switch to the new window, at least I do.
|
||||
((or 'right :right) (split-window-right) (other-window 1))
|
||||
((or 'left :left) (split-window-right))
|
||||
((or 'below :below) (split-window-below) (other-window 1))
|
||||
((or 'above :above) (split-window-below))
|
||||
((pred null)
|
||||
(or (split-window-sensibly)
|
||||
(if (< (window-height) (window-width))
|
||||
(split-window-below)
|
||||
(split-window-right)))
|
||||
(other-window 1))
|
||||
(_ (user-error "Unknown WHERE paramater: %s" where)))
|
||||
(unless arg
|
||||
(condition-case nil
|
||||
(call-interactively
|
||||
(pcase (read-char "(B)uffer or (F)ile?")
|
||||
(?b (if (fboundp #'consult-buffer)
|
||||
#'consult-buffer
|
||||
#'switch-to-buffer))
|
||||
(?f #'find-file)
|
||||
(_ #'ignore)))
|
||||
(quit (delete-window)))))
|
||||
|
||||
(defun +split-window-right-then (&optional arg)
|
||||
"Split window right, then prompt for a new buffer.
|
||||
With optional ARG (\\[universal-argument]), just split."
|
||||
(interactive "P")
|
||||
(+split-window-then :right arg))
|
||||
|
||||
(defun +split-window-below-then (&optional arg)
|
||||
"Split window below, then prompt for a new buffer.
|
||||
With optional ARG (\\[universal-argument]), just split."
|
||||
(interactive "P")
|
||||
(+split-window-then :below arg))
|
||||
|
||||
(defun +bytes (number unit)
|
||||
"Convert NUMBER UNITs to bytes.
|
||||
UNIT can be one of :kb, :mb, :gb, :tb, :pb, :eb, :zb, :yb; :kib, :mib, :gib,
|
||||
:tib, :pib, :eib, :zib, :yib."
|
||||
(* number (pcase unit
|
||||
;; Base 10 units
|
||||
(:kb 1000)
|
||||
(:mb (* 1000 1000))
|
||||
(:gb (* 1000 1000 1000))
|
||||
(:tb (* 1000 1000 1000 1000))
|
||||
(:pb (* 1000 1000 1000 1000 1000))
|
||||
(:eb (* 1000 1000 1000 1000 1000 1000))
|
||||
(:zb (* 1000 1000 1000 1000 1000 1000 1000))
|
||||
(:yb (* 1000 1000 1000 1000 1000 1000 1000 1000))
|
||||
;; Base 2 units
|
||||
(:kib 1024)
|
||||
(:mib (* 1024 1024))
|
||||
(:gib (* 1024 1024 1024))
|
||||
(:tib (* 1024 1024 1024 1024))
|
||||
(:pib (* 1024 1024 1024 1024 1024))
|
||||
(:eib (* 1024 1024 1024 1024 1024 1024))
|
||||
(:zib (* 1024 1024 1024 1024 1024 1024 1024))
|
||||
(:yib (* 1024 1024 1024 1024 1024 1024 1024 1024)))))
|
||||
|
||||
;;; Font lock TODO keywords
|
||||
|
||||
(defcustom font-lock-todo-keywords '("TODO" "XXX" "FIXME" "BUG")
|
||||
"Keywords to highlight with `font-lock-todo-face'.")
|
||||
|
||||
(defface font-lock-todo-face '((t :inherit font-lock-comment-face
|
||||
:background "yellow"))
|
||||
;; TODO: XXX: FIXME: BUG: testing :)
|
||||
"Face for TODO keywords.")
|
||||
|
||||
(defun font-lock-todo-insinuate ()
|
||||
(let ((keyword-regexp
|
||||
(rx bow (group (eval (let ((lst '(or)))
|
||||
(dolist (kw font-lock-todo-keywords)
|
||||
(push kw lst))
|
||||
(nreverse lst))))
|
||||
":")))
|
||||
(font-lock-add-keywords
|
||||
nil
|
||||
`((,keyword-regexp 1 'font-lock-todo-face prepend)))))
|
||||
|
||||
;; I don't use this much but I always forget the exact implementation, so this
|
||||
;; is more to remember than anything else.
|
||||
(defmacro setc (&rest vars-and-vals)
|
||||
"Set VARS-AND-VALS by customizing them or using set-default.
|
||||
Use like `setq'."
|
||||
`(progn ,@(cl-loop for (var val) on vars-and-vals by #'cddr
|
||||
if (null val) return (user-error "Not enough arguments")
|
||||
collecting `(funcall (or (get ',var 'custom-get)
|
||||
#'set-default)
|
||||
',var ',val)
|
||||
into ret
|
||||
finally return ret)))
|
||||
|
||||
(defun +set-faces (specs)
|
||||
"Set fonts to SPECS.
|
||||
Specs is an alist: its cars are faces and its cdrs are the plist
|
||||
passed to `set-face-attribute'. Note that the FRAME argument is
|
||||
always nil; this function is mostly intended for use in init."
|
||||
(dolist (spec specs)
|
||||
(apply #'set-face-attribute (car spec) nil (cdr spec))))
|
||||
|
||||
(defcustom chat-functions '(+irc
|
||||
jabber-connect-all
|
||||
;; slack-start
|
||||
)
|
||||
"Functions to start when calling `chat'."
|
||||
:type '(repeat function)
|
||||
:group 'applications)
|
||||
|
||||
(defun +string-repeat (n str)
|
||||
"Repeat STR N times."
|
||||
(let ((r ""))
|
||||
(dotimes (_ n)
|
||||
(setq r (concat r str)))
|
||||
r))
|
||||
|
||||
(defun chat-disconnect ()
|
||||
"Disconnect from all chats."
|
||||
(interactive)
|
||||
(+with-progress "Quitting circe..."
|
||||
(ignore-errors
|
||||
(circe-command-GQUIT "peace love bread")
|
||||
(cancel-timer (irc-connection-get conn :flood-timer))))
|
||||
(+with-progress "Quitting jabber..."
|
||||
(ignore-errors
|
||||
(jabber-disconnect)))
|
||||
(when (boundp '+slack-teams)
|
||||
(+with-progress "Quitting-slack..."
|
||||
(dolist (team +slack-teams)
|
||||
(ignore-errors
|
||||
(slack-team-disconnect team)))
|
||||
(ignore-errors (slack-ws-close))))
|
||||
(+with-progress "Killing buffers..."
|
||||
(ignore-errors
|
||||
(+mapc-some-buffers (lambda () "Remove the buffer from tracking and kill it unconditionally."
|
||||
(let ((kill-buffer-query-functions nil))
|
||||
(tracking-remove-buffer (current-buffer))
|
||||
(kill-buffer)))
|
||||
(lambda () "Return t if derived from the following modes."
|
||||
(derived-mode-p 'lui-mode
|
||||
'jabber-chat-mode
|
||||
'jabber-roster-mode
|
||||
'jabber-browse-mode
|
||||
'slack-mode))))))
|
||||
|
||||
;; I can never remember all the damn chat things I run, so this just does all of em.
|
||||
(defun chat (&optional arg)
|
||||
"Initiate all chat functions.
|
||||
With optional ARG, kill all chat-related buffers first."
|
||||
(interactive "P")
|
||||
(when arg (chat-disconnect))
|
||||
(dolist-with-progress-reporter (fn chat-functions)
|
||||
"Connecting to chat..."
|
||||
(call-interactively fn)))
|
||||
|
||||
(defun +forward-paragraph (arg)
|
||||
"Move forward ARG (simple) paragraphs.
|
||||
A paragraph here is simply defined: it's a block of buffer that's
|
||||
separated from others by two newlines."
|
||||
(interactive "p")
|
||||
(let ((direction (/ arg (abs arg))))
|
||||
(forward-line direction)
|
||||
(while (not (or (bobp)
|
||||
(eobp)
|
||||
(= arg 0)))
|
||||
(if (looking-at "^[ \f\t]*$")
|
||||
(setq arg (- arg direction))
|
||||
(forward-line direction)))))
|
||||
|
||||
(defun +backward-paragraph (arg)
|
||||
"Move backward ARG (simple) paragraphs.
|
||||
See `+forward-paragraph' for the behavior."
|
||||
(interactive "p")
|
||||
(+forward-paragraph (- arg)))
|
||||
|
||||
(defun +concat (&rest strings)
|
||||
"Concat STRINGS separated by SEPARATOR.
|
||||
Each item in STRINGS is either a string or a list or strings,
|
||||
which is concatenated without any separator.
|
||||
|
||||
SEPARATOR defaults to the newline (\\n)."
|
||||
(let (ret
|
||||
;; I don't know why a `cl-defun' with
|
||||
;; (&rest strings &key (separator "\n")) doesn't work
|
||||
(separator (or (cl-loop for i from 0 upto (length strings)
|
||||
if (eq (nth i strings) :separator)
|
||||
return (nth (1+ i) strings))
|
||||
"\n")))
|
||||
(while strings
|
||||
(let ((string (pop strings)))
|
||||
(cond ((eq string :separator) (pop strings))
|
||||
((listp string) (push (apply #'concat string) ret))
|
||||
((stringp string) (push string ret)))))
|
||||
(mapconcat #'identity (nreverse ret) separator)))
|
||||
|
||||
(defun +file-string (file)
|
||||
"Fetch the contents of FILE and return its string."
|
||||
(with-current-buffer (find-file-noselect file)
|
||||
(buffer-string)))
|
||||
|
||||
(defmacro +with-progress (pr-args &rest body)
|
||||
"Perform BODY wrapped in a progress reporter.
|
||||
PR-ARGS is the list of arguments to pass to
|
||||
`make-progress-reporter'; it can be a single string for the
|
||||
message, as well. If you want to use a formatted string, wrap
|
||||
the `format' call in a list."
|
||||
(declare (indent 1))
|
||||
(let ((reporter (gensym))
|
||||
(pr-args (if (listp pr-args) pr-args (list pr-args))))
|
||||
`(let ((,reporter (make-progress-reporter ,@pr-args)))
|
||||
(prog1 (progn ,@body)
|
||||
(progress-reporter-done ,reporter)))))
|
||||
|
||||
(defmacro +with-eval-after-loads (features &rest body)
|
||||
"Execute BODY after all FEATURES are loaded."
|
||||
(declare (indent 1) (debug (form def-body)))
|
||||
(unless (listp features)
|
||||
(setq features (list features)))
|
||||
(if (null features)
|
||||
(macroexp-progn body)
|
||||
(let* ((this (car features))
|
||||
(rest (cdr features)))
|
||||
`(with-eval-after-load ',this
|
||||
(+with-eval-after-loads ,rest ,@body)))))
|
||||
|
||||
(defun +scratch-buffer (&optional nomode)
|
||||
"Create a new scratch buffer and switch to it.
|
||||
If the region is active, paste its contents into the scratch
|
||||
buffer. The scratch buffer inherits the mode of the current
|
||||
buffer unless NOMODE is non-nil. When called interactively,
|
||||
NOMODE will be set when called with \\[universal-argument]."
|
||||
(interactive "P")
|
||||
(let* ((mode major-mode)
|
||||
(bufname (generate-new-buffer-name (format "*scratch (%s)*" mode)))
|
||||
(paste (and (region-active-p)
|
||||
(prog1
|
||||
(buffer-substring (mark t) (point))
|
||||
(deactivate-mark)))))
|
||||
(when (and (not nomode)
|
||||
(bound-and-true-p ess-dialect)) ; Not sure what `ess-dialect' is
|
||||
(setq mode (intern-soft (concat ess-dialect "-mode"))))
|
||||
;; Set up buffer
|
||||
(switch-to-buffer (get-buffer-create bufname))
|
||||
(when (and (not nomode) mode)
|
||||
(ignore-errors (funcall mode)))
|
||||
(insert (format "%s Scratch buffer for %s%s\n\n"
|
||||
comment-start mode comment-end))
|
||||
(when paste (insert paste))
|
||||
(get-buffer bufname)))
|
||||
|
||||
(defun +indent-rigidly (arg &optional interactive)
|
||||
"Indent all lines in the region, or the current line.
|
||||
This calls `indent-rigidly' and passes ARG to it."
|
||||
(interactive "P\np")
|
||||
(unless (region-active-p)
|
||||
(push-mark)
|
||||
(push-mark (line-beginning-position) nil t)
|
||||
(goto-char (line-end-position)))
|
||||
(call-interactively #'indent-rigidly))
|
||||
|
||||
(defun +sort-lines (reverse beg end)
|
||||
"Sort lines in region, ignoring leading whitespace.
|
||||
REVERSE non-nil means descending order; interactively, REVERSE is
|
||||
the prefix argument, and BEG and END are the region. The
|
||||
variable `sort-fold-case' determines whether case affects the
|
||||
sort order."
|
||||
(interactive "P\nr")
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(narrow-to-region beg end)
|
||||
(goto-char (point-min))
|
||||
(let ((inhibit-field-text-motion t))
|
||||
(sort-subr reverse
|
||||
#'forward-line
|
||||
#'end-of-line
|
||||
#'beginning-of-line-text)))))
|
||||
|
||||
(defun +crm-indicator (args)
|
||||
"AROUND advice for `completing-read-multiple'."
|
||||
;; [[https://github.com/minad/vertico/blob/8ab2cddf3a1fb8799611b1d35118bf579aaf3154/README.org][from vertico's README]]
|
||||
(cons (format "[CRM%s] %s"
|
||||
(replace-regexp-in-string
|
||||
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
|
||||
crm-separator)
|
||||
(car args))
|
||||
(cdr args)))
|
||||
|
||||
|
||||
;;; Timers!
|
||||
;; inspired by [[https://git.sr.ht/~protesilaos/tmr/tree/main/item/tmr.el][prot's tmr.el package]]
|
||||
|
||||
(defvar +timer-string nil)
|
||||
(defvar +timer-timer nil)
|
||||
|
||||
(defcustom +timer-running-string "⏰"
|
||||
"What to display when the timer is running."
|
||||
:type 'string)
|
||||
(defcustom +timer-done-string "❗"
|
||||
"What to display when the timer is done."
|
||||
:type 'string)
|
||||
|
||||
(defun +timer (time)
|
||||
"Set a timer for TIME."
|
||||
(interactive (list (read-string "Set a timer for how long? ")))
|
||||
(let ((secs (cond ((natnump time) (* time 60))
|
||||
((and (stringp time)
|
||||
(string-match-p "[0-9]\\'" time))
|
||||
(* (string-to-number time) 60))
|
||||
(t (let ((secs 0)
|
||||
(time time))
|
||||
(save-match-data
|
||||
(while (string-match "\\([0-9.]+\\)\\([hms]\\)" time)
|
||||
(cl-incf secs
|
||||
(* (string-to-number (match-string 1 time))
|
||||
(pcase (match-string 2 time)
|
||||
("h" 3600)
|
||||
("m" 60)
|
||||
("s" 1))))
|
||||
(setq time (substring time (match-end 0)))))
|
||||
secs)))))
|
||||
(message "Setting timer for \"%s\" (%S seconds)..." time secs)
|
||||
(setq +timer-string +timer-running-string)
|
||||
(setq +timer-timer (run-with-timer secs nil
|
||||
(lambda ()
|
||||
(message "%S-second timer DONE!" secs)
|
||||
(setq +timer-string +timer-done-string)
|
||||
(let ((visible-bell t)
|
||||
(ring-bell-function nil))
|
||||
(ding))
|
||||
(ding))))))
|
||||
|
||||
(defun +timer-cancel ()
|
||||
"Cancel the running timer."
|
||||
(interactive)
|
||||
(cond ((not +timer-timer)
|
||||
(message "No timer found!"))
|
||||
(t
|
||||
(cancel-timer +timer-timer)
|
||||
(message "Timer canceled.")))
|
||||
(setq +timer-string nil))
|
||||
|
||||
|
||||
|
||||
(defun +switch-to-last-buffer ()
|
||||
"Switch to the last-used buffer in this window."
|
||||
(interactive)
|
||||
(switch-to-buffer nil))
|
||||
|
||||
(provide 'acdw)
|
||||
;;; acdw.el ends here
|
||||
|
|
74
lisp/dawn.el
Normal file
74
lisp/dawn.el
Normal file
|
@ -0,0 +1,74 @@
|
|||
;;; dawn.el --- Do things at dawn (and dusk) -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; There is also circadian.el, but it doesn't quite work for me.
|
||||
;; This code comes mostly from https://gnu.xyz/auto_theme.html, but also
|
||||
;; somewhere else (which I've forgotten) and my own brain :)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'calendar)
|
||||
(require 'cl-lib)
|
||||
(require 'solar)
|
||||
|
||||
(defvar dawn--dawn-timer nil
|
||||
"Timer for dawn-command.")
|
||||
|
||||
(defvar dawn--dusk-timer nil
|
||||
"Timer for dusk-command.")
|
||||
|
||||
(defvar dawn--reset-timer nil
|
||||
"Timer to reset dawn at midnight.")
|
||||
|
||||
(defun dawn-encode-time (f)
|
||||
"Encode fractional time F."
|
||||
(let ((hhmm (cl-floor f))
|
||||
(date (cdddr (decode-time))))
|
||||
(encode-time
|
||||
(append (list 0
|
||||
(round (* 60 (cadr hhmm)))
|
||||
(car hhmm)
|
||||
)
|
||||
date))))
|
||||
|
||||
(defun dawn-midnight ()
|
||||
"Return the time of the /next/ midnight."
|
||||
(let ((date (cdddr (decode-time))))
|
||||
(encode-time
|
||||
(append (list 0 0 0 (1+ (car date))) (cdr date)))))
|
||||
|
||||
(defun dawn-sunrise ()
|
||||
"Return the time of today's sunrise."
|
||||
(dawn-encode-time (caar (solar-sunrise-sunset (calendar-current-date)))))
|
||||
|
||||
(defun dawn-sunset ()
|
||||
"Return the time of today's sunset."
|
||||
(dawn-encode-time (caadr (solar-sunrise-sunset (calendar-current-date)))))
|
||||
|
||||
(defun dawn-schedule (dawn-command dusk-command)
|
||||
"Run DAWN-COMMAND at sunrise, and DUSK-COMMAND at dusk.
|
||||
RESET is an argument for internal use."
|
||||
(let ((dawn (dawn-sunrise))
|
||||
(dusk (dawn-sunset)))
|
||||
(cond
|
||||
((time-less-p nil dawn)
|
||||
;; If it isn't dawn yet, it's still dark. Run DUSK-COMMAND and schedule
|
||||
;; DAWN-COMMAND and DUSK-COMMAND for later.
|
||||
(funcall dusk-command)
|
||||
(run-at-time dawn nil dawn-command)
|
||||
(run-at-time dusk nil dusk-command))
|
||||
((time-less-p nil dusk)
|
||||
;; If it isn't dusk yet, it's still light. Run DAWN-COMMAND and schedule
|
||||
;; DUSK-COMMAND.
|
||||
(funcall dawn-command)
|
||||
(run-at-time dusk nil dusk-command))
|
||||
(t ;; Otherwise, it's past dusk, so run DUSK-COMMAND.
|
||||
(funcall dusk-command)))
|
||||
;; Schedule a reset at midnight, to re-calculate dawn/dusk times.
|
||||
;(unless reset)
|
||||
(run-at-time (dawn-midnight) nil
|
||||
#'dawn-schedule dawn-command dusk-command)))
|
||||
|
||||
(provide 'dawn)
|
||||
;;; dawn.el ends here
|
58
lisp/elephant.el
Normal file
58
lisp/elephant.el
Normal file
|
@ -0,0 +1,58 @@
|
|||
;;; elephant.el --- Remember variables and modes -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defmacro elephant-remember (alist)
|
||||
"Setup a closure remembering symbols to apply with
|
||||
`remember-reset'. The variables will be renamed using TEMPLATE.
|
||||
ALIST contains cells of the form (SYMBOL . NEW-VALUE), where
|
||||
SYMBOL is a variable or mode name, and its value is what to set
|
||||
after `remember-set'."
|
||||
(unless lexical-binding
|
||||
(user-error "`elephant' requires lexical binding."))
|
||||
|
||||
(let* ((template (format "elephant--%s-%%s" (gensym)))
|
||||
(reset-fn (intern (format template "reset"))))
|
||||
(cl-destructuring-bind (let-list fn-set-list fn-reset-list)
|
||||
(cl-loop
|
||||
for (sym . val) in (if (symbolp alist) (symbol-value alist) alist)
|
||||
as rem = (intern (format template sym))
|
||||
|
||||
collect (list rem sym)
|
||||
into let-list
|
||||
|
||||
collect (cond ((eq val 'enable)
|
||||
`(,sym +1))
|
||||
((eq val 'disable)
|
||||
`(,sym -1))
|
||||
(t `(setq-local ,sym ,val)))
|
||||
into fn-set-list
|
||||
|
||||
collect (cond ((memq val '(enable disable))
|
||||
`(progn (,sym (if ,rem +1 -1))
|
||||
(fmakunbound ',rem)))
|
||||
(t `(progn (setq-local ,sym ,rem)
|
||||
(makunbound ',rem))))
|
||||
into fn-reset-list
|
||||
|
||||
finally return (list let-list
|
||||
fn-set-list
|
||||
fn-reset-list))
|
||||
`(progn
|
||||
(defvar-local ,reset-fn nil
|
||||
"Function to recall values from `elephant-remember'.")
|
||||
(let ,let-list
|
||||
(setf (symbol-function ',reset-fn)
|
||||
(lambda ()
|
||||
,@fn-reset-list
|
||||
(redraw-display)
|
||||
(fmakunbound ',reset-fn))))
|
||||
,@fn-set-list
|
||||
',reset-fn))))
|
||||
|
||||
(defun elephant-forget ()
|
||||
"Forget all symbols generated by `elephant-remember'."
|
||||
)
|
||||
|
||||
(provide 'elephant)
|
||||
;;; elephant.el ends here
|
36
lisp/find-script.el
Normal file
36
lisp/find-script.el
Normal file
|
@ -0,0 +1,36 @@
|
|||
;;; find-script.el --- Find a script in $PATH -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This package makes it easier to find a script to edit in $PATH. The initial
|
||||
;; `rehash-exes' is slow, but it's stored in `*exes*' as a caching mechanism.
|
||||
;; However, I'm sure it could be improved.
|
||||
|
||||
;; In addition, `*exes*' currently contains /all/ executables in $PATH, which
|
||||
;; ... maybe only the ones stored in some text format should be shown.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defvar *exes* nil
|
||||
"All the exectuables in $PATH.
|
||||
Run `rehash-exes' to refresh this variable.")
|
||||
|
||||
(defun rehash-exes ()
|
||||
"List all the executables in $PATH.
|
||||
Also sets `*exes*' parameter."
|
||||
(setq *exes*
|
||||
(cl-loop for dir in exec-path
|
||||
append (file-expand-wildcards (concat dir "*"))
|
||||
into exes
|
||||
finally return exes)))
|
||||
|
||||
;;;###autoload
|
||||
(defun find-script (script)
|
||||
"Find a file in $PATH."
|
||||
(interactive
|
||||
(list (let ((exes (or *exes* (rehash-exes))))
|
||||
(completing-read "Script> " exes nil t))))
|
||||
(find-file script))
|
||||
|
||||
(provide 'find-script)
|
||||
;;; find-script.el ends here
|
130
lisp/gdrive.el
Normal file
130
lisp/gdrive.el
Normal file
|
@ -0,0 +1,130 @@
|
|||
;;; gdrive.el --- Gdrive integration -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2022 Case Duckworth
|
||||
|
||||
;; Author: Case Duckworth <case@bob>
|
||||
;; Keywords: convenience, data, docs
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; [[https://github.com/prasmussen/gdrive][gdrive]] is a Go program to interface with Google Drive. This library connects
|
||||
;; that to Emacs.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(defgroup gdrive nil
|
||||
"Customizations for Emacs's gdrive module."
|
||||
:group 'applications
|
||||
:prefix "gdrive-")
|
||||
|
||||
(defcustom gdrive-bin (executable-find "gdrive")
|
||||
"Where gdrive binary is located."
|
||||
:type 'string)
|
||||
|
||||
(defcustom gdrive-buffer "*gdrive*"
|
||||
"Default buffer for gdrive output."
|
||||
:type 'string)
|
||||
|
||||
;;; Global flags
|
||||
|
||||
;;;; -c, --config <configDir>
|
||||
;;;;; Application path, default: /Users/<user>/.gdrive
|
||||
(defcustom gdrive-config-dir nil
|
||||
"Application path.")
|
||||
|
||||
;;;; --refresh-token <refreshToken>
|
||||
;;;;; Oauth refresh token used to get access token (for advanced users)
|
||||
(defcustom gdrive-refresh-token nil
|
||||
"Oauth refresh token used to get access token.
|
||||
(For advanced users).")
|
||||
|
||||
;;;; --access-token <accessToken>
|
||||
;;;;; Oauth access token, only recommended for short-lived requests because of
|
||||
;;;;; short lifetime (for advanced users)
|
||||
(defcustom gdrive-access-token nil
|
||||
"Oauth access token.
|
||||
Only recommended for short-lived requests because of short
|
||||
lifetime (for advanced users).")
|
||||
|
||||
;;;; --service-account <accountFile>
|
||||
;;;;; Oauth service account filename, used for server to server communication
|
||||
;;;;; without user interaction (file is relative to config dir)
|
||||
(defcustom gdrive-service-account nil
|
||||
"Oauth service account filename.
|
||||
Used for server to server communication without user
|
||||
interaction (file is relative to config dir).")
|
||||
|
||||
(defun gdrive--global-arguments ()
|
||||
"Build global arguments for gdrive."
|
||||
(append
|
||||
(when gdrive-config-dir (list "--config" gdrive-config-dir))
|
||||
(when gdrive-refresh-token (list "--refresh-token" gdrive-refresh-token))
|
||||
(when gdrive-access-token (list "--access-token" gdrive-access-token))
|
||||
(when gdrive-service-account (list "--service-account" gdrive-service-account))))
|
||||
|
||||
;;; List files
|
||||
;; gdrive [global] list [options]
|
||||
;;;; -m, --max <maxFiles>
|
||||
;;;; Max files to list, default: 30
|
||||
;;;; -q, --query <query>
|
||||
;;;;; Default query: "trashed = false and 'me' in owners". See https://developers.google.com/drive/search-parameters
|
||||
;;;; --order <sortOrder>
|
||||
;;;;; Sort order. See https://godoc.org/google.golang.org/api/drive/v3#FilesListCall.OrderBy
|
||||
;;;; --name-width <nameWidth>
|
||||
;;;;; Width of name column, default: 40, minimum: 9, use 0 for full width
|
||||
;; NOTE: gdrive-list will pass 0 for this argument.
|
||||
;;;; --absolute Show absolute path to file (will only show path from first parent)
|
||||
;;;; --no-header Dont print the header
|
||||
;; NOTE: gdrive-list will always pass this argument.
|
||||
;;;; --bytes Size in bytes
|
||||
(cl-defun gdrive-list (&key max query order absolute no-header bytes)
|
||||
"Run the \"gdrive list\" command.
|
||||
MAX is the max files to list; it defaults to 30. QUERY is the
|
||||
query to pass; the default is \"trashed = false and 'me' in
|
||||
owners\"."
|
||||
(gdrive--run (append (gdrive--global-arguments)
|
||||
(list "list")
|
||||
(when max (list "--max" max))
|
||||
(when query (list "--query" query))
|
||||
(when order (list "--order" order))
|
||||
(list "--name-width" "0")
|
||||
(when absolute (list "--absolute"))
|
||||
(when no-header (list "--no-header"))
|
||||
(when bytes (list "--bytes")))))
|
||||
|
||||
|
||||
(defmacro gdrive-query)
|
||||
|
||||
|
||||
(defun gdrive--build-command-name (command)
|
||||
"INTERNAL: Build a string name for COMMAND."
|
||||
(concat "gdrive-" (car command)))
|
||||
|
||||
(defun gdrive--run (command &optional buffer)
|
||||
"Run 'gdrive COMMAND', collecting results in BUFFER.
|
||||
COMMAND, if not a list, will be made a list and appended to
|
||||
`gdrive-bin'.
|
||||
BUFFER defaults to `gdrive-buffer'."
|
||||
(let ((command (if (listp command) command (list command)))
|
||||
(buffer (or buffer gdrive-buffer)))
|
||||
(make-process :name (gdrive--build-command-name command)
|
||||
:buffer buffer
|
||||
:command (cons gdrive-bin command))))
|
||||
|
||||
(provide 'gdrive)
|
||||
;;; gdrive.el ends here
|
67
lisp/long-s-mode.el
Normal file
67
lisp/long-s-mode.el
Normal file
|
@ -0,0 +1,67 @@
|
|||
;;; long-s-mode.el --- Proper typography for Emacs -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; from Catie on #emacs
|
||||
|
||||
;;; Code:
|
||||
|
||||
(define-minor-mode long-s-mode
|
||||
"Minor mode for inserting 'ſ' characters")
|
||||
|
||||
(defconst +long-s+ ?ſ)
|
||||
(defconst +short-s+ ?s)
|
||||
|
||||
(defun long-s-p (char)
|
||||
(char-equal char +long-s+))
|
||||
|
||||
(defun short-s-p (char)
|
||||
(or (char-equal char +short-s+)))
|
||||
|
||||
(defun s-char-p (char)
|
||||
(or (long-s-p char)
|
||||
(short-s-p char)))
|
||||
|
||||
(defun alpha-char-p (char)
|
||||
(memq (get-char-code-property char 'general-category)
|
||||
'(Ll Lu Lo Lt Lm Mn Mc Me Nl)))
|
||||
|
||||
(defun long-s-insert-short-s ()
|
||||
(interactive)
|
||||
(if (long-s-p (preceding-char))
|
||||
(insert-char +short-s+)
|
||||
(insert-char +long-s+)))
|
||||
|
||||
(defun long-s-insert-space ()
|
||||
(interactive)
|
||||
(if (long-s-p (preceding-char))
|
||||
(progn (delete-backward-char 1)
|
||||
(insert-char +short-s+))
|
||||
(save-excursion
|
||||
(while (not (alpha-char-p (preceding-char)))
|
||||
(backward-char))
|
||||
(when (long-s-p (preceding-char))
|
||||
(delete-backward-char 1)
|
||||
(insert-char +short-s+))))
|
||||
(insert-char ?\ ))
|
||||
|
||||
(defvar long-s-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(set-keymap-parent map (current-global-map))
|
||||
(define-key map (kbd "s") #'long-s-insert-short-s)
|
||||
(define-key map (kbd "SPC") #'long-s-insert-space)
|
||||
map))
|
||||
|
||||
(setq long-s-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "s") #'long-s-insert-short-s)
|
||||
(define-key map (kbd "SPC") #'long-s-insert-space)
|
||||
map))
|
||||
|
||||
(unless (seq-some #'(lambda (x) (eq (car x) 'long-s-mode))
|
||||
minor-mode-map-alist)
|
||||
(push (cons 'long-s-mode long-s-mode-map)
|
||||
minor-mode-map-alist))
|
||||
|
||||
(provide 'long-s-mode)
|
||||
;;; long-s-mode.el ends here
|
23
lisp/private.el
Normal file
23
lisp/private.el
Normal file
|
@ -0,0 +1,23 @@
|
|||
;;; private.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'acdw)
|
||||
|
||||
(defgroup private nil
|
||||
"Private things are private. Shhhhh....")
|
||||
|
||||
;; Private directory
|
||||
|
||||
(+define-dir private/ (sync/ "emacs/private")
|
||||
"Private secretive secrets inside.")
|
||||
(add-to-list 'load-path private/)
|
||||
|
||||
;; Load random private stuff
|
||||
|
||||
(require '_acdw)
|
||||
|
||||
(provide 'private)
|
||||
;;; private.el ends here
|
|
@ -58,9 +58,7 @@ FUNC should be a function with the signature (THING REMEMBERED-SETTING)."
|
|||
;;;###autoload
|
||||
(define-minor-mode reading-mode
|
||||
"A mode for reading."
|
||||
:init-value nil
|
||||
:lighter " Read"
|
||||
:keymap (make-sparse-keymap)
|
||||
(if reading-mode
|
||||
;; turn on
|
||||
(progn
|
||||
|
|
240
lisp/system.el
240
lisp/system.el
|
@ -1,11 +1,17 @@
|
|||
;;; system.el --- System-specific configuration -*- lexical-binding: t; -*-
|
||||
;;; system.el --- Load system-dependendant settings -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; When using Emacs on separate computers, some variables need different
|
||||
;; settings. This library contains functions and variables to work with
|
||||
;; When using Emacs on multiple computers, some variables and functions need
|
||||
;; different definitions. This library is built to assist in working with
|
||||
;; different system configurations for Emacs.
|
||||
|
||||
;;; TODO:
|
||||
|
||||
;; machine.el
|
||||
;; machine-case to switch on machine
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
@ -15,145 +21,159 @@
|
|||
:group 'emacs
|
||||
:prefix "system-")
|
||||
|
||||
;;; Variables
|
||||
;;; Settings
|
||||
|
||||
(defcustom system-load-alist '((system-microsoft-p . windows)
|
||||
(system-linux-p . linux))
|
||||
"Alist describing which system Emacs is on.
|
||||
Each cell is of the form (PREDICATE . SYSTEM), where PREDICATE is
|
||||
a function of no arguments and SYSTEM is a string or symbol that
|
||||
will be passed to `system-settings-load'.
|
||||
|
||||
This list need not be exhaustive; see `system-settings-load' for
|
||||
more details on what happens if this alist is exhausted."
|
||||
:type '(alist :key-type function :value-type (choice string symbol)))
|
||||
|
||||
(defcustom system-load-directory (locate-user-emacs-file "systems")
|
||||
"The directory from which to load system-specific configurations."
|
||||
(defcustom system-load-directory (locate-user-emacs-file "systems"
|
||||
"~/.emacs-systems")
|
||||
"The directory where system-specific configurations live."
|
||||
:type 'file)
|
||||
|
||||
;; `defcustoms' defined here are best-guess defaults.
|
||||
;; These `defcustom's are best-guess defaults.
|
||||
|
||||
(defcustom system-default-font (pcase system-type
|
||||
((or 'ms-dos 'windows-nt)
|
||||
"Consolas")
|
||||
(_ "monospace"))
|
||||
"The font used for the `default' face."
|
||||
(defcustom system-default-font (cond
|
||||
((memq system-type '(ms-dos windows-nt))
|
||||
"Consolas")
|
||||
(t "monospace"))
|
||||
"The font used for the `default' face.
|
||||
Set this in your system files."
|
||||
:type 'string)
|
||||
|
||||
(defcustom system-default-height 100
|
||||
"The height used for the `default' face."
|
||||
"The height used for the `default' face.
|
||||
Set this in your system files."
|
||||
:type 'number)
|
||||
|
||||
(defcustom system-variable-pitch-font (pcase system-type
|
||||
((or 'ms-dos 'windows-nt)
|
||||
"Arial")
|
||||
(_ "sans-serif"))
|
||||
"The font used for the `variable-pitch' face."
|
||||
(defcustom system-variable-pitch-font (cond
|
||||
((memq system-type '(ms-dos windows-nt))
|
||||
"Arial")
|
||||
(t "sans-serif"))
|
||||
"The font used for the `variable-pitch' face.
|
||||
Set this in your system files."
|
||||
:type 'string)
|
||||
|
||||
(defcustom system-variable-pitch-height 1.0
|
||||
"The height used for the `variable-pitch' face.
|
||||
A floating-point number is recommended, since that makes it
|
||||
relative to the `default' face height."
|
||||
relative to the `default' face height.
|
||||
|
||||
Set this in your system files."
|
||||
:type 'number)
|
||||
|
||||
(defcustom system-files-order '(:type :name :user)
|
||||
"The order to load `system-files' in.
|
||||
The elements of this list correspond to the keys in
|
||||
`system-system'."
|
||||
:type '(list (const :tag "System type" :type)
|
||||
(const :tag "System name" :name)
|
||||
(const :tag "Current user" :user)))
|
||||
|
||||
;;; Variables
|
||||
|
||||
(defvar system-system nil
|
||||
"The current system's symbol.
|
||||
Do not edit this by hand. Instead, call `system-get-system'.")
|
||||
"Plist of systems that Emacs is in.
|
||||
The keys are as follows:
|
||||
|
||||
(defvar system-file nil
|
||||
"The current system's file for system-specific configuration.
|
||||
Do not edit this by hand. Instead, call `system-get-system-file'.")
|
||||
- :name - `system-name'
|
||||
- :type - `system-type'
|
||||
- :user - `user-login-name'
|
||||
|
||||
Each value is made safe to be a file name by passing through
|
||||
`system--safe'.
|
||||
|
||||
Do not edit this by hand. Instead, call `system-get-systems'.")
|
||||
|
||||
(defvar system-files nil
|
||||
"List of files to load for system-specific configuration.
|
||||
Do not edit this by hand. Instead, call `system-get-system-files'.")
|
||||
|
||||
|
||||
;;; Functions
|
||||
|
||||
;; Convenience functions for systems
|
||||
(defun system-microsoft-p ()
|
||||
"Return non-nil if running in a Microsoft system."
|
||||
(memq system-type '(ms-dos windows-nt)))
|
||||
|
||||
(defun system-linux-p ()
|
||||
"Return non-nil if running on a Linux system."
|
||||
(memq system-type '(gnu/linux)))
|
||||
|
||||
(defun system-warn (message &rest args)
|
||||
"Display a wraning message made from (format-message MESSAGE ARGS...).
|
||||
This function is like `warn', except it uses the `system' type."
|
||||
(defun system--warn (message &rest args)
|
||||
"Display a system-file warning message.
|
||||
This function is like `warn', except it uses a `system' type."
|
||||
(display-warning 'system (apply #'format-message message args)))
|
||||
|
||||
(defun system-get-system ()
|
||||
"Determine the current system."
|
||||
(cl-loop for (p . s) in system-load-alist
|
||||
if (with-demoted-errors (format "Problem running function `%s'" p)
|
||||
(funcall p))
|
||||
return (setq system-system s)))
|
||||
(defun system--safe (str)
|
||||
"Make STR safe for a file name."
|
||||
(let ((bad-char-regexp ))
|
||||
(downcase (string-trim
|
||||
(replace-regexp-in-string "[#%&{}\$!'\":@<>*?/ \r\n\t+`|=]+"
|
||||
"-" str)
|
||||
"-" "-"))))
|
||||
|
||||
(defun system-get-system-file (&optional system refresh-cache set-system-file-p)
|
||||
"Determine the current system's system-specific file.
|
||||
The current system's file will be returned. The value of
|
||||
`system-file' is set, /unless/ the parameter SYSTEM was passed to
|
||||
this function and SET-SYSTEM-FILE-P is nil. If both SYSTEM and
|
||||
SET-SYSTEM-FILE-P are non-nil, this function will still set
|
||||
`system-file'.
|
||||
(defun system-get-systems ()
|
||||
"Determine the current system(s).
|
||||
This system updates `system-system', which see."
|
||||
;; Add system-name
|
||||
(setf (plist-get system-system :name)
|
||||
(intern (system--safe (system-name))))
|
||||
;; Add system-type
|
||||
(setf (plist-get system-system :type)
|
||||
(intern (system--safe (symbol-name system-type))))
|
||||
;; Add current user
|
||||
(setf (plist-get system-system :user)
|
||||
;; Use `user-real-login-name' in case Emacs gets called under su.
|
||||
(intern (system--safe (user-real-login-name))))
|
||||
system-system)
|
||||
|
||||
If SYSTEM is not passed, and `system-file' is set, simply return
|
||||
its value /unless/ REFRESH-CACHE is non-nil, in which case
|
||||
`system-load-alist' will be looped through to find the
|
||||
appropriate system by testing the car of each cell there. When
|
||||
one matches, use the cdr of that cell as SYSTEM. If none
|
||||
matches, return nil.
|
||||
(defun system-get-files ()
|
||||
"Determine the current systems' load-files.
|
||||
The system load-files should live in `system-load-directory', and
|
||||
named using either the raw name given by the values of
|
||||
`system-system', or that name prepended with the type, e.g.,
|
||||
\"name-bob.el\", for a system named \"bob\".
|
||||
|
||||
This function will only look for system-specific files in
|
||||
`system-load-directory'."
|
||||
(let* ((system* (or system
|
||||
(and system-file (not refresh-cache))
|
||||
(system-get-system)))
|
||||
(file (expand-file-name (format "%s" system*) system-load-directory)))
|
||||
(when (or (not system)
|
||||
(and system set-system-file-p))
|
||||
(setq system-file file))
|
||||
file))
|
||||
The second form of file-name is to work around name collisions,
|
||||
e.g. if a there's a user named \"bob\" and a system named
|
||||
\"bob\".
|
||||
|
||||
This function updates `system-files'."
|
||||
;; Get systems
|
||||
(system-get-systems)
|
||||
;; Re-set `system-files'
|
||||
(setq system-files nil)
|
||||
|
||||
(let (ret)
|
||||
(dolist (key (reverse system-files-order))
|
||||
(let* ((val (plist-get system-system key))
|
||||
(key-val (intern (system--safe (format "%s-%s" key val)))))
|
||||
(push (list key-val val) ret)))
|
||||
|
||||
;; Update `system-files'.
|
||||
(setq system-files ret)))
|
||||
|
||||
;;;###autoload
|
||||
(defun system-settings-load (&optional system error nomessage)
|
||||
"Load system settings.
|
||||
Load settings from `system-file', or the `system-file' as
|
||||
determined by SYSTEM, if passed. See `system-get-system-file' for
|
||||
details on how the `system-file' is determined.
|
||||
(defun system-settings-load (&optional error nomessage)
|
||||
"Load system settings from `system-files'.
|
||||
Each list in `system-files' will be considered item-by-item; the
|
||||
first found file in each will be loaded.
|
||||
|
||||
ERROR determines how to deal with errors: if nil, warn the user
|
||||
when `system-file' can't be found or when the system being used
|
||||
can't be determined. If t, those are elevated to errors. If any
|
||||
other value, the errors are completely ignored.
|
||||
when no system-files can be found or when the system being used
|
||||
cannot be determined. If t, these warnings are elevated to
|
||||
errors. Any other value ignores the warnings completely.
|
||||
|
||||
NOMESSAGE is passed directly to `load'."
|
||||
(let ((file (system-get-system-file system)))
|
||||
(if file
|
||||
(condition-case e
|
||||
(load file nil nomessage)
|
||||
(t (cond ((eq error t) (signal (car e) (cdr e)))
|
||||
((null error) (system-warn "Couldn't find file `%s'."
|
||||
file)))))
|
||||
(funcall (cond ((eq error t) #'error)
|
||||
((null error) #'system-warn)
|
||||
(t #'ignore))
|
||||
"Could not determine the system being used."))))
|
||||
|
||||
;;;###autoload
|
||||
(defun system-find-system-file (&optional system)
|
||||
"Find the current system's system-file."
|
||||
(interactive (list (completing-read "System file: "
|
||||
(mapcar (lambda (a) (format "%s" (cdr a)))
|
||||
system-load-alist)
|
||||
nil t nil nil
|
||||
(format "%s" (system-get-system)))))
|
||||
(find-file (cl-loop with file = (system-get-system-file system)
|
||||
for cand in (list file
|
||||
(concat file ".el"))
|
||||
if (file-exists-p cand)
|
||||
return cand
|
||||
finally return cand)))
|
||||
(system-get-files)
|
||||
(if system-files
|
||||
(let (files-loaded)
|
||||
(dolist (ss system-files)
|
||||
(catch :done
|
||||
(dolist (s ss)
|
||||
(let ((fn (expand-file-name (format "%s" s)
|
||||
system-load-directory)))
|
||||
(when (load fn t nomessage)
|
||||
(push fn files-loaded)
|
||||
(throw :done nil))))))
|
||||
(unless files-loaded
|
||||
(cond ((eq error t) (error "Error loading system-files.")
|
||||
(null error) (system--warn "Couldn't load system-files."))))
|
||||
files-loaded)
|
||||
(funcall (cond ((eq error t) #'error)
|
||||
((null error) #'system--warn)
|
||||
(t #'ignore))
|
||||
"Couldn't determine the system being used.")))
|
||||
|
||||
(provide 'system)
|
||||
;;; system.el ends here
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
;;; titlecase.el --- title-case phrases -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; adapted from https://hungyi.net/posts/programmers-way-to-title-case/
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'seq)
|
||||
|
||||
(defgroup titlecase nil
|
||||
"Customizations for titlecasing phrases."
|
||||
:prefix "titlecase-"
|
||||
:group 'text)
|
||||
|
||||
;;; Lists of words /never/ to capitalize
|
||||
|
||||
(defvar titlecase-prepositions
|
||||
'("'thout" "'tween" "aboard" "about" "above"
|
||||
"abreast" "absent" "abt." "across" "after" "against" "ago" "aloft" "along"
|
||||
"alongside" "amid" "amidst" "among" "amongst" "anti" "apart" "apropos"
|
||||
"around" "as" "aside" "aslant" "astride" "at" "atop" "away" "before"
|
||||
"behind" "below" "beneath" "beside" "besides" "between" "beyond" "but" "by"
|
||||
"c." "ca." "circa" "come" "concerning" "contra" "counting" "cum" "despite"
|
||||
"down" "during" "effective" "ere" "except" "excepting" "excluding" "failing"
|
||||
"following" "for" "from" "hence" "in" "including" "inside" "into" "less"
|
||||
"like" "mid" "midst" "minus" "mod" "modulo" "near" "nearer" "nearest"
|
||||
"neath" "next" "notwithstanding" "o'" "o'er" "of" "off" "offshore" "on"
|
||||
"onto" "ontop" "opposite" "out" "outside" "over" "pace" "past" "pending"
|
||||
"per" "plus" "post" "pre" "pro" "qua" "re" "regarding" "respecting" "round"
|
||||
"sans" "save" "saving" "short" "since" "sub" "t'" "than" "through"
|
||||
"throughout" "thru" "thruout" "till" "times" "to" "toward" "towards" "under"
|
||||
"underneath" "unlike" "until" "unto" "up" "upon" "v." "versus" "via"
|
||||
"vis-à-vis" "vs." "w." "w/" "w/i" "w/o" "wanting" "with" "within"
|
||||
"without")
|
||||
"List of prepositions in English.
|
||||
This list is, by necessity, incomplete, even though prepositions
|
||||
are a closed lexical group in the English language. This list
|
||||
was pulled and culled from
|
||||
https://en.wikipedia.org/wiki/List_of_English_prepositions.")
|
||||
|
||||
(defvar titlecase-articles '("a" "an" "the")
|
||||
"List of articles in English.")
|
||||
|
||||
(defvar titlecase-coordinating-conjunctions '("for" "and" "nor" "but" "or"
|
||||
"yet" "so")
|
||||
"List of coordinating conjunctions in English.")
|
||||
|
||||
(defvar titlecase-lowercase-chicago (append titlecase-articles
|
||||
titlecase-prepositions
|
||||
titlecase-coordinating-conjunctions)
|
||||
"Words to lowercase in Chicago Style.
|
||||
Include: articles, coordinating conjunctions, prepositions, and
|
||||
\"to\" in an infinitive (though that's caught as a preposition).")
|
||||
|
||||
(defvar titlecase-lowercase-apa (append titlecase-articles
|
||||
(seq-filter (lambda (p)
|
||||
(< (length p) 4))
|
||||
titlecase-prepositions))
|
||||
"Words to lowercase in APA Style.")
|
||||
|
||||
(defvar titlecase-lowercase-mla (append titlecase-articles
|
||||
titlecase-prepositions
|
||||
titlecase-coordinating-conjunctions)
|
||||
"Words to lowercase in MLA Style.")
|
||||
|
||||
(defvar titlecase-lowercase-ap (append titlecase-articles
|
||||
(seq-filter (lambda (p)
|
||||
(< (length p) 4))
|
||||
titlecase-prepositions)
|
||||
(seq-filter
|
||||
(lambda (p)
|
||||
(< (length p) 4))
|
||||
titlecase-coordinating-conjunctions))
|
||||
"Words to lowercase in AP Style.")
|
||||
|
||||
(defvar titlecase-lowercase-bluebook (append titlecase-articles
|
||||
titlecase-coordinating-conjunctions
|
||||
(seq-filter
|
||||
(lambda (p)
|
||||
(< (length p) 4))
|
||||
titlecase-prepositions))
|
||||
"Words to lowercase in Bluebook Style.")
|
||||
|
||||
(defvar titlecase-lowercase-ama (append titlecase-articles
|
||||
titlecase-coordinating-conjunctions
|
||||
(seq-filter (lambda (p)
|
||||
(< (length p) 4))
|
||||
titlecase-prepositions))
|
||||
"Words to lowercase in AMA Style.")
|
||||
|
||||
(defvar titlecase-lowercase-nyt (append titlecase-articles
|
||||
titlecase-prepositions
|
||||
titlecase-coordinating-conjunctions)
|
||||
"Words to lowercase in New York Times Style.")
|
||||
|
||||
(defvar titlecase-lowercase-wikipedia
|
||||
(append titlecase-articles
|
||||
(seq-filter (lambda (p) (< (length p) 5)) titlecase-prepositions)
|
||||
titlecase-coordinating-conjunctions)
|
||||
"Words to lowercase in Wikipedia Style.")
|
||||
|
||||
(defcustom titlecase-style 'chicago
|
||||
"Which style to use when titlecasing."
|
||||
:type '(choice (const :tag "Chicago Style" chicago)
|
||||
(const :tag "APA Style" apa)
|
||||
(const :tag "MLA Style" mla)
|
||||
(const :tag "AP Style" ap)
|
||||
(const :tag "Bluebook Style" bluebook)
|
||||
(const :tag "AMA Style" ama)
|
||||
(const :tag "New York Times Style" nyt)
|
||||
(const :tag "Wikipedia Style" wikipedia)))
|
||||
|
||||
(defun titlecase--normalize (begin end)
|
||||
"Normalize region from BEGIN to END."
|
||||
(goto-char begin)
|
||||
(unless (re-search-forward "[a-z]" end :noerror)
|
||||
(downcase-region begin end)))
|
||||
|
||||
(defun titlecase--capitalize-first-word (begin end)
|
||||
"Capitalize the first word of region from BEGIN to END."
|
||||
(goto-char begin)
|
||||
(capitalize-word 1))
|
||||
|
||||
(defun titlecase--capitalize-last-word (begin end)
|
||||
"Capitalize the last word of region from BEGIN to END."
|
||||
(goto-char end)
|
||||
(backward-word 1)
|
||||
(when (and (>= (point) begin))
|
||||
(capitalize-word 1)))
|
||||
|
||||
|
||||
|
||||
|
||||
(defun titlecase-region-with-style (begin end style)
|
||||
"Titlecase the region of English text from BEGIN to END, using STYLE."
|
||||
(interactive "*r")
|
||||
(save-excursion
|
||||
(goto-char begin)
|
||||
;; If the region is in ALL-CAPS, normalize it first
|
||||
(unless (re-search-forward "[a-z]" end :noerror)
|
||||
(downcase-region begin end))
|
||||
(goto-char begin) ; gotta go back to the beginning
|
||||
(let (;; Constants during this function's runtime
|
||||
(case-fold-search nil)
|
||||
(downcase-word-list (symbol-value
|
||||
(intern (format "titlecase-lowercase-%s"
|
||||
style))))
|
||||
;; State variables
|
||||
(this-word (current-word))
|
||||
(force-capitalize t))
|
||||
;; And loop over the rest
|
||||
(while (< (point) end)
|
||||
(setq this-word (current-word))
|
||||
(cond
|
||||
;; Skip ALL-CAPS words
|
||||
((string-match "^[A-Z]+$" this-word) (forward-word 1))
|
||||
;; Force capitalization if `force-capitalize' is t
|
||||
(force-capitalize (progn (capitalize-word 1)
|
||||
(setq force-capitalize nil)))
|
||||
;; Special rules for different styles
|
||||
((and (memq style '(ap))
|
||||
(> (length this-word) 3))
|
||||
(capitalize-word 1))
|
||||
;; Downcase words that should be
|
||||
((member (downcase this-word) downcase-word-list)
|
||||
(downcase-word 1))
|
||||
;; Otherwise, capitalize the word
|
||||
(t (capitalize-word 1)))
|
||||
;; If the word ends with a :, ., ?, newline, or carriage-return, force
|
||||
;; the next word to be capitalized.
|
||||
(when (looking-at "[:.?;\n\r]")
|
||||
(setq force-capitalize t))
|
||||
(skip-syntax-forward "^w" end))
|
||||
;; Capitalize the last word, only in some styles
|
||||
(when (memq style '(chicago ap bluebook ama nyt wikipedia))
|
||||
(backward-word 1)
|
||||
(when (and (>= (point) begin))
|
||||
(capitalize-word 1))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun titlecase-region (begin end)
|
||||
"Titlecase the region of English text from BEGIN to END.
|
||||
Uses the style provided in `titlecase-style'."
|
||||
(interactive "*r")
|
||||
(titlecase-region-with-style begin end titlecase-style))
|
||||
|
||||
;;;###autoload
|
||||
(defun titlecase-dwim ()
|
||||
"Titlecase either the region, if active, or the current line."
|
||||
(interactive)
|
||||
(if (region-active-p)
|
||||
(titlecase-region (region-beginning) (region-end))
|
||||
(titlecase-region (point-at-bol) (point-at-eol))))
|
||||
|
||||
(provide 'titlecase)
|
||||
;;; titlecase.el ends here
|
129
lisp/user-save.el
Normal file
129
lisp/user-save.el
Normal file
|
@ -0,0 +1,129 @@
|
|||
;;; user-save.el --- Do things when explicitly saving files -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2021--2022 Case Duckworth <acdw@acdw.net>
|
||||
;; URL: ...
|
||||
;; Version: 0.1.0
|
||||
;; Package-Requires: ((emacs "24.3"))
|
||||
;; Keywords: files
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Because `super-save-mode' automatically saves every time we move away from a
|
||||
;; buffer, it tends to run a lot of `before-save-hook's that don't need to be
|
||||
;; run that often. For that reason, I'm writing a mode where C-x C-s saves
|
||||
;; /and/ runs all the "real" before-save-hooks, so that super-save won't
|
||||
;; automatically do things like format the buffer all the time.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(defgroup user-save nil
|
||||
"Group for `user-save-mode' customizations."
|
||||
:group 'emacs
|
||||
:prefix "user-save-")
|
||||
|
||||
(defcustom user-save-hook-into-kill-emacs nil
|
||||
"Add a hook to perform `user-save' to `kill-emacs-hook'.
|
||||
This option is only useful is `user-save-mode' is active when
|
||||
Emacs is killed."
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom user-save-inhibit-modes '(special-mode)
|
||||
"List of modes to inhibit `user-save-mode' from activation in."
|
||||
:type '(repeat symbol))
|
||||
|
||||
(defcustom user-save-inhibit-predicates '(user-save-non-file-buffer-p)
|
||||
"List of predicates to inhibit `user-save-mode' from activation.
|
||||
Each predicate will be called with no arguments, and if it
|
||||
returns t, will inhibit `user-save-mode' from activating."
|
||||
:type '(repeat function))
|
||||
|
||||
(defvar user-save-hook nil
|
||||
"Hook to run when the user, not Emacs, saves the buffer.")
|
||||
|
||||
(defvar user-save-mode-map (let ((map (make-sparse-keymap)))
|
||||
(define-key map (kbd "C-x C-s") #'user-save-buffer)
|
||||
(define-key map (kbd "C-x s") #'user-save-some-buffers)
|
||||
map)
|
||||
"Keymap for `user-save-mode'.
|
||||
This map shadows the default map for `save-buffer'.")
|
||||
|
||||
(defun user-save-run-hooks (&rest _)
|
||||
"Run the hooks in `user-save-hook'.
|
||||
This does /not/ also save the buffer."
|
||||
(with-demoted-errors "User-save-hook error: %S"
|
||||
(run-hooks 'user-save-hook)))
|
||||
|
||||
(defun user-save-non-file-buffer-p (&optional buffer-or-name)
|
||||
"Return whether BUFFER-OR-NAME is a non-file buffer.
|
||||
BUFFER-OR-NAME, if omitted, defaults to the current buffer."
|
||||
(with-current-buffer (or buffer-or-name (current-buffer))
|
||||
(not (buffer-file-name))))
|
||||
|
||||
(defun user-save-buffer (&optional arg)
|
||||
"Save current buffer in visited file if modified.
|
||||
This function is precisely the same as `save-buffer', but with
|
||||
one modification: it also runs functions in `user-save-hook'.
|
||||
This means that if you have some functionality in Emacs to
|
||||
automatically save buffers periodically, but have hooks you want
|
||||
to automatically run when the buffer saves that are
|
||||
computationally expensive or just aren't something you want to
|
||||
run all the time, put them in `user-save-hook'.
|
||||
|
||||
ARG is passed directly to `save-buffer'."
|
||||
(interactive '(called-interactively))
|
||||
(message "User-Saving the buffer...")
|
||||
(user-save-run-hooks)
|
||||
(save-buffer arg)
|
||||
(message "User-Saving the buffer...Done."))
|
||||
|
||||
(defun user-save-some-buffers (&optional pred)
|
||||
"Save some buffers as though the user saved them.
|
||||
This function does not ask the user about each buffer, but PRED
|
||||
is used in almost the same way as `save-some-buffers': if it's
|
||||
nil or t, it will save all file-visiting modified buffers; if
|
||||
it's a zero-argument function, that will be called to determine
|
||||
whether the buffer needs to be saved."
|
||||
;; This could maybe be much better.
|
||||
(interactive "P")
|
||||
(unless pred (setq pred save-some-buffers-default-predicate))
|
||||
(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when (and (buffer-modified-p)
|
||||
(buffer-file-name)
|
||||
(or (null pred)
|
||||
(if (functionp pred) (funcall pred) pred)))
|
||||
(user-save-buffer)))))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode user-save-mode
|
||||
"Mode to enable an an extra user-save hook."
|
||||
:lighter " US"
|
||||
:keymap user-save-mode-map)
|
||||
|
||||
;;;###autoload
|
||||
(defun user-save-mode-disable ()
|
||||
"Turn off `user-save-mode' in the current buffer."
|
||||
(user-save-mode -1))
|
||||
|
||||
;;;###autoload
|
||||
(defun user-save-mode-in-some-buffers ()
|
||||
"Enable `user-save-mode', but only in some buffers.
|
||||
The mode will not be enabled in buffers derived from modes in
|
||||
`user-save-inhibit-modes', those for which
|
||||
`user-save-inhibit-predicates' return t, or in the minibuffer."
|
||||
(unless (or (minibufferp)
|
||||
(cl-some #'derived-mode-p user-save-inhibit-modes)
|
||||
(run-hook-with-args-until-failure 'user-save-inhibit-predicates))
|
||||
(user-save-mode +1)))
|
||||
|
||||
;;;###autoload
|
||||
(define-globalized-minor-mode user-save-global-mode user-save-mode user-save-mode-in-some-buffers
|
||||
(if user-save-global-mode
|
||||
(when user-save-hook-into-kill-emacs
|
||||
(add-hook 'kill-emacs-hook #'user-save-some-buffers))
|
||||
(remove-hook 'kill-emacs-hook #'user-save-some-buffers)))
|
||||
|
||||
(provide 'user-save)
|
||||
;;; user-save.el ends here
|
45
machines/bob.el
Normal file
45
machines/bob.el
Normal file
|
@ -0,0 +1,45 @@
|
|||
;;; bob.el --- Customizations for "bob" -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'acdw)
|
||||
(require 'machine)
|
||||
|
||||
(defun +bob-set-faces (&rest _)
|
||||
(let (;;(base-face "IBM Plex Mono")
|
||||
;; (var-face "IBM Plex Sans")
|
||||
;; (base-face "Iosevka Comfy Wide")
|
||||
;; (var-face "Iosevka Comfy Duo")
|
||||
(base-face "DejaVu Sans Mono")
|
||||
(var-face "DejaVu Sans")
|
||||
(base-size 100)
|
||||
(var-size 1.0)
|
||||
(italic-face nil)
|
||||
;; (bold-face nil)
|
||||
(mono-face nil))
|
||||
(+set-faces
|
||||
`((default
|
||||
:family ,base-face
|
||||
:height ,base-size
|
||||
:weight regular)
|
||||
(bold :family ,(or (bound-and-true-p bold-face) base-face)
|
||||
:weight extra-bold)
|
||||
(italic :family ,(or (bound-and-true-p italic-face) base-face)
|
||||
:weight normal
|
||||
:slant italic)
|
||||
(fixed-pitch :family ,(or (bound-and-true-p mono-face) base-face)
|
||||
:height 1.0)
|
||||
(variable-pitch
|
||||
:family ,(or var-face base-face)
|
||||
:height ,var-size)
|
||||
;; (org-italic
|
||||
;; :family ,(or var-face base-face)
|
||||
;; :slant italic)
|
||||
))))
|
||||
|
||||
;; Other ideas: [[https://twitter.com/NPRougier/status/1488570192561160195][from Nic Rougier]]
|
||||
(add-hook 'machine-after-load-theme-hook #'+bob-set-faces)
|
||||
|
||||
;; bob.el ends here (+bob-set-faces)
|
5
machines/gnu-linux.el
Normal file
5
machines/gnu-linux.el
Normal file
|
@ -0,0 +1,5 @@
|
|||
;;; linux.el -*- lexical-binding: t; -*-
|
||||
|
||||
(setq machine-default-height 105)
|
||||
|
||||
;;; linux.el ends here
|
13
machines/larry.el
Normal file
13
machines/larry.el
Normal file
|
@ -0,0 +1,13 @@
|
|||
;;; larry.el --- Customizations for "larry" -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'acdw)
|
||||
(require 'machine)
|
||||
|
||||
(add-function :after machine-after-load-theme
|
||||
(defun +larry-set-faces (&rest _)
|
||||
(+set-faces
|
||||
`((default :family "DejaVu Sans Mono")
|
||||
(fixed-pitch :family "DejaVu Sans Mono")
|
||||
(variable-pitch :family "DejaVu Sans")))))
|
23
machines/windows-nt.el
Normal file
23
machines/windows-nt.el
Normal file
|
@ -0,0 +1,23 @@
|
|||
;;; windows.el --- Windows settings! -*- lexical-binding: t; -*-
|
||||
|
||||
;; Annoying gnu-tls bug; I "always" trust the certificate anyway, so let's be
|
||||
;; insecure.
|
||||
(setq network-security-level 'low
|
||||
debug-on-error t)
|
||||
|
||||
;; Fonts
|
||||
|
||||
(setq machine-default-font "Cascadia Mono"
|
||||
machine-default-height 90
|
||||
machine-variable-pitch-font "Carlito"
|
||||
machine-variable-pitch-height 1.2)
|
||||
|
||||
;; Add C:\Program Files\* and C:\Program Files (x86)\* to exec-path
|
||||
(dolist (path (append (file-expand-wildcards "C:/Program Files/*")
|
||||
(file-expand-wildcards "c:/Program Files (x86)/*")
|
||||
;; Others...
|
||||
(save-match-data
|
||||
(split-string (getenv "PATH") ";" t))))
|
||||
(add-to-list 'exec-path path :append))
|
||||
|
||||
;;; windows.el ends here
|
5
readme.md
Normal file
5
readme.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# ~/.emacs
|
||||
|
||||
This is my Emacs config. There are many like it, but this one is mine.
|
||||
|
||||
(If you're reading this from tildegit, I've moved to [my own server now](https://git.acdw.net/emacs/).)
|
14
snippets/emacs-lisp-mode/+feature
Normal file
14
snippets/emacs-lisp-mode/+feature
Normal file
|
@ -0,0 +1,14 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: +feature
|
||||
# key: +f
|
||||
# --
|
||||
;;; `(file-name-nondirectory (buffer-file-name))` --- ${1:Title} -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
$0
|
||||
|
||||
(provide '`(file-name-nondirectory (file-name-sans-extension (buffer-file-name)))`)
|
||||
;;; `(file-name-nondirectory (buffer-file-name))` ends here
|
677
snippets/fundamental-mode/gpl3
Normal file
677
snippets/fundamental-mode/gpl3
Normal file
|
@ -0,0 +1,677 @@
|
|||
# key: gpl3
|
||||
# name: gpl3
|
||||
# --
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) ${1:`(format-time-string "%Y")`} ${2:`user-full-name`} <${3:`user-mail-address`}>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
A fancy and fast mode-line inspired by minimalism design.
|
||||
Copyright (C) 2018 Vincent Zhang <seagle0128@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
doom-modeline Copyright (C) 2018 Vincent Zhang <seagle0128@gmail.com>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
4
snippets/org-mode/sc
Normal file
4
snippets/org-mode/sc
Normal file
|
@ -0,0 +1,4 @@
|
|||
# key: sc
|
||||
# name: sc
|
||||
# --
|
||||
[sc name="${1: $(yas-choose-value '("total-recovery" "br-location-page" "_locationnameslisted" "organizations-helped" "other-results" "truck-accident-results" "car-wreck-results" "personal-injury-results" "number-locations" "experience" "employees" "mon-number" "mon-address" "lc-number" "lc-address" "ham-number" "ham-address" "zac-number" "zac-address" "liv-number" "liv-address" "asc-number" "asc-address" "shrev-number" "shrev-address" "alx-address" "alx-number" "laf-number" "laf-address" "toll-free" "br-number" "br-address" "gmia" "g-guarantee" "ds-number"))}"][/sc] $0
|
8
snippets/scheme-mode/chicken
Normal file
8
snippets/scheme-mode/chicken
Normal file
|
@ -0,0 +1,8 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: chicken
|
||||
# key: chicken
|
||||
# --
|
||||
\#!/bin/sh
|
||||
\#| -*- scheme -*-
|
||||
exec csi -s $0 \"$@\"
|
||||
|#
|
10
snippets/sh-mode/getopts
Normal file
10
snippets/sh-mode/getopts
Normal file
|
@ -0,0 +1,10 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: getopts
|
||||
# key: getopts
|
||||
# --
|
||||
while getopts ${1:h} opt; do
|
||||
case "$opt" in
|
||||
$0
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND -1 ))
|
Loading…
Reference in New Issue
Block a user