diff options
author | Case Duckworth | 2021-09-01 18:14:55 -0500 |
---|---|---|
committer | Case Duckworth | 2021-09-01 18:14:55 -0500 |
commit | 963d94a0ec90311429807fdf1700271e54441f2c (patch) | |
tree | d15583eb5a13c23d6e3e21c63c5fc69ddd0b6430 /lisp | |
parent | Automatically call persistent-scratch-mode on *scratch* (diff) | |
parent | Add acdw-org/work-month-headings (diff) | |
download | emacs-963d94a0ec90311429807fdf1700271e54441f2c.tar.gz emacs-963d94a0ec90311429807fdf1700271e54441f2c.zip |
Merge branch 'main' of https://tildegit.org/acdw/emacs
Diffstat (limited to 'lisp')
-rw-r--r-- | lisp/acdw-compat.el | 8 | ||||
-rw-r--r-- | lisp/acdw-org.el | 46 | ||||
-rw-r--r-- | lisp/acdw.el | 13 | ||||
-rw-r--r-- | lisp/titlecase.el | 157 |
4 files changed, 211 insertions, 13 deletions
diff --git a/lisp/acdw-compat.el b/lisp/acdw-compat.el index 04e42ae..0a1a037 100644 --- a/lisp/acdw-compat.el +++ b/lisp/acdw-compat.el | |||
@@ -22,7 +22,7 @@ | |||
22 | ;;; Code: | 22 | ;;; Code: |
23 | 23 | ||
24 | ;; Convenience macro | 24 | ;; Convenience macro |
25 | (defmacro safe-define (&rest defines) | 25 | (defmacro safely (&rest defines) |
26 | "Wrap DEFINES in tests to make sure they're not already defined. | 26 | "Wrap DEFINES in tests to make sure they're not already defined. |
27 | Is it necessary? Who knows!!" | 27 | Is it necessary? Who knows!!" |
28 | (let (output) | 28 | (let (output) |
@@ -53,7 +53,7 @@ Is it necessary? Who knows!!" | |||
53 | 53 | ||
54 | ;;; Functions for changing capitalization that Do What I Mean | 54 | ;;; Functions for changing capitalization that Do What I Mean |
55 | ;; Defined in EMACS/lisp/simple.el | 55 | ;; Defined in EMACS/lisp/simple.el |
56 | (safe-define | 56 | (safely |
57 | (defun upcase-dwim (arg) | 57 | (defun upcase-dwim (arg) |
58 | "Upcase words in the region, if active; if not, upcase word at point. | 58 | "Upcase words in the region, if active; if not, upcase word at point. |
59 | If the region is active, this function calls `upcase-region'. | 59 | If the region is active, this function calls `upcase-region'. |
@@ -88,7 +88,7 @@ to capitalize ARG words." | |||
88 | ;;; Repeat.el | 88 | ;;; Repeat.el |
89 | ;; Defined in EMACS/lisp/repeat.el | 89 | ;; Defined in EMACS/lisp/repeat.el |
90 | 90 | ||
91 | (safe-define | 91 | (safely |
92 | (defcustom repeat-too-dangerous '(kill-this-buffer) | 92 | (defcustom repeat-too-dangerous '(kill-this-buffer) |
93 | "Commands too dangerous to repeat with \\[repeat]." | 93 | "Commands too dangerous to repeat with \\[repeat]." |
94 | :group 'convenience | 94 | :group 'convenience |
@@ -538,7 +538,7 @@ Used in `repeat-mode'.") | |||
538 | 538 | ||
539 | 539 | ||
540 | ;;; goto-address-mode | 540 | ;;; goto-address-mode |
541 | (safe-define | 541 | (safely |
542 | (defvar global-address-mode nil) | 542 | (defvar global-address-mode nil) |
543 | 543 | ||
544 | (define-globalized-minor-mode global-goto-address-mode | 544 | (define-globalized-minor-mode global-goto-address-mode |
diff --git a/lisp/acdw-org.el b/lisp/acdw-org.el index 50a0488..89269ab 100644 --- a/lisp/acdw-org.el +++ b/lisp/acdw-org.el | |||
@@ -370,19 +370,49 @@ instead of the true count." | |||
370 | ;;; Next and previous heading, with widening | 370 | ;;; Next and previous heading, with widening |
371 | (defun acdw/org-next-heading-widen (arg) | 371 | (defun acdw/org-next-heading-widen (arg) |
372 | (interactive "p") | 372 | (interactive "p") |
373 | (let ((point-target (if (> arg 0) | 373 | (let ((current-point (point)) |
374 | (point-max) | 374 | (point-target (if (> arg 0) (point-max) (point-min)))) |
375 | (point-min)))) | 375 | (org-next-visible-heading arg) |
376 | (unless (or (org-next-visible-heading arg) ; XXX: this doesn't work!!! ARGH | 376 | (when (and (buffer-narrowed-p) |
377 | (/= (point) point-target)) | 377 | (= (point) point-target) |
378 | (when (buffer-narrowed-p) | 378 | (or (and (> arg 0)) |
379 | (widen) | 379 | (and (< arg 0) |
380 | (org-next-visible-heading arg))))) | 380 | (= (point) current-point)))) |
381 | (widen) | ||
382 | (org-next-visible-heading arg)))) | ||
381 | 383 | ||
382 | (defun acdw/org-previous-heading-widen (arg) | 384 | (defun acdw/org-previous-heading-widen (arg) |
383 | (interactive "p") | 385 | (interactive "p") |
384 | (acdw/org-next-heading-widen (- arg))) | 386 | (acdw/org-next-heading-widen (- arg))) |
385 | 387 | ||
386 | 388 | ||
389 | ;;; Add headings for every day of the work month | ||
390 | ;; Gets rid of weekends. | ||
391 | |||
392 | (defun acdw-org/work-month-headings (&optional month year) | ||
393 | (interactive (list | ||
394 | (read-number "Month: " (car (calendar-current-date))) | ||
395 | (read-number "Year: " (nth 2 (calendar-current-date))))) | ||
396 | (let ((offset 0) | ||
397 | (month (or month | ||
398 | (car (calendar-current-date)))) | ||
399 | (year (or year | ||
400 | (car (last (calendar-current-date)))))) | ||
401 | (dotimes (day (calendar-last-day-of-month month year)) | ||
402 | (let* ((day (1+ day)) | ||
403 | (day-of-week (calendar-day-of-week (list month day year)))) | ||
404 | (unless (memq day-of-week '(0 6)) ; weekend | ||
405 | (end-of-line) | ||
406 | (org-insert-heading nil t t) | ||
407 | (insert (concat "[" (mapconcat (lambda (n) | ||
408 | (format "%02d" n)) | ||
409 | (list year month day) | ||
410 | "-") | ||
411 | " " | ||
412 | (nth day-of-week '("Sun" "Mon" "Tue" "Wed" "Thu" | ||
413 | "Fri" "Sat")) | ||
414 | "]"))))))) | ||
415 | |||
416 | |||
387 | (provide 'acdw-org) | 417 | (provide 'acdw-org) |
388 | ;; acdw-org.el ends here | 418 | ;; acdw-org.el ends here |
diff --git a/lisp/acdw.el b/lisp/acdw.el index f23ca0e..796c2f1 100644 --- a/lisp/acdw.el +++ b/lisp/acdw.el | |||
@@ -579,7 +579,18 @@ It's called 'require-private' for historical reasons." | |||
579 | (switch-to-buffer nil) | 579 | (switch-to-buffer nil) |
580 | (other-window (or arg 1)))) | 580 | (other-window (or arg 1)))) |
581 | 581 | ||
582 | 582 | ||
583 | ;;; Auth-sources | ||
584 | ;; https://github.com/emacs-circe/circe/wiki/Configuration | ||
585 | (defun acdw/fetch-password (&rest params) | ||
586 | (require 'auth-source) | ||
587 | (let ((match (car (apply #'auth-source-search params)))) | ||
588 | (if match | ||
589 | (let ((secret (plist-get match :secret))) | ||
590 | (if (functionp secret) | ||
591 | (funcall secret) | ||
592 | secret)) | ||
593 | (warn "Password not found for %S" params)))) | ||
583 | 594 | ||
584 | (provide 'acdw) | 595 | (provide 'acdw) |
585 | ;;; acdw.el ends here | 596 | ;;; acdw.el ends here |
diff --git a/lisp/titlecase.el b/lisp/titlecase.el new file mode 100644 index 0000000..64da5b4 --- /dev/null +++ b/lisp/titlecase.el | |||
@@ -0,0 +1,157 @@ | |||
1 | ;;; titlecase.el -*- lexical-binding: t; -*- | ||
2 | |||
3 | ;; https://hungyi.net/posts/programmers-way-to-title-case/ | ||
4 | |||
5 | (require 'cl-lib) | ||
6 | (require 'subr-x) | ||
7 | |||
8 | ;;;###autoload | ||
9 | (defun titlecase-string (str) | ||
10 | "Convert string STR to title case and return the resulting string." | ||
11 | (let* ((case-fold-search nil) | ||
12 | (str-length (length str)) | ||
13 | ;; A list of markers that indicate start of a new phrase within the | ||
14 | ;; title, e.g. "The Lonely Reindeer: A Christmas Story" | ||
15 | ;; must be followed by one of word-boundary-chars | ||
16 | (new-phrase-chars '(?: ?. ?? ?\; ?\n ?\r)) | ||
17 | ;; immediately triggers new phrase behavior without waiting for word | ||
18 | ;; boundary | ||
19 | (immediate-new-phrase-chars '(?\n ?\r)) | ||
20 | ;; A list of characters that indicate "word boundaries"; used to split | ||
21 | ;; the title into processable segments | ||
22 | (word-boundary-chars (append '(? ?– ?— ?- ?‑ ?/) | ||
23 | immediate-new-phrase-chars)) | ||
24 | ;; A list of small words that should not be capitalized (in the right | ||
25 | ;; conditions) | ||
26 | (small-words '("a" "an" "and" "as" "at" "but" "by" "en" "for" "if" | ||
27 | "in" "of" "on" "or" "the" "to" "v" "v." "vs" "vs." | ||
28 | "via")) | ||
29 | ;; Fix if str is ALL CAPS | ||
30 | (str (if (string-match-p "[a-z]" str) str (downcase str))) | ||
31 | ;; Reduce over a state machine to do title casing | ||
32 | (final-state | ||
33 | (cl-reduce | ||
34 | (lambda (state char) | ||
35 | (let* ((result (aref state 0)) | ||
36 | (last-segment (aref state 1)) | ||
37 | (first-word-p (aref state 2)) | ||
38 | (was-in-path-p (aref state 3)) | ||
39 | (last-char (car last-segment)) | ||
40 | (in-path-p (or (and (eq char ?/) | ||
41 | (or (not last-segment) | ||
42 | (member last-char '(?. ?~)))) | ||
43 | (and was-in-path-p | ||
44 | (not | ||
45 | (or (eq char ? ) | ||
46 | (member | ||
47 | char | ||
48 | immediate-new-phrase-chars)))))) | ||
49 | (end-p | ||
50 | ;; are we at the end of the input string? | ||
51 | (eq (+ (length result) (length last-segment) 1) | ||
52 | str-length)) | ||
53 | (pop-p | ||
54 | ;; do we need to pop a segment onto the output result? | ||
55 | (or end-p (and (not in-path-p) | ||
56 | (member char word-boundary-chars)))) | ||
57 | (segment | ||
58 | ;; add the current char to the current segment | ||
59 | (cons char last-segment)) | ||
60 | (segment-string | ||
61 | ;; the readable version of the segment | ||
62 | (apply #'string (reverse segment))) | ||
63 | (small-word-p | ||
64 | ;; was the last segment a small word? | ||
65 | (member (downcase (substring segment-string 0 -1)) | ||
66 | small-words)) | ||
67 | (capitalize-p | ||
68 | ;; do we need to capitalized this segment or lowercase it? | ||
69 | (or end-p first-word-p (not small-word-p))) | ||
70 | (ignore-segment-p | ||
71 | ;; ignore explicitly capitalized segments | ||
72 | (or (string-match-p "[a-zA-Z].*[A-Z]" segment-string) | ||
73 | ;; ignore URLs | ||
74 | (string-match-p "^https?:" segment-string) | ||
75 | ;; ignore hostnames and namespaces.like.this | ||
76 | (string-match-p "\\w\\.\\w" segment-string) | ||
77 | ;; ignore windows filesystem paths | ||
78 | (string-match-p "^[A-Za-z]:\\\\" segment-string) | ||
79 | ;; ignore unix filesystem paths | ||
80 | was-in-path-p | ||
81 | ;; ignore email addresses and user handles with @ symbol | ||
82 | (member ?@ segment))) | ||
83 | (next-result | ||
84 | (if pop-p | ||
85 | (concat result | ||
86 | (if ignore-segment-p | ||
87 | ;; pop segment onto the result without | ||
88 | ;; processing | ||
89 | segment-string | ||
90 | ;; titlecase the segment before popping onto | ||
91 | ;; result | ||
92 | (titlecase--segment | ||
93 | segment-string capitalize-p))) | ||
94 | result)) | ||
95 | (next-segment | ||
96 | (unless pop-p segment)) | ||
97 | (will-be-first-word-p | ||
98 | (if pop-p | ||
99 | (or (not last-segment) | ||
100 | (member last-char new-phrase-chars) | ||
101 | (member char immediate-new-phrase-chars)) | ||
102 | first-word-p))) | ||
103 | (vector | ||
104 | next-result next-segment will-be-first-word-p in-path-p))) | ||
105 | str | ||
106 | :initial-value | ||
107 | (vector nil ; result stack | ||
108 | nil ; current working segment | ||
109 | t ; is it the first word of a phrase? | ||
110 | nil)))) ; are we inside of a filesystem path? | ||
111 | (aref final-state 0))) | ||
112 | |||
113 | (defun titlecase--segment (segment capitalize-p) | ||
114 | "Convert a title's inner SEGMENT to capitalized or lower case | ||
115 | depending on CAPITALIZE-P, then return the result." | ||
116 | (let* ((case-fold-search nil) | ||
117 | (ignore-chars '(?' ?\" ?\( ?\[ ?‘ ?“ ?’ ?” ?_)) | ||
118 | (final-state | ||
119 | (cl-reduce | ||
120 | (lambda (state char) | ||
121 | (let ((result (aref state 0)) | ||
122 | (downcase-p (aref state 1))) | ||
123 | (cond | ||
124 | (downcase-p | ||
125 | ;; already upcased start of segment, so lowercase the rest | ||
126 | (vector (cons (downcase char) result) t)) | ||
127 | ((member char ignore-chars) | ||
128 | ;; check if start char of segment needs to be ignored | ||
129 | (vector (cons char result) downcase-p)) | ||
130 | (t | ||
131 | ;; haven't upcased yet, and we can, so do it | ||
132 | (vector (cons (upcase char) result) t))))) | ||
133 | segment | ||
134 | :initial-value (vector nil (not capitalize-p))))) | ||
135 | (thread-last (aref final-state 0) | ||
136 | (reverse) | ||
137 | (apply #'string)))) | ||
138 | |||
139 | ;;;###autoload | ||
140 | (defun titlecase-region (begin end) | ||
141 | "Convert text in region from BEGIN to END to title case." | ||
142 | (interactive "*r") | ||
143 | (let ((pt (point))) | ||
144 | (insert (titlecase-string (delete-and-extract-region begin end))) | ||
145 | (goto-char pt))) | ||
146 | |||
147 | ;;;###autoload | ||
148 | (defun titlecase-dwim () | ||
149 | "Convert the region or current line to title case. | ||
150 | If Transient Mark Mode is on and there is an active region, convert | ||
151 | the region to title case. Otherwise, work on the current line." | ||
152 | (interactive) | ||
153 | (if (and transient-mark-mode mark-active) | ||
154 | (titlecase-region (region-beginning) (region-end)) | ||
155 | (titlecase-region (point-at-bol) (point-at-eol)))) | ||
156 | |||
157 | (provide 'titlecase) | ||