-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdiogenes-lisp-utils.el
204 lines (176 loc) · 6.68 KB
/
diogenes-lisp-utils.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
;;; diogenes-lisp-utils.el --- Lisp utilities for diogenes.el -*- lexical-binding: t -*-
;; Copyright (C) 2024 Michael Neidhart
;;
;; Author: Michael Neidhart <[email protected]>
;; Keywords: classics, tools, philology, humanities
;;; Commentary:
;; This file contains some lisp utilites needed by diogenes.el
;;; Code:
(require 'cl-lib)
(require 'seq)
(defmacro diogenes--replace-regexes-in-string (str &rest subst-lists)
"Apply a list of regex-substitutions to a string in sequence.
Each SUBST-LIST contains the REGEXP REP, followed optionaleval
parameters of `replace-regexp-in-string', FIXEDCASE LITERAL SUBEXP
START. Alternativly, SUBST-LIST can be a string or a list of one
element, in which case this is taken as the REGEXP and all of its
matches are deleted.
Returns the resulting string."
(declare (indent 1))
(let ((result str))
(dolist (subst subst-lists result)
(setf result
(cl-typecase subst
(list (let ((regex (car subst))
(rep (or (cadr subst) ""))
(rest (cddr subst)))
`(replace-regexp-in-string ,regex ,rep ,result
,@rest)))
(string `(replace-regexp-in-string ,subst "" ,result))
(t (error "%s must be either a list or a string!"
subst)))))))
(defun diogenes--plist-keys (plist)
"Traverse a plist and extract its keys"
(unless (plistp plist) (error "Not a plist!"))
(cl-loop for key in plist by #'cddr
collect key))
(defun diogenes--plist-values (plist)
"Traverse a plist and extract its values"
(unless (plistp plist) (error "Not a plist!"))
(cl-loop for key in (cdr plist) by #'cddr
collect key))
(defun diogenes--plist-keyword-keys-p (plist)
"Check if all keys of a plist are keywords"
(cond ((not (plistp plist)) nil)
((cdr plist) (and (keywordp (car plist))
(diogenes--plist-keyword-keys-p (cddr plist))))
(t t)))
(defun diogenes--assoc-cadr (key alist)
"Return non-nil if KEY is equal to the cadr of an element of ALIST.
The value is actually the first element of ALIST whose car equals KEY."
(cl-find-if (lambda (e) (equal key (cadr e)))
alist))
(defun diogenes--keyword->string (kw)
(unless (keywordp kw) (error "Not a keyword: %s" kw))
(substring (symbol-name kw) 1))
(defun diogenes--string->keyword (s)
(intern (concat ":" s)))
(defun diogenes--hash-to-alist (hash-table)
(cl-loop for k being the hash-keys of hash-table
using (hash-values v)
collect (cons k v)))
(defun diogenes--split-once (regexp str)
"Split a string once on regexp and return the substrings as a list."
(save-match-data
(if (string-match regexp str)
(list (substring str 0 (match-beginning 0))
(substring str (match-end 0)))
(list str))))
(defun diogenes--get-text-prop-boundaries (pos property)
"Get the boundaries of the region where property does not change."
(let* ((end (or (next-single-char-property-change pos property)
(point-max)))
(start (or (previous-single-char-property-change end property)
(point-min))))
(list start end)))
(defun diogenes--ascii-alpha-p (letter)
(or (<= 65 letter 90)
(<= 97 letter 122)))
(defsubst diogenes--ascii-alpha-only (str)
(cl-remove-if-not #'diogenes--ascii-alpha-p str))
(defun diogenes--string-equal-letters-only (str-a str-b)
"Compare two string, making them equal if they contain the same letters"
(string-equal (replace-regexp-in-string "[^[:alpha:]]" "" str-a)
(replace-regexp-in-string "[^[:alpha:]]" "" str-b)))
(defun diogenes--first-line-p ()
"Return non-nil if on the first line in buffer."
(save-excursion (beginning-of-line) (bobp)))
(defun diogenes--last-line-p ()
"Return non-nil if on the last line in buffer."
(save-excursion (end-of-line) (eobp)))
(cl-defun diogenes--filter-in-minibuffer (list prompt
&key
initial-selection
remove-prompt
all-string
remove-string
regexp-string
commit-string)
"Filter a list interactively in minibuffer, with initial-selection preselected.
When supplied, the keyword arguments add additional strings with a special meaning:
- :all-string adds all values and toggles the other input mode (add <-> remove)
- :regexp-string causes the next input to be read in as a regexp
- :remove-string switches input mode to `remove'"
(setq list (copy-list list))
(setq remove-prompt (or remove-prompt prompt))
(let ((max-mini-window-height 0.8))
(cl-loop
with list-length = (length list)
with current-list = (cl-set-difference list initial-selection)
with remove = nil
with results = (nreverse initial-selection)
for collection = (append (if remove results current-list)
(when regexp-string
(list regexp-string))
(when (and remove-string
results
(not remove))
(list remove-string))
(when (and all-string
(or remove
(< (length results)
list-length)))
(list all-string))
(when commit-string (list commit-string)))
for inp = (completing-read (concat
(if results (format "%s\n" results) "")
(if remove remove-prompt prompt))
collection)
if (or (string-blank-p inp)
(equal inp commit-string))
return (nreverse results)
for matcher = (cond ((string= inp regexp-string)
(setq inp "")
(let ((regexp (read-regexp "Regexp: ")))
(lambda (str) (string-match regexp str))))
(t (lambda (str) (string-equal inp str))))
do
(cond ((not (or (string-blank-p inp)
(member inp collection)))
(message "Invalid input!")
(sit-for 1))
((string= inp remove-string)
(setq remove t))
((and remove (string= inp all-string))
(setq remove nil
current-list (copy-list list)
results nil))
((string= inp all-string)
(setq current-list nil
results (copy-list list)))
(remove
(let ((matches (cl-remove-if-not matcher results)))
(setq remove nil
current-list (nconc matches current-list)
results (cl-delete-if matcher results))))
(t
(let ((matches (cl-remove-if-not matcher current-list)))
(setq results (nconc matches results)
current-list (cl-delete-if matcher current-list))))))))
(defun diogenes-undo ()
"Undo also when buffer is readonly."
(interactive)
(let ((inhibit-read-only t))
(undo)))
(defun diogenes--quit ()
(interactive) (kill-buffer))
(defun diogenes--ask-and-quit ()
(interactive)
(when (y-or-n-p "Discard edits and quit?")
(kill-buffer)))
;;; Transient scope accessors
(defsubst diogenes--tr--type () (plist-get (transient-scope) :type))
(defsubst diogenes--tr--callback () (plist-get (transient-scope) :callback))
(defsubst diogenes--tr--no-ask () (plist-get (transient-scope) :no-ask))
(provide 'diogenes-lisp-utils)
;;; diogenes-lisp-utils.el ends here