-
-
Notifications
You must be signed in to change notification settings - Fork 101
/
find-where.el
807 lines (747 loc) · 36.7 KB
/
find-where.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
;;; find-where.el --- Find where something is true. -*- lexical-binding:t -*-
;;
;; Filename: find-where.el
;; Description: Find where something is true.
;; Author: Drew Adams
;; Maintainer: Drew Adams
;; Copyright (C) 2018, Drew Adams, all rights reserved.
;; Created: Sat Mar 17 10:13:09 2018 (-0700)
;; Version: 2018-10-08
;; Package-Requires: (thingatpt+ "0")
;; Last-Updated: Tue Oct 8 13:25:32 2019 (-0700)
;; By: dradams
;; Update #: 856
;; URL: https://www.emacswiki.org/emacs/download/find-where.el
;; Doc URL: https://www.emacswiki.org/emacs/FindWhere
;; Keywords: motion thing search
;; Compatibility: GNU Emacs: 20.x, 21.x, 22.x, 23.x, 24.x, 25.x, 26.x
;;
;; Features that might be required by this library:
;;
;; `thingatpt', `thingatpt+'.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;;
;; Find where something is true.
;;
;; Get or go to the next place where some predicate is satisfied.
;;
;; But first, you don't really need this library! ;-)
;;
;; In Emacs and Emacs Lisp there are multiple ways to find things.
;; And in many cases it will be simpler or more efficient to use
;; another way than to take advantage of this library.
;;
;; What this library offers is some convenience sometimes, and a
;; certain kind of generality: Specify what you want to find by a
;; predicate. The predicate is tested at successive places, forward
;; or backward, until it is satisfied.
;;
;; By default, the forward and backward movement is among buffer
;; positions, in the usual sense. And by default, each movement
;; before testing is just one character. This is the minimum
;; movement needed to get past the current position (which is often
;; the last place found).
;;
;; Moving only one character and testing is obviously not very
;; efficient, but it is all that can be done in the general case.
;;
;; When you know a way to move farther before testing and to be sure
;; there is no need to test closer, you can take advantage of that by
;; providing for the appropriate movement in the predicate you
;; provide or in optional forward and backward movement functions.
;;
;; Clearly, this move-one-char-and-test approach is not the way to go
;; for ordinary string searching. Emacs uses an efficient,
;; Boyer-Moore string-search algorithm (see
;; https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm),
;; which essentially moves forward in chunks that are as long as what
;; your search string matches, rather than moving just a character at
;; a time before each match attempt.
;;
;; So if you want to search for a sequence of characters, just use
;; `(re-)search-forward' or similar. And if you need an additional
;; test at a match position (e.g., check a text or overlay property)
;; you can easily add that. So forget about this library for
;; ordinary buffer search.
;;
;; Still, you might find this library convenient for some things,
;; even in cases where there is an easy alternative. The abstraction
;; of defining a destination by a predicate that holds there can be
;; helpful.
;;
;; So just what does it mean to "find where something is true"? Find
;; what? Well, the what is defined by your predicate. What is
;; found, that is, returned, is both the position (where) and
;; whatever the predicate returns as a non-nil value. For example,
;; you can find a text THING, such as the next vector with 13
;; elements, and have it returned along with its bounds (start and
;; end positions).
;;
;; The library defines some general functions to find and return the
;; next or previous where-plus-what, which you provide with a
;; defining predicate. And it defines corresponding commands to go
;; to the next or previous such location.
;;
;; The commands have names that start (after prefix `fw-') with
;; `to-'. The corresponding non-interactive functions generally have
;; the same names without the `to-'. For example, function
;; `fw-next-where' returns the next position and data that satisfy a
;; predicate, and command `fw-to-next-where' goes there.
;;
;; By default, movement is to the start position of something, but
;; really the where, in relation to its what, is up to you. The
;; default behavior is thus different from the standard Emacs
;; `forward-THING' and `backward-THING' behavior, which moves just
;; past the THING rather than just to it.
;;
;; This default behavior applies to all functions provided here, not
;; just to those (`fw-next-thing' etc.) that find buffer THINGs. For
;; THINGs, `find-where.el' requires library `thingatpt+.el', which
;; uses and enhances standard library `thingatpt.el'.
;;
;; When repeated, the commands reuse the same predicate as the last
;; time (it is the value of variable `fw-last-pred'), but a plain
;; prefix argument (`C-u') makes them prompt you for the predicate to
;; use. The predicate you enter must accept a buffer position (the
;; position to test) as its first argument.
;;
;; A typical use might check something about the character at (i.e.,
;; after) that position.
;;
;; The THING-related function `fw-next-thing' just uses
;; `fw-next-where' with a predicate that tests whether the position
;; is at the start of a THING, where being at the start also means
;; that the previous buffer position is not on the same thing (not
;; just the same type of thing).
;;
;; For instance, if the THING type passed to `fw-next-thing' is
;; `list' then it returns the start position of the next list (as
;; well as the list text as a string and its end position).
;;
;; When repeated, the THING commands reuse the same THING type as the
;; last time (it is the value of variable `fw-last-thing'), but a
;; plain prefix argument (`C-u') makes them prompt you for the THING
;; type to use.
;;
;; You can bind any of the commands defined here to keys, of course.
;; But you can also easily define other commands that make use of
;; them, and bind those commands to keys.
;;
;; For example, here's a simple command that moves to the start of
;; the next use of face `font-lock-doc-face':
;;
;; (defun to-next-doc-face (n)
;; "Move to next occurrence of `font-lock-doc-face'.
;; With numeric prefix arg N, move to the Nth next occurrence."
;; (interactive "p")
;; (fw-to-next-where #'doc-face-start-p nil nil n))
;;
;; where the predicate is defined like so:
;;
;; (defun doc-face-start-p (position)
;; "Return non-nil if char at POSITION starts `font-lock-doc-face'.
;; That is, it has that face, and any char just before it does not."
;; (and (eq (get-text-property position 'face) 'font-lock-doc-face)
;; (or (= position (point-min))
;; (not (eq (get-text-property (1- position) 'face)
;; 'font-lock-doc-face)))))
;;
;; Note the use here of two complementary tests within the predicate.
;; The character at the tested position must pass the test (having
;; property `font-lock-doc-face'), and the preceding char, if there
;; is one, must NOT pass the test. This means that
;; `to-next-doc-face' finds the _first_ character that passes the
;; test. This is typical of a predicate used with `find-where.el'
;; functions.
;;
;; For this reason, you can use helper function `fw-test-start-p' to
;; take care of that true-here-but-not-just-before-here logic. It
;; takes the position to test and a predicate as arguments. The
;; predicate must be true at the position and false just before the
;; position, for `fw-test-start-p' to be true (return non-nil).
;;
;; Using `fw-test-start-p', `doc-face-start-p' becomes just this:
;;
;; (defun doc-face-start-p (position)
;; "Return non-nil if char at POSITION starts `font-lock-doc-face'.
;; That is, it has that face, and any char just before it does not."
;; (fw-test-start-p position
;; (lambda ()
;; (eq (get-text-property (point) 'face)
;; 'font-lock-doc-face))))
;;
;; The predicate arg to `fw-to-next-where' can accept additional
;; arguments, besides the position. So you can use a predicate that
;; accepts, as argument, the face to look for, as well as the
;; position to test. For example:
;;
;; (defun face-start-p (position face)
;; "Return non-nil if the character at POSITION starts FACE.
;; That is, it has FACE, and any character just before it does not."
;; (and (eq (get-text-property position 'face) face)
;; (or (= position (point-min))
;; (not (eq (get-text-property (1- position) 'face)
;; face)))))
;;
;; Or simplified using `fw-test-start-p':
;;
;; (defun face-start-p (position face)
;; "Return non-nil if the character at POSITION starts FACE.
;; That is, it has FACE, and any character just before it does not."
;; (fw-test-start-p position
;; `(lambda ()
;; (eq (get-text-property (point) 'face)
;; ',face))))
;;
;; (defvar last-face nil "Last face used by `to-next-face'.")
;;
;; (defun to-next-face (arg)
;; "Move to next text-property occurrence of face `last-face'.
;; With plain `C-u', prompt for the face to assign to `last-face'.
;; With numeric prefix arg N, move to the Nth next occurrence."
;; (interactive "P")
;; (if (or (consp arg) (not last-face))
;; (setq last-face (read-face-name "Face: ")
;; arg 1)
;; (setq arg (prefix-numeric-value arg)))
;; (fw-to-next-where #'face-start-p nil (list last-face) arg))
;;
;;
;; [Note: Text property `face' can actually have a list of faces as
;; its value, so instead of using an `eq' text in those `*-start-p'
;; functions a more realistic example would test for the particular
;; face using both `eq' and `memq' (return true if either is true).]
;;
;; Now recall that the way `fw-to-next-where' works by default is to
;; move forward only one char and then test with the predicate. This
;; is not very efficient, but it is all that can be done, unless
;; there is some way to move farther forward before testing and be
;; sure there is no need to test closer.
;;
;; In the case of testing for a given text property (or overlay
;; property) at a buffer position there is such a better way: use
;; function `next-single-property-change'. Using that, we can define
;; the forward-movement function `to-next-face-prop', which we can
;; pass to `to-next-face' to override the default one-char movement
;; (`forward-char').
;;
;; (defun to-next-face-prop ()
;; "Go to next change in `face' text property."
;; (let ((pos (next-single-property-change (point) 'face)))
;; (when pos (goto-char pos))))
;;
;; (defun to-next-face (arg)
;; (interactive "P")
;; (if (or (consp arg) (not last-face))
;; (setq last-face (read-face-name "Face: ")
;; arg 1)
;; (setq arg (prefix-numeric-value arg)))
;; (fw-to-next-where #'face-start-p nil (list last-face) arg
;; nil nil nil #'to-next-face-prop))
;;
;;
;; As an example of defining a next-THING command, this defines a
;; command to move forward among sexps:
;;
;; (defun to-next-sexp (n)
;; "Go to next start of a sexp."
;; (interactive "p")
;; (fw-to-next-thing 'sexp nil n))
;;
;; And this moves among strings:
;;
;; (defun to-next-string (n)
;; "Go to next start of a string."
;; (interactive "p")
;; (fw-to-next-thing 'string nil n))
;;
;; Note that the various `fw-to-next-*' and `fw-to-previous-*'
;; commands move to the _beginning_ of the next or previous place
;; where something is true. For example, if you use `fw-next-thing'
;; with THING `word' then the cursor moves to the beginning of each
;; word. This is different from typical Emacs `forward-*' and
;; `backward-*' commands, which move _past_ the end or the beginning
;; of something.
;;
;; Typical Emacs `forward-*' commands essentially perform the
;; following sequence of actions, expressed in terms of `fw-next-*':
;;
;; 1. While some predicate PRED-X is NOT true, do `fw-to-next-X'.
;; 2. Do `fw-to-next-NOT-X'.
;;
;; Step 1 moves to the next place X is true (e.g., a word beginning).
;; Step 2 moves just past where X continues to be true (e.g., just
;; after the end of the word).
;;
;; Partly as a way of illustrating this, commands `fw-downward-word'
;; and `fw-upward-word' act like Emacs `forward-word' and
;; `backward-word', but they move through text vertically, not
;; horizontally. They are defined using steps 1 and 2.
;;
;;
;; Commands defined here:
;;
;; `fw-downward-word', `fw-to-column-down', `fw-to-column-up',
;; `fw-to-next-thing', `fw-to-next-where',
;; `fw-to-next-where-vertical', `fw-to-previous-thing',
;; `fw-to-previous-where', `fw-to-previous-where-vertical',
;; `fw-upward-word'.
;;
;; Non-interactive functions defined here:
;;
;; `fw--next/prev-thing', `fw--next/prev-where',
;; `fw--read-predicate', `fw--to-next/prev-thing',
;; `fw--to-next/prev-where', `fw-next-thing', `fw-next-where',
;; `fw-next-where-vertical', `fw-not-word-char-after-p',
;; `fw-not-word-char-before-p', `fw-previous-thing',
;; `fw-previous-where', `fw-previous-where-vertical',
;; `fw-test-start-p', `fw-thing-start-p', `fw-word-char-after-p',
;; `fw-word-char-before-p'.
;;
;; Internal variables defined here:
;;
;; `fw-last-pred', `fw-last-thing'.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Change Log:
;;
;; 2018/07/09 dadams
;; Renamed find-where.el from gowhere.el.
;; 2018/07/04 dadams
;; Added: fw--read-predicate, fw-test-start-p.
;; Renamed: fw-to-where-last to fw-last-pred and fw-to-thing-last to fw-last-thing.
;; Commands: Added progress messages.
;; fw-thing-start-p: Use fw-test-start-p.
;; fw-to-(next|previous-)(where|thing): Corrected use of prefix arg.
;; fw-to-(next|previous-)thing, fw--to-next/prev-thing: Added optional arg INTERACTIVEP.
;; Corrected for args N and READP. If fw-last-thing then do not read.
;; fw--to-next/prev-(where|thing): Corrected use of NOERROR. Use fw--read-predicate. Made
;; args except NEXT/PREV optional. Added arg INTERACTIVEP.
;; fw--to-next/prev-where: Removed spurious occurrence of var PREDICATE.
;; fw-thing-start-p: Provide lexical-binding version for Emacs 24+.
;; fw-(to-)(next|previous-)where:
;; Handle negative N. Added args FORWARD-FN, BACKWARD-FN. Define previous using next.
;; fw-(to-)(next|previous-)where-vertical: Use lexical-binding (removed backquote construct).
;; Handle PREDICATE and READP correctly. Use 1, not N, for movement functions. Define
;; previous using next.
;; fw-(next|previous)-where-vertical:
;; Handle null PREDICATE. Handle negative N. Made PREDICATE optional too. Define previous
;; using next.
;; fw-to-column-up: Define using down.
;; fw-(up|down)ward-word: Removed _POS arg. Added INTERACTIVEP arg. Handle negative N.
;; Define up using down.
;; 2018/07/01 dadams
;; fw-(up|down)ward-word: Corrected use of bobp|eobp.
;; 2018/06/30 dadams
;; Return more than just the position:
;; fw--next/prev-where, fw--to-next/prev-where:
;; Return cons of position found and result of applying predicate.
;; fw-thing-start-p:
;; Return cons of thing and end position. Do not try to go back a char if at bob.
;; fw--to-next/prev-thing: Adapt to change in fw--next/prev-thing.
;; 2018/06/29 dadams
;; fw--next/prev-where: Use eobp for next, bobp for previous.
;; fw-upward-word: Use bobp, not eobp.
;; 2018/03/27 dadams
;; Renamed fw-line-move-down to fw-to-column-down, fw-line-move-up to fw-to-column-up.
;; fw-to-column-(down|up): Move down only if point already at COLUMN or N > 1.
;; 2018/03/25 dadams
;; fw-line-move-(down|up): Default COLUMN to current. Prefix arg is for N, not COLUMN.
;; 2018/03/24 dadams
;; Renamed library from next/prev.el to gowhere.el (prefix np- to fw-).
;; Added: fw-line-move-down, fw-line-move-up, fw-word-char-after-p, fw-word-char-before-p,
;; fw-not-word-char-after-p, fw-not-word-char-before-p.
;; Renamed: fw-to-next/prev-thing to fw--to-next/prev-thing,
;; fw-to-(next|previous)-word-vertical to fw-(down|up)ward-word.
;; Removed: np--line-move-visual-down-1.
;; fw--next/prev-where: Call FORWARD-FN and BACKWARD-FN with no args now.
;; fw-line-move-(down|up):
;; Added args COLUMN and FORCE. All args optional now. Move to COLUMN first.
;; fw-(to-)(next|previous)-where-vertical:
;; Added args NOERROR and FORCE. Effective predicate checks that column remains the same.
;; Use constructed nullary movement function.
;; fw-(down|up)ward-word: Transactional for double movement, and restore point if error.
;; 2018/03/18 dadams
;; Added: np--line-move-visual-down-1, np-next-where-vertical, np-previous-where-vertical,
;; np-to-next-where-vertical, np-to-previous-where-vertical.
;; Renamed: np-to-next/prev-where to np--to-next/prev-where.
;; np--(to-)next/prev-where: Added args FORWARD-FN and BACKWARD-FN. Made most args optional.
;; 2018/03/17 dadams
;; Created as next/prev.el.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Code:
(require 'thingatpt+)
;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defgroup find-where nil
"Next and previous functions to find where something is true."
:prefix "fw-"
:group 'editing :group 'convenience
:link `(url-link :tag "Send Bug Report"
,(concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\
find-where.el bug: \
&body=Describe bug here, starting with `emacs -Q'. \
Don't forget to mention your Emacs and library versions."))
:link '(url-link :tag "Download" "https://www.emacswiki.org/emacs/download/find-where.el")
:link '(url-link :tag "Description" "https://www.emacswiki.org/emacs/FindWhere")
:link '(emacs-commentary-link :tag "Commentary" "find-where"))
(defvar fw-last-pred nil
"Last predicate used by `to-next-where' or `to-previous-where'.")
(defun fw-next-where (predicate &optional start args n forward-fn backward-fn)
"Find the Nth buffer position after START where PREDICATE is true.
If N is negative, find the Nth position before START.
Return nil if there is no such position.
Otherwise, return the found position in a cons (POSITION . VALUE),
where VALUE is what PREDICATE returns.
PREDICATE must accept a buffer position as its first arg.
START defaults to point.
Non-nil ARGS are passed to PREDICATE as additional args.
N defaults to 1.
Optional args FORWARD-FN and BACKWARD-FN are functions for moving
forward and backward, respectively, to a position to test. Only one
of them is used, depending on the sign of N, and it is called with no
arguments. By default, they each move one character."
(fw--next/prev-where
(if (< n 0) 'previous 'next) predicate start args (if (< n 0) (- n) n) forward-fn backward-fn))
(defun fw-previous-where (predicate &optional start args n forward-fn backward-fn)
"Find the Nth buffer position before START where PREDICATE is true.
Like as `fw-next-where', but this searches backward."
(fw-next-where predicate start args (- n) forward-fn backward-fn))
(defun fw--next/prev-where (next/prev predicate &optional start args n forward-fn backward-fn)
"Helper for `fw-next-where*' and `fw-previous-where*'.
Optional args FORWARD-FN and BACKWARD-FN are functions for moving
forward and backward, respectively. Only one of them is invoked,
depending on NEXT/PREV, and it is called with no arguments.
By default, they move forward or backward one character."
(setq n (or n 1)
start (or start (point))
forward-fn (or forward-fn (lambda () (forward-char 1)))
backward-fn (or backward-fn (lambda () (backward-char 1))))
(let ((pos nil)
(count 0)
res)
(save-excursion
(goto-char start)
(while (and (< count n) (not (if (eq 'next next/prev) (eobp) (bobp))))
(funcall (if (eq 'next next/prev) forward-fn backward-fn))
(when (setq res (apply predicate (point) args))
(setq pos (point)
count (1+ count)))))
(and res (cons pos res))))
;;;###autoload
(defun fw-to-next-where (&optional predicate start args n noerror readp interactivep
forward-fn backward-fn)
"Go to first buffer position after point where PREDICATE is true.
PREDICATE must accept a buffer position as its first arg.
You are prompted for PREDICATE if you use a plain prefix arg or it is
the first time you use the command. Otherwise, PREDICATE is the value
of `fw-last-pred', which is the last predicate used by the command.
Return nil if there is no such position.
Otherwise, return the found position in a cons (POSITION . VALUE),
where VALUE is what PREDICATE returns.
Non-interactively:
Go to Nth buffer position after START where PREDICATE is true.
Non-nil NOERROR means do not raise an error when there is no such
next position. See `fw-next-where' for the other arguments.
Non-nil READP means read a new predicate.
Non-nil INTERACTIVEP means treat the call as if interactive: read a
predicate if none has been defined, and provide status messages.
Optional args FORWARD-FN and BACKWARD-FN are functions for moving
forward and backward, respectively, to a position to test. Only one
of them is used, depending on the sign of N, and it is called with no
arguments. By default, they each move one character."
(interactive
(let ((parg current-prefix-arg))
(list nil nil nil (if (atom parg) (prefix-numeric-value parg) 1) nil (consp parg) t)))
(setq n (or n 1))
(fw--to-next/prev-where
(if (< n 0) 'previous 'next) predicate start args (if (< n 0) (- n) n)
noerror readp interactivep forward-fn backward-fn))
;;;###autoload
(defun fw-to-previous-where (&optional predicate start args n noerror readp interactivep
forward-fn backward-fn)
"Go to first buffer position before point where PREDICATE is true.
Like `fw-to-next-where', but this moves backward."
(interactive
(let ((parg current-prefix-arg))
(list nil nil nil (if (atom parg) (prefix-numeric-value parg) 1) nil (consp parg) t)))
(setq n (or n 1))
(fw-to-next-where predicate start args (- n) noerror readp interactivep forward-fn backward-fn))
(defun fw--to-next/prev-where (&optional next/prev predicate start args n noerror
readp interactivep forward-fn backward-fn)
"Helper for `fw-to-next-where' and `fw-to-previous-where'.
FORWARD-FN and BACKWARD-FN are functions for moving forward and
backward, respectively, by one unit (defaults: `forward-char',
`backward-char'). (Only one of them is used, depending on NEXT/PREV.)"
(when readp (setq fw-last-pred nil))
(when predicate (setq fw-last-pred predicate))
(when interactivep
(unless fw-last-pred (setq fw-last-pred (fw--read-predicate)))
(message "Finding %s%s..." next/prev (if (= n 1) "" (format " (%d)" n))))
(let ((res (fw--next/prev-where next/prev fw-last-pred start args n forward-fn backward-fn)))
(if (not res)
(unless noerror (error "Not found"))
(goto-char (car res)))
(when interactivep
(message "Finding %s%s...done" next/prev (if (= n 1) "" (format " (%d)" n))))
res))
(defun fw--read-predicate ()
"Read a predicate that accepts at least one argument, a buffer position.
You are prompted for a function name or lambda expression."
;; The function needs to accept a buffer position as its first arg, to be usable by find-where.
(let (pred)
(while (not (functionp pred))
(setq pred (read (let (this-command) (read-string "Predicate: ")))))
(when (fboundp 'func-arity) ; Emacs 26+
(let ((arity (if (subrp pred) (subr-arity pred) (func-arity pred))))
(while (or (not (>= (car arity) 1))
(not (= (car arity) 1))) ; Cannot know how to read ARGS.
(setq pred (read (let (this-command) (read-string "Predicate (at least 1 arg): ")))
arity (if (subrp pred) (subr-arity pred) (func-arity pred))))))
pred))
(defun fw-word-char-after-p (pos)
"Return non-nil if next char is a word constituent."
(equal '(2) (syntax-after pos)))
(defun fw-not-word-char-after-p (pos)
"Return non-nil if next char is not a word constituent."
(not (equal '(2) (syntax-after pos))))
(defun fw-word-char-before-p (pos)
"Return non-nil if previous char is a word constituent."
(equal '(2) (syntax-after (1- pos))))
(defun fw-not-word-char-before-p (pos)
"Return non-nil if previous char is not a word constituent."
(not (equal '(2) (syntax-after (1- pos)))))
;; (defun fw-go-past-next-word (pos &optional n)
;; "Same behavior as `forward-word'."
;; (interactive "i\np")
;; (dotimes (ii n)
;; (if (and (not (eobp)) (fw-word-char-after-p (point)))
;; (fw-to-next-where #'fw-not-word-char-after-p)
;; (fw-to-next-where #'fw-word-char-after-p)
;; (fw-to-next-where #'fw-not-word-char-after-p))))
;; (defun fw-go-before-previous-word (pos &optional n)
;; "Same behavior as `backward-word'."
;; (interactive "i\np")
;; (dotimes (ii n)
;; (if (and (not (bobp)) (fw-word-char-before-p (point)))
;; (fw-to-previous-where #'fw-not-word-char-before-p)
;; (fw-to-previous-where #'fw-word-char-before-p)
;; (fw-to-previous-where #'fw-not-word-char-before-p))))
(defun fw-test-start-p (position predicate &rest args)
"Return non-nil if PREDICATE is true at POSITION and not just before it.
Otherwise return nil. The non-nil return value is whatever PREDICATE
returns at POSITION.
PREDICATE is applied to (only) the arguments in list ARGS (empty by
default)."
(let ((here (save-excursion (goto-char position) (apply predicate args))))
(and here
(or (bobp)
(not (equal here (save-excursion (goto-char (1- position)) (apply predicate args)))))
here)))
;;; Vertical movement ------------------------------------------------
(when (fboundp 'line-move-visual) ; Emacs 25+
(defun fw-to-column-down (&optional column n noerror force)
"Move to COLUMN. If already there or N > 1 then move down N lines.
The line movement uses `line-move-visual'.
COLUMN defaults to the current column.
N is the numeric prefix argument. It defaults to 1.
Non-nil NOERROR means do not raise an error. Otherwise, raise an
error if `line-move-visual' would raise an error.
Non-nil FORCE means force moving to COLUMN, inserting SPC chars as
needed."
(interactive "i\np")
(let ((opoint (point)))
(setq column (or column (current-column))
n (or n 1))
(move-to-column column force)
(when (or (= opoint (point)) (> n 1)) (line-move-visual n noerror))))
(defun fw-to-column-up (&optional column n noerror force)
"Same as `fw-to-column-down', except move up, not down."
(interactive "i\np")
(fw-to-column-down column (- n) noerror force))
(defun fw-next-where-vertical (&optional predicate start args n noerror force)
"Like `fw-next-where', but look down instead of forward (right).
Optional args NOERROR and FORCE are as for `fw-to-column-down'."
(setq n (or n 1))
(let* ((col (current-column))
(pred (lambda (pos &rest args)
(and (= (current-column) col)
(or (not (functionp predicate)) (apply predicate pos args))))))
(fw--next/prev-where (if (< n 0) 'previous 'next) pred start args (if (< n 0) (- n) n)
(lambda () (fw-to-column-down col n noerror force)))))
(defun fw-previous-where-vertical (&optional predicate start args n noerror force)
"Like `fw-next-where-vertical', but look up instead of down."
(setq n (or n 1))
(fw-next-where-vertical predicate start args (- n) noerror force))
(defun fw-to-next-where-vertical (&optional predicate start args n noerror
force readp interactivep)
"Like `fw-to-next-where', but move down instead of forward (across).
Find the Nth position directly below START where PREDICATE is true.
If N is negative, find the Nth position directly above START.
Optional args NOERROR and FORCE are as for `fw-to-column-down'.
Other args are as for `fw-to-next-where'."
(interactive
(let ((parg current-prefix-arg))
(list nil nil nil (if (atom parg) (prefix-numeric-value parg) 1) nil nil (consp parg) t)))
(setq n (or n 1))
;; If PREDICATE arg, use it. ELSE if READP arg, read PREDICATE and use it.
;; In each of those cases, use PREDICATE to define a column-specific predicate, and use that.
;; ELSE (no PREDICATE and no READP arg provided), use `fw-last-pred' (which could be nil).
(setq predicate (or predicate (and readp (fw--read-predicate))))
(let* ((col (current-column))
(pred (if (not predicate)
fw-last-pred
(lambda (pos &rest args)
(and (= (current-column) col)
(or (not (functionp predicate)) (apply predicate pos args)))))))
(fw--to-next/prev-where (if (< n 0) 'previous 'next) pred start args (if (< n 0) (- n) n)
noerror nil interactivep
;; Use 1, not N, so can test after each move.
(lambda () (fw-to-column-down col (if (< n 0) -1 1) noerror force))
(lambda () (fw-to-column-down col (if (< n 0) -1 1) noerror force)))))
(defun fw-to-previous-where-vertical (&optional predicate start args n noerror
force readp interactivep)
"Like `fw-to-previous-where', but move up instead of backward (across)."
(interactive
(let ((parg current-prefix-arg))
(list nil nil nil (if (atom parg) (prefix-numeric-value parg) 1) nil nil (consp parg) t)))
(setq n (or n 1))
(fw-to-next-where-vertical
predicate start args (if (< n 0) n (- n)) noerror force readp interactivep))
;; Could be called `fw-next-word-vertical', but it moves _after_ the word, like `forward-word'.
(defun fw-downward-word (&optional n interactivep)
"Like `forward-word', but move down, not across."
(interactive "p\np")
(setq n (or n 1))
(let ((next/prev (if (< n 0) 'previous 'next)))
(when interactivep (message "Finding %s%s word%s vertically..."
next/prev
(if (memq n '(1 -1)) "" (format " (%d)" (abs n)))
(if (memq n '(1 -1)) "" "s")))
(dotimes (_i (abs n))
(if (or (eobp) (fw-word-char-after-p (point)))
(fw-to-next-where-vertical #'fw-not-word-char-after-p nil nil (if (< n 0) -1 1))
(let ((pos (point)))
(condition-case err
(progn
(fw-to-next-where-vertical #'fw-word-char-after-p nil nil (if (< n 0) -1 1))
(fw-to-next-where-vertical #'fw-not-word-char-after-p nil nil (if (< n 0) -1 1))
(when interactivep (message "Finding %s%s word%s vertically...done"
next/prev
(if (memq n '(1 -1)) "" (format " (%d)" (abs n)))
(if (memq n '(1 -1)) "" "s"))))
(error (progn (goto-char pos) (error "%s" (error-message-string err))))))))))
;; Could be called `fw-previous-word-vertical', but moves _before_ word, like `backward-word'.
(defun fw-upward-word (&optional n interactivep)
"Like `backward-word', but move up, not across."
(interactive "p\np")
(fw-downward-word (- n) interactivep))
)
;;; THING movement ---------------------------------------------------
(defvar fw-last-thing nil
"Last thing used by `fw-to-next-thing' or `fw-to-previous-thing'.")
(defun fw-next-thing (thing &optional start n)
"Find the Nth buffer position after START that is the start of a THING.
Return nil if there is no such position.
Otherwise, return the found position in a cons (POSITION . VALUE),
where VALUE is what `fw-thing-start-p' returns.
START defaults to point.
N defaults to 1."
(fw--next/prev-thing 'next thing start n))
(defun fw-previous-thing (thing &optional start n)
"Find the Nth buffer position before START that is the start of a THING.
Same as `fw-next-thing' except this searches backward."
(fw--next/prev-thing 'previous thing start n))
(defun fw--next/prev-thing (next/prev thing start n)
"Helper for `fw-next-thing' and `fw-previous-thing'."
(fw--next/prev-where next/prev #'fw-thing-start-p start (list thing) n))
(defun fw-thing-start-p (position thing)
"Return true if POSITION is at the start of a THING, otherwise nil.
A true value means also that (1- POSITION) is not on the same THING,
or else point is at the beginning of the buffer.
The true value returned is a cons (THE-THING . END), where THE-THING is
the THING that starts at POSITION, and END is the buffer position of its end.
THE-THING."
(fw-test-start-p position
(if (> emacs-major-version 23) ; Emacs 24+
(lambda ()
(let ((bounds (tap-bounds-of-thing-at-point thing)))
(and bounds
(= position (car bounds))
(cons (buffer-substring (car bounds) (cdr bounds))
(cdr bounds)))))
`(lambda () ; No `lexical-binding' before Emacs 24
(let ((bounds (tap-bounds-of-thing-at-point ',thing)))
(and bounds
(= ,position (car bounds))
(cons (buffer-substring (car bounds) (cdr bounds))
(cdr bounds))))))))
;;;###autoload
(defun fw-to-next-thing (&optional thing start n noerror readp interactivep)
"Go to first buffer position after point that is the start of a THING.
You are prompted for THING if you use a plain prefix arg or if this is
the first time you use the command. Otherwise, THING is the value of
`fw-last-thing', which is the last THING used by the command.
N is the numeric prefix argument. It defaults to 1.
Non-interactively:
Go to Nth buffer position after START that is the start of a THING.
Non-nil NOERROR means do not raise an error when there is no such
next position.
Return what `fw-thing-start-p' returns:
* nil if there is no such position.
* a cons (START THE-THING . END), where THE-THING is the THING, and
START and END are its buffer-position bounds."
(interactive (let ((parg current-prefix-arg))
(list nil nil (if (atom parg) (prefix-numeric-value parg) 1) nil (consp parg) t)))
(fw--to-next/prev-thing 'next thing start n noerror readp interactivep))
;;;###autoload
(defun fw-to-previous-thing (&optional thing start n noerror readp interactivep)
"Go to first buffer position before point that is the start of a THING.
Same as `fw-to-next-thing', except this moves backward."
(interactive (let ((parg current-prefix-arg))
(list nil nil (if (atom parg) (prefix-numeric-value parg) 1) nil (consp parg) t)))
(fw--to-next/prev-thing 'previous thing start n noerror readp interactivep))
(defun fw--to-next/prev-thing (next/prev &optional thing start n noerror readp interactivep)
"Helper for `fw-to-next-thing' and `fw-to-previous-thing'."
(when readp (setq fw-last-thing nil))
(when thing (setq fw-last-thing thing))
(unless fw-last-thing
(while (not fw-last-thing)
(setq fw-last-thing (read (let (this-command) (read-string "Thing: "))))))
(when interactivep (message "Finding %s%s %s..."
next/prev
(if (= n 1) "" (format " (%d)" n))
fw-last-thing))
(let ((res (fw--next/prev-thing next/prev fw-last-thing start n)))
(if (not res)
(unless noerror (error "Not found"))
(goto-char (car res))
(when interactivep (message "Finding %s%s %s...done"
next/prev
(if (= n 1) "" (format " (%d)" n))
fw-last-thing)))
res))
;;;;;;;;;;;;;;;;;;;;;;;
(provide 'find-where)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; find-where.el ends here