-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpel--base.el
3280 lines (2868 loc) · 120 KB
/
pel--base.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; pel--base.el --- PEL base utilities. -*-lexical-binding: t-*-
;; Copyright (C) 2020, 2021, 2022, 2023, 2024, 2025 Pierre Rouleau
;; Author: Pierre Rouleau <[email protected]>
;; This file is part of the PEL package
;; This file is not part of GNU Emacs.
;; 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/>.
;;; ---------------------------------------------------------------------------
;;; Commentary:
;;
;; A loosely coupled collection of simple utilities used by other PEL
;; which exist simply to simplify the PEL code.
;;
;; The following is a list of available commands (*) and functions (-) listed
;; in hierarchical calling order.
;;
;; PEL version:
;; * `pel-version'
;;
;; Environment Querying functions:
;; - `pel-in-fast-startup-p'
;; - `pel-string-with-major-mode'
;; - `pel-file-type-for'
;; - `pel-major-mode-of'
;; - `pel-buffers-in-mode'
;; - `pel-major-mode-of'
;; - `pel-current-buffer-filename'
;; - `pel-current-buffer-file-extension'
;; - `pel-current-buffer-eol-type'
;; - `pel-running-under-ssh-p'
;; - `pel-cd-to-current'
;;
;; Read/Set variable with a formatted name derived from major mode:
;; - `pel-major-mode-symbol-value-or'
;; - `pel-major-mode-symbol-value'
;; - `pel-major-mode-symbol-for'
;; - `pel-set-major-mode-symbol'
;;
;; Function alias
;; - `λc'
;;
;; Emacs Lisp Development support:
;; - `pel-add-dir-to-loadpath'
;;
;; Base predicates:
;; - `pel-expression-p'
;;
;; Conditional variable set:
;; - `pel-set-if-non-nil'
;;
;; Check for Zero:
;; - `pel-!0'
;;
;; Bitwise Operations:
;; - `pel-all-bitset-p'
;;
;; File System Type:
;; - `pel-unix-socket-p'
;; - `pel-file-type'
;;
;; String predicates:
;; - `pel-whitespace-in-str-p'
;; - `pel-ends-with-space-p'
;; - `pel-starts-with-space-p'
;; - `pel-string-ends-with-p'
;; - `pel-string-starts-with-p'
;; - `pel-lowercase-p'
;; - `pel-uppercase-p'
;;
;; Pluralizer:
;; - `pel-count-string'
;; - `pel-pluralize'
;; - `pel-plural-of'
;;
;; Symbol value extraction
;; - `pel-symbol-value'
;; - `pel-as-symbol'
;;
;; Symbol at point
;; - `pel-symbol-at-point'
;;
;; String generation utilities:
;; - `pel-option-mode-state'
;; - `pel-activated-in-str'
;; - `pel-symbol-value-or'
;; - `pel-symbol-text'
;; - `pel-symbol-on-off-string'
;; - `pel-on-off-string'
;; - `pel-yes-no-string'
;;
;; String transformation utilities:
;; - `pel-as-string'
;; - `pel-capitalize-first-letter'
;; - `pel-end-text-with-period'
;; - `pel-hastext'
;; - `pel-when-text-in'
;; - `pel-string-or-nil'
;; - `pel-string-for'
;; - `pel-string-when'
;; - `pel-string-spread'
;; - `pel-list-str'
;; - `pel-title-case-to-dash-separated'
;; - `pel-grp-regex'
;;
;; Message List formatting
;; - `pel-format-problem-messages'
;; - `pel--format-problem-messages'
;; - `pel-message-for'
;;
;; Value check:
;; - `pel-use-or'
;;
;; Operations on sequences:
;; - `pel-concat-strings-in-list'
;; - `pel-push-fmt'
;; - `pel-prepend'
;; - `pel-cons-alist-at'
;; - `pel-nth-elt'
;; - `pel-list-insert-before'
;; - `pel-list-prepend-nth'
;; - `pel-list-insert-car-at'
;;
;; Operation on auto-mode-alist
;; - `pel-delete-from-auto-mode-alist'
;;
;; File System Checks
;; - `pel-file-problems'
;; - `pel-dir-problems'
;; - `pel-symlink-problems'
;; - `pel--problem-format'
;;
;; Lazy loading and package installation:
;; - `pel-require-at-load'
;; - `pel--require-at-load'
;; - `pel-require-after-init'
;; - `pel--require-after-init'
;; - `pel-eval-after-load'
;; - `pel-set-auto-mode'
;; - `pel-autoload-file'
;; - `pel-declare-file'
;;
;; - `pel-install-github-file'
;; - `pel--install-github-file'
;; - `pel-install-github-files'
;; - `pel--install-github-files'
;; - `pel-install-files'
;; - `pel-install-file'
;; - `pel-url-copy-file'
;;
;; - `pel-require'
;; - `pel-package-installed-p'
;; - `pel-package-install'
;;
;; - `pel-ensure-package'
;; - `pel-ensure-pkg'
;; - `pel--pin-package'
;; - `pel-archive-exists-p'
;; - `pel--package-ensure-elpa'
;; - `pel--package-install'
;;
;; Tree-sitter major mode support
;; - `pel-major-mode-use-tree-sitter'
;; - `pel-major-ts-mode-supported-p'
;;
;; Mode argument interpretation
;; - `pel-action-for'
;;
;; Toggle a local mode:
;; - `pel-toggle-mode-and-show'
;; - `pel-toggle-mode'
;; - `pel-autoload-p'
;;
;; Basic functions working with values and variables:
;; - `pel-toggle-and-show-user-option'
;; - `pel-toggle-and-show'
;; - `pel-toggle'
;; - `pel-val-or-default'
;;
;; Symbol processing
;; - `pel-hook-symbol-for'
;; - `pel-map-symbol-for'
;;
;; Hook control
;; - `pel-add-hook-for'
;;
;; Minor mode activation
;; - `pel-check-minor-modes-in'
;; - `pel--check-minor-modes-in'
;; - `pel-turn-on-global-minor-modes-in'
;; - `pel-turn-on-local-minor-modes-in'
;; Argument converter:
;; - `pel-multiplier'
;;
;; Iteration helpers:
;; - `pel-dec'
;; - `pel-inc'
;;
;; Assignment operators:
;; - `pel+='
;; - `pel-='
;;
;; Swap 2 values:
;; - `pel-swap'
;;
;; Text at point:
;; - `pel-at-lowercase-p'
;; - `pel-at-uppercase-p'
;; - `pel-at-letter-p'
;; - `pel-chars-at-point'
;;
;; Calling functions:
;; - `pel-n-funcall-to'
;;
;; Moving Point:
;; - `pel-goto-position'
;; - `pel-goto-line'
;;
;; Line position:
;; - `pel-same-line-p'
;;
;; Identifying region:
;; - `pel-region-for'
;;
;; Insert or overwrite text
;; - `pel-insert-or-overwrite'
;;
;; Extract text from buffer
;; - `pel-text-from-beginning-of-line'
;;
;; Check text from buffer
;; - `pel-line-has-only-whitespace-p'
;;
;; File Path processing
;; - `pel-is-subdir-of'
;; - `pel-normalize-fname'
;; - `pel-parent-dirpath'
;; - `pel-normalize-fname'
;; - `pel-sibling-dirpath'
;; - `pel-expand-url-file-name'
;; - `pel-path-strip'
;; - `pel-url-join'
;; - `pel-url-location'
;; - `pel-same-fname-p'
;; - `pel-normalize-fname'
;; - `pel-point-symlink-to'
;; - `pel-symlink-points-to-p'
;;
;; Insertion of text in current buffer
;; - `pel-insert-url-link'
;; - `pel-insert-symbol-content-line'
;; - `pel-insert-symbol-content'
;; - `pel-insert-symbol'
;; - `pel-insert-list-value'
;; - `pel-insert-list-content'
;; - `pel--pp'
;; - `pel-line-prefixed-with'
;;
;; Print in dedicated buffer
;; - `pel-print-in-buffer'
;;
;; Code parsing support
;; - `pel-point-in-comment-or-docstring'
;;
;; Tab width control
;; - `pel-set-tab-width'
;;
;; Speedbar Support
;; - `pel-add-speedbar-extension'
;;
;; Byte Compilation
;; - `pel-byte-compile-if-needed'
;; - `pel-modtime-of'
;;
;; Imenu Utilities
;; - `pel-add-imenu-sections-to'
;;
;; Tags support
;; - `pel-visit-tags'
;;
;;; --------------------------------------------------------------------------
;;; Dependencies:
;; subr (always loaded) ; use: called-interactively-p
(eval-when-compile
(require 'subr-x) ; use: split-string, string-join, string-trim
(require 'cl-macs)) ; use: cl-eval-when
;;; --------------------------------------------------------------------------
;;; Code:
;; ---------------------------------------------------------------------------
;; Constants
;; ---------
;;
;; Note: The following symbols have nothing to do with PEL and could
;; have a name that starts with 'system' but I'm not doing it
;; to provide name space isolation in case Emacs declares these
;; in the future.
(defconst pel-system-is-macos-p
(eq system-type 'darwin)
"Predicate: t if running under a macOS Operating System, nil otherwise.")
(defconst pel-terminal-is-macos-terminal-p
(string-equal (getenv "TERM_PROGRAM") "Apple_Terminal")
"Predicate: t if Emacs is running under the macOS Terminal.app, else nil.")
(defconst pel-system-is-linux-p
(eq system-type 'gnu/linux)
"Predicate: t if running under a Linux Operating System, nil otherwise.")
(defconst pel-system-is-FreeBSD-p
(eq system-type 'gnu/kfreebsd)
"Predicate: t if running under a FreeBSD Operating System, nil otherwise.")
(defconst pel-system-is-windows-p
(memq system-type '(windows-nt ms-dos))
"Predicate: t if running under a Windows Operating System, nil otherwise.")
(defconst pel-emacs-has-dynamic-module-support-p
(and (functionp 'module-load)
module-file-suffix)
"Predicate: non-nil when Emacs has dynamic module support enabled.
The non-nil value of the predicate is the `module-file-suffix'.")
(defconst pel-emacs-is-graphic-p (display-graphic-p)
"Predicate: t when Emacs is running in graphics mode, nil otherwise.")
(defconst pel-emacs-is-a-tty-p (not pel-emacs-is-graphic-p)
"Predicate: t when Emacs is running in TTY mode, nil otherwise.")
(defconst pel-can-display-special-chars-p (and (eq system-type 'darwin)
pel-emacs-is-a-tty-p)
"Predicate: t if Emacs can properly show Unicode characters like 👍 or 👎.")
;; TODO: add ability to install unicode fonts and take it into account.
(defconst pel-emacs-27-or-later-p (>= emacs-major-version 27)
"Predicate: t when Emacs version 27 or later is running, nil otherwise.")
(defconst pel-emacs-28-or-later-p (>= emacs-major-version 28)
"Predicate: t when Emacs version 28 or later is running, nil otherwise.")
(defconst pel-emacs-29-or-later-p (>= emacs-major-version 29)
"Predicate: t when Emacs version 29 or later is running, nil otherwise.")
(defconst pel-emacs-30-or-later-p (>= emacs-major-version 30)
"Predicate: t when Emacs version 30 or later is running, nil otherwise.")
(defconst pel-filesep (if pel-system-is-windows-p "\\" "/")
"String directory/file separator character for this OS.")
;; Variables
;; ---------
(defvar pel-uses-tree-sitter nil
"Set to t when PEL currently uses tree-sitter, nil otherwise.
It is set to t only by the logic of pel_keys.el which is
executed by `pel-init' on startup.")
;; ---------------------------------------------------------------------------
;; Code Style Buffer Local Variables
;; ---------------------------------
(defvar-local pel-comment-prefix nil
"String identifying the comment start. Set by specific modes only.")
;; ---------------------------------------------------------------------------
;; PEL version
;; ===========
(defun pel-version (&optional insert)
"Display and return PEL package version string.
Optionally insert it at point if INSERT is non-nil."
(interactive "P")
(let ((version "0.4.1"))
(if insert
(insert version))
(message "PEL version: %s" version)
version))
;; ---------------------------------------------------------------------------
;; Support for future Emacs versions
;; ---------------------------------
(when (version< emacs-version "28")
;; the following function is available in Emacs 28, as part of macroexp
;; TODO: check if this file must be required in Emacs 28
(defun macroexp-file-name ()
"Return the name of the file from which the code comes.
Returns nil when we do not know.
A non-nil result is expected to be reliable when called from a macro in order
to find the file in which the macro's call was found, and it should be
reliable as well when used at the top-level of a file.
Other uses risk returning non-nil value that point to the wrong file."
;; `eval-buffer' binds `current-load-list' but not `load-file-name',
;; so prefer using it over using `load-file-name'.
(let ((file (car (last current-load-list))))
(or (if (stringp file) file)
(bound-and-true-p byte-compile-current-file)))))
;; (declare-function macroexp-file-name (if (version< emacs-version "28")
;; "pel--base"
;; "macroexp"))
;; ---------------------------------------------------------------------------
;; Environment Querying functions:
;; ------------------------------
;;
;; The following functions provide information about the Emacs environment.
(eval-and-compile
(defun pel-in-fast-startup-p ()
"Return non-nil when PEL runs in fast startup operation."
(bound-and-true-p pel-running-in-fast-startup-p)))
;; ---------------------------------------------------------------------------
;; Functions checking Major Mode
;; -----------------------------
(defun pel-derived-mode-p (buffer-or-name &rest modes)
"Non-nil if major mode of BUFFER-OR-NAME is derived from one of MODES.
If BUFFER-OR-NAME is nil, use current buffer."
(if buffer-or-name
(with-current-buffer buffer-or-name
(apply (function derived-mode-p) modes)))
(apply (function derived-mode-p) modes))
(defun pel-dired-buffer-p (&optional buffer-or-name strict)
"Return mode if mode of BUFFER-OR-NAME is a dired buffer, nil otherwise.
Accepts mode derived from dired-mode unless STRICT is non-nil."
(if buffer-or-name
(with-current-buffer buffer-or-name
(or (eq major-mode 'dired-mode)
(unless strict
(derived-mode-p 'dired-mode))))
(or (eq major-mode 'dired-mode)
(unless strict
(derived-mode-p 'dired-mode)))))
(defun pel-major-mode-of (&optional buffer-or-name)
"Return the major mode symbol of the specified BUFFER-OR-NAME.
If not specified (or nil) return the major mode of the current buffer."
(if buffer-or-name
(with-current-buffer buffer-or-name
major-mode)
major-mode))
(defun pel-file-type-for (major-mode-symbol &optional suffix)
"Return the file type name string for the specified MAJOR-MODE-SYMBOL.
By default that's the symbol name stripped off the '-mode' suffix unless
SUFFIX is specified (like \"-minor-mode\")."
(substring (symbol-name major-mode-symbol)
0
(- (length (or suffix "-mode")))))
(defun pel-string-with-major-mode (symbol-format-string
&optional buffer-or-name)
"Return a string formatted with the single %s replaced by the major mode.
The %s in the FORMAT-STRING is replaced by the prefix string
before the \"-mode\" of the major mode of the current buffer or the one
specified by BUFFER-OR-NAME."
(format symbol-format-string
(pel-file-type-for (pel-major-mode-of buffer-or-name))))
(defun pel-buffers-in-mode (wanted-major-mode)
"Return a list of buffers with specified WANTED-MAJOR-MODE, nil if none open.
WANTED-MODE is a symbol."
(let ((buffers-in-wanted-mode '()))
(dolist (buffer (buffer-list) (reverse buffers-in-wanted-mode))
(with-current-buffer buffer
(when (eq major-mode wanted-major-mode)
(push buffer buffers-in-wanted-mode))))))
(defun pel-current-buffer-filename
(&optional sans-directory sans-extension no-error)
"Return current buffer's filename string.
Return a filename with full path unless SANS-DIRECTORY is non-nil.
If SANS-EXTENSION is non-nil exclude the extension, otherwise include it.
Issue a user error if current buffer does not visit a file, unless the
optional NO-ERROR argument is non-nil, in which case it returns nil."
(if buffer-file-truename
(let ((fn (expand-file-name buffer-file-truename)))
(when sans-extension
(setq fn (file-name-sans-extension fn)))
(if sans-directory
(file-name-nondirectory fn)
fn))
(if no-error
nil
(user-error "Buffer %s is not visiting a file!" (buffer-name)))))
(defun pel-current-buffer-file-extension (&optional with-period)
"Return the extension of the current buffer's file.
By default, the returned value excludes the period that starts
the extension, but if the optional argument WITH-PERIOD is
non-nil, the period is included in the value and in that case, if
FILENAME has no extension the returned value is \"\".
See the function `file-name-extension' for details on how this
treats files with no extension or file names that ends with a
period. Issue a user error if current buffer does not visit a
file."
(if buffer-file-truename
(file-name-extension buffer-file-truename with-period)
(user-error "No file in buffer %s" (buffer-name))))
(defconst pel-eol-mode-name '((0 . unix)
(1 . dos)
(2 . mac)
(t . nil))
"Association list of buffer-file-coding-system value to its symbolic name.")
(defun pel-current-buffer-eol-type ()
"Return line ending of current buffer content: \\='unix, \\='dos, \\='mac or nil.
The nil value means that the type is unknown."
(let ((eol-type (coding-system-eol-type buffer-file-coding-system)))
(when (vectorp eol-type)
(setq eol-type (coding-system-eol-type (aref eol-type 0))))
(cdr (assoc eol-type pel-eol-mode-name))))
(defun pel-running-under-ssh-p ()
"Return t if Emacs is invoked through SSH, nil otherwise."
(when (getenv "SSH_CLIENT")
t))
(defun pel-cd-to-current (&optional silent)
"Change current directory to the directory holding visited file.
Print message showing the new current working directory if it changed unless
SILENT is non-nil (can be requested by prefix argument)."
(interactive "P")
(let* ((original-cwd (cd "."))
(new-cwd (cd (file-name-directory (pel-current-buffer-filename)))))
(unless (string= new-cwd original-cwd)
(unless silent
(message "Current directory back to: %s" new-cwd)))))
;; ---------------------------------------------------------------------------
;; Read/Set variable with a formatted name derived from major mode
;; ---------------------------------------------------------------
(defvar pel-insert-symbol-content-context-buffer nil
"Contextual value for the buffer argument of `pel-insert-symbol-content'.
Let-bind this variable in functions that need to call
`pel-insert-symbol-content' repetitively always passing the same value for its
buffer argument.")
(defun pel-major-mode-symbol-for (symbol-format-string
&optional buffer-or-name)
"Return the major-mode specific symbol for specified buffer.
The symbol name is identified by the FORMAT-STRING which must
contain one \"%s\" that is replaced by the by the prefix string
before the \"-mode\" of the major mode of the the current buffer
or of the buffer specified by the BUFFER argument or the variable
`pel-insert-symbol-content-context-buffer'. The BUFFER argument
value takes precedence to the value of the variable
`pel-insert-symbol-content-context-buffer'. If both are nil, then
the value is read from the context of the current buffer, which
may be a local or global."
(intern
(pel-string-with-major-mode symbol-format-string
(or buffer-or-name pel-insert-symbol-content-context-buffer))))
(defun pel-major-mode-symbol-value (symbol-format-string
&optional buffer-or-name)
"Return the value of major-mode specific symbol for specified buffer.
The symbol name is identified by the FORMAT-STRING which must
contain one \"%s\" that is replaced by the by the prefix string
before the \"-mode\" of the major mode of the the current buffer
or of the buffer specified by the BUFFER argument or the variable
`pel-insert-symbol-content-context-buffer'. The BUFFER argument
value takes precedence to the value of the variable
`pel-insert-symbol-content-context-buffer'. If both are nil, then
the value is read from the context of the current buffer, which
may be a local or global."
(symbol-value
(pel-major-mode-symbol-for
symbol-format-string
(or buffer-or-name pel-insert-symbol-content-context-buffer))))
(defun pel-major-mode-symbol-value-or (symbol-format-string
default-value
&optional buffer-or-name)
"Return the value or default of major-mode specific symbol for specified buffer.
The symbol name is identified by the FORMAT-STRING which must
contain one \"%s\" that is replaced by the by the prefix string
before the \"-mode\" of the major mode of the the current buffer
or of the buffer specified by the BUFFER argument or the variable
`pel-insert-symbol-content-context-buffer'. The BUFFER argument
value takes precedence to the value of the variable
`pel-insert-symbol-content-context-buffer'. If both are nil, then
the value is read from the context of the current buffer, which
may be a local or global.
If the symbol name does not exists for the specified SYMBOL-FORMAT-STRING
for the current major mode, then return the specified DEFAULT-VALUE."
(condition-case nil
(pel-major-mode-symbol-value
symbol-format-string
(or buffer-or-name pel-insert-symbol-content-context-buffer))
(error default-value)))
(defun pel-set-major-mode-symbol (symbol-format-string
value
&optional buffer-or-name)
"Set symbol identified by SYMBOL-FORMAT-STRING to specified VALUE.
The symbol name is identified by the FORMAT-STRING which must
contain one \"%s\" that is replaced by the by the prefix string
before the \"-mode\" of the major mode of the the current buffer
or the one specified by BUFFER-OR-NAME."
(let ((symbol (intern (pel-string-with-major-mode symbol-format-string
buffer-or-name))))
(set symbol value)))
;; ---------------------------------------------------------------------------
;; Function alias
;; --------------
;; - `λc'
;;
(defmacro λc (fct &rest args)
"Funcall lambda function FCT with ARGS.
This is an alias for `funcall'.
Note: this, so far, is the *only* PEL symbol whose name does not start with
the \\='pel\\=' prefix. If this clashes with something you use, please
accept my apologies and please let me know. Hopefully the use of
a Unicode symbol in the name will reduce this possibility."
`(funcall ,fct ,@args))
;; ---------------------------------------------------------------------------
;; Emacs Lisp Development Support
;; ------------------------------
(defun pel-add-dir-to-loadpath (dir)
"Add directory DIR to Emacs variable `load-path' if not already in the list.
Interactively display the number of directories in the list and whether
the operation succeeded or not.
Return non-nil if it was added, nil otherwise."
(interactive "DDir: ")
(let* ((original-length (length load-path))
(new-dir (directory-file-name (expand-file-name dir)))
(new-length (length (add-to-list 'load-path new-dir))))
(when (called-interactively-p 'interactive)
(message "load-path: %d directories. %s was %s"
new-length
new-dir
(if (= new-length original-length)
" already in the list, nothing new added!"
"added.")))
(= new-length original-length)))
;; ---------------------------------------------------------------------------
;; Base predicates
;; ---------------
;;
;; I looked for the following predicate function and did not find it.
;; If there is something like this already, let me know.
(defun pel-expression-p (val)
"Return non-nil if VAL is an expression, nil if it is a value.
Return nil for t and nil.
Return t for \\='some-symbols or \\='(some expressions), nothing else.
Meant to be used to identify code that is quoted (for delayed
code execution)."
(and (not (eq val t))
(not (eq val nil))
(or (symbolp val)
(consp val))))
(defun pel-user-option-p (symbol)
"Return t when SYMBOL is a valid PEL User-option, nil otherwise."
(and (custom-variable-p symbol)
(eq t (compare-strings "pel-use-" nil nil
(symbol-name symbol) 0 8))))
;; ---------------------------------------------------------------------------
;; Conditional variable set
;; ------------------------
(defun pel-set-if-non-nil (symbol value)
"Set SYMBOL to VALUE only if VALUE is non-nil.
If VALUE is nil do nothing."
(when value
(set symbol value)))
;; ---------------------------------------------------------------------------
;; Check for Zero
;; --------------
;; In Lisp, nil is the only \\='false\\=' value. Even 0 is an equivalent to \\='true\\='.
;; The following inline help checking for a zero-value result.
;; If I find something similar native in Emacs I\\='ll use and remove this one.
(defsubst pel-!0 (v)
"Return nil if V is 0, t otherwise."
(not (zerop v)))
;; ---------------------------------------------------------------------------
;; Bitwise Operations
;; ------------------
(defun pel-all-bitset-p (value &rest bits)
"Return t when all and only those BITS are set in VALUE, nil otherwise."
(let ((bitmask 0))
(dolist (bit bits bitmask)
(setq bitmask (logior bitmask bit)))
(equal 0 (logxor value bitmask))))
;; ---------------------------------------------------------------------------
;; File System Type
;; ----------------
(defun pel-unix-socket-p (fname)
"Return t if FNAME is a Unix Socket, nil otherwise.
FNAME must exists otherwise an error is raised."
(eq (string-to-char (file-attribute-modes (file-attributes fname))) ?s))
(defun pel-file-type-str (path)
"Return a string describing the type of file system element at PATH.
PATH must identify an existing file system object otherwise an
error is raised."
(cond
((file-symlink-p path) "symbolic link")
((file-directory-p path) "directory")
((file-regular-p path) "file")
((not (file-exists-p path)) (error "%s does not exists" path))
((pel-unix-socket-p path) "UNIX socket")
(t "unknown file system object")))
;; ---------------------------------------------------------------------------
;; String predicates
;; -----------------
(defun pel-whitespace-in-str-p (text)
"Return non-nil if any whitespace character is inside TEXT, nil otherwise.
The index of the first whitespace character is returned when one is present."
(string-match "[ \t\n\r]" text))
(defun pel-ends-with-space-p (text)
"Return t if TEXT ends with a space character, nil otherwise."
(let ((len (length text)))
(when (> len 0)
(string= (substring text (- len 1) len) " "))))
(defun pel-starts-with-space-p (text)
"Return t if TEXT has space character(s) at beginning, nil otherwise."
(when (> (length text) 0)
(string= (substring text 0 1) " ")))
(defun pel-string-ends-with-p (text suffix &optional ignore-case)
"Return t if TEXT string does end with SUFFIX string, nil otherwise.
Ignore case differences if IGNORE-CASE is non-nil."
(let ((text-len (length text))
(suffix-len (length suffix)))
(and (>= text-len suffix-len)
(eq t (compare-strings suffix nil nil
text (- text-len suffix-len) nil
ignore-case)))))
(defun pel-string-starts-with-p (text prefix &optional ignore-case)
"Return t if TEXT string does start with PREFIX string, nil otherwise.
Ignore case differences if IGNORE-CASE is non-nil."
(eq t (compare-strings prefix nil nil
text nil (length prefix)
ignore-case)))
(defun pel-lowercase-p (string)
"Return t if all characters in STRING are lowercase, nil otherwise."
(let ((case-fold-search nil))
(not (string-match-p "[[:upper:]]" string))))
(defun pel-uppercase-p (string)
"Return t if all characters in STRING are uppercase, nil otherwise."
(let ((case-fold-search nil))
(not (string-match-p "[[:lower:]]" string))))
(defun pel-alnum-p (string)
"Return t if all characters in STRING are letters or digits, nil otherwise."
(let ((case-fold-search nil))
(and (not (string-match-p "[[:punct:]]" string))
(not (string-match-p "[[:space:]]" string))
(not (string-match-p "[[:cntrl:]]" string)))))
;; ---------------------------------------------------------------------------
;; - Pluralizer
;; ------------
(defun pel-plural-of (word)
"Return the plural of the specified word.
Does not handle all of English, it handles the following types:
- class -> classes
- tomato -> tomatoes
- sky -> skies
- calf -> calves
- command -> commands"
(let ((last-letter (substring-no-properties word -1)))
(cond
;; class -> classes. tomato -> tomatoes
((member last-letter '("s" "o"))
(concat word "es"))
;; sky -> skies
((string= last-letter "y")
(concat (substring-no-properties word 0 -1) "ies"))
;; calf -> calves
((string= last-letter "f")
(concat (substring-no-properties word 0 -1) "ves"))
;; command -> commands
(t (concat word "s")))))
(defun pel-count-string (n singular &optional plural no-count-for-1)
"Return a formatted string for N in SINGULAR form or PLURAL form.
If N is 0 or 1, use the singular form.
If N > 2: use the PLURAL form if specified,
otherwise use `pel-plural-of' to compute the plural
form of SINGULAR.
By default, display the count of 1 unless NO-COUNT_FOR-1 is set."
(if (> n 1)
(format "%d %s" n (or plural
(pel-plural-of singular)))
(if no-count-for-1
singular
(format "%d %s" n singular))))
(defun pel-pluralize (n singular &optional plural)
"Return the plural of SINGULAR when N is larger than 1.
Use `pel-plural-of' for the plural form unless PLURAL is specified
in which case return PLURAL."
(if (> n 1)
(or plural (pel-plural-of singular))
singular))
;; ---------------------------------------------------------------------------
;; Symbol value extraction
;; -----------------------
(defun pel--symbol-value (symbol &optional quiet)
"Return SYMBOL value if it is bound.
If it is not bound, then return a list with the symbol and a
string describing that it is not bound, unless QUIET is non-nil. If QUIET is
non-nil, just return nil when SYMBOL is not bound."
(if (boundp symbol)
(symbol-value symbol)
(unless quiet
(list symbol "**is currently unbound!**"))))
(defun pel-symbol-value (symbol &optional buffer)
"Return SYMBOL value in current or specified BUFFER."
(if buffer
(with-current-buffer buffer
(pel--symbol-value symbol))
(pel--symbol-value symbol)))
(defun pel-as-symbol (s)
"Return the symbol for S, which can either be a string or a symbol."
(if (symbolp s)
s
(intern s)))
;; ---------------------------------------------------------------------------
;; Symbol at point
(defun pel-symbol-at-point ()
"Return symbol at point. Return nil if there are none."
(if (and (require 'thingatpt nil :noerror)
(fboundp 'thing-at-point))
(thing-at-point 'symbol :no-properties)
(error "Function thing-at-point not loaded!")))
;; ---------------------------------------------------------------------------
;; String generation utilities
;; ---------------------------
;;
;; Call hierarchy:
;; - `pel-option-mode-state'
;; - `pel-activated-in-str'
;; - `pel-symbol-value-or'
;; - `pel-symbol-text'
;; - `pel-symbol-on-off-string'
;; - `pel-on-off-string'
;; - `pel-yes-no-string'
(defun pel-on-off-string (boolean &optional on-string off-string)
"Return \"off\" for nil, \"on\" for non-nil BOOLEAN argument.
If ON-STRING and OFF-STRING arguments are specified use them as the
on/off value, otherwise use \"on\" and \"off\"."
(if boolean
(or on-string "on")
(or off-string "off")))
(defun pel-symbol-on-off-string (symbol &optional on-string off-string
void-string)
"Return representation of SYMBOL value and whether it is bound.
When SYMBOL is not bound: return VOID-STRING or \"void\" if it's nil,
When it is bound, return:
- the OFF-STRING or \"off\" for nil,
- the ON-STRING or \"on\" for SYMBOL boolean value."
(if (boundp symbol)
(pel-on-off-string (eval symbol) on-string off-string)
(or void-string "void")))
(defun pel-symbol-text (symbol &optional on-string off-string void-string)
"Return a string with an interpretation of SYMBOL value.
If symbol is not bound: show \"void\".
If symbol is set to t: show ON-STRING if defined, \"t\" otherwise.
If symbol is nil: show OFF-STRING if defined, \"nil\" otherwise."
(format "%s is now: %s"
symbol
(pel-symbol-on-off-string symbol on-string off-string void-string)))
(defun pel-symbol-value-or (symbol &optional replacement formatter)
"Return SYMBOL value if non void, otherwise its REPLACEMENT.
If SYMBOL is void and there is no REPLACEMENT return a string
created by (format \"unknown - %S is not loaded\" symbol).
If SYMBOL is void and replacement is :nil-for-void, return nil.
If SYMBOL is bound and FORMATTER is non nil it's a function that
takes the symbol and returns a string."
(if (boundp symbol)
(if formatter
(funcall formatter symbol)
(symbol-value symbol))
(if replacement
(if (eq replacement :nil-for-void)
nil
replacement)
(format "unknown - %S is not loaded" symbol))))
(defun pel-yes-no-string (test &optional true-string false-string)
"Return TRUE-STRING when boolean TEST is non-nil, otherwise FALSE_STRING.
By default or when these arguments are nil:
- TRUE_STRING is \"yes\" and
- FALSE_STRING is \"no\"."
(if test
(or true-string "yes")
(or false-string "no")))
;; ---------------------------------------------------------------------------
;; Automated Mode Activation Check functions
;; -----------------------------------------
(defun pel-activated-in-str (activated-in)
"Return a string describing ACTIVATED-IN list.
Return an empty string if ACTIVATED-IN is nil.
Otherwise return a string start starts with \" Auto-loaded in: \"
followed by the elements of ACTIVATED-IN separated by commas."
(if activated-in
(format " Auto-loaded in: %s"
(pel-list-str activated-in))
""))
(defun pel-modes-activating-symbol-name-for (minor-mode)
"Return user-option symbol that sets which major mode activates MINOR-MODE.
This is typically a symbol like:
- \\='pel-modes-activating-subword-mode : controls subword-mode
- \\='pel-modes-activating-dumb-jump : controls dumb-jump-mode
- \\='pel-modes-activating-ggtags : controls ggtags-mode
Ideally all minor-mode controlling PEL user-options would have a name that
ends with \\='-mode\\=' but it\\='s unfortunately not the case.
Use this function to return the appropriate symbol if one exists, otherwise
raise an error because the caller is trying to retrieve information that does
not exists."
(let ((full-symbol (intern (format "pel-modes-activating-%s" minor-mode))))
(if (boundp full-symbol)
full-symbol
(let ((partial-symbol (intern
(format "pel-modes-activating-%s"
(pel-file-type-for minor-mode)))))
(if (boundp partial-symbol)
partial-symbol