unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
blob 28b61880d0a30eccbc66d409ac2fac5e233fceee 8002 bytes (raw)
name: lisp/emacs-lisp/cursor-sensor.el 	 # note: path name is non-authoritative(*)

  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
 
;;; cursor-sensor.el --- React to cursor movement  -*- lexical-binding: t; -*-

;; Copyright (C) 2015-2016 Free Software Foundation, Inc.

;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
;; Keywords:

;; This file is part of GNU Emacs.

;; GNU Emacs 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.

;; GNU Emacs 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 <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This package implements the `cursor-intangible' property, which is
;; meant to replace the old `intangible' property.  To use it, just enable the
;; `cursor-intangible-mode', after which this package will move point away from
;; any position that has a non-nil `cursor-intangible' property.  This is only
;; done just before redisplay happens, contrary to the old `intangible'
;; property which was done at a much lower level.

;;; Code:

;;;###autoload
(defvar cursor-sensor-inhibit nil)

(defun cursor-sensor--intangible-p (pos)
  (let ((p (get-pos-property pos 'cursor-intangible)))
    (if p
        (let (a b)
          (if (and (setq a (get-char-property pos 'cursor-intangible))
                   (setq b (if (> pos (point-min))
                               (get-char-property (1- pos) 'cursor-intangible)))
                   (not (eq a b)))
              ;; If we're right between two different intangible thingies,
              ;; we can stop here.  This is not quite consistent with the
              ;; interpretation of "if it's sticky, then this boundary is
              ;; itself intangible", but it's convenient (and it better matches
              ;; the behavior of `intangible', making it easier to port code).
              nil p))
      p)))

(defun cursor-sensor-tangible-pos (curpos window &optional second-chance)
  (let ((newpos curpos))
    (when (cursor-sensor--intangible-p newpos)
      (let ((oldpos (window-parameter window 'cursor-intangible--last-point)))
        (cond
         ((or (and (integerp oldpos) (< oldpos newpos))
              (eq newpos (point-min)))
          (while
              (when (< newpos (point-max))
                (setq newpos
                      (if (get-char-property newpos 'cursor-intangible)
                          (next-single-char-property-change
                           newpos 'cursor-intangible nil (point-max))
                        (1+ newpos)))
                (cursor-sensor--intangible-p newpos))))
         (t ;; (>= oldpos newpos)
          (while
              (when (> newpos (point-min))
                (setq newpos
                      (if (get-char-property (1- newpos) 'cursor-intangible)
                          (previous-single-char-property-change
                           newpos 'cursor-intangible nil (point-min))
                        (1- newpos)))
                (cursor-sensor--intangible-p newpos)))))
        (if (not (and (or (eq newpos (point-min)) (eq newpos (point-max)))
                      (cursor-sensor--intangible-p newpos)))
            ;; All clear, we're good to go.
            newpos
          ;; We're still on an intangible position because we bumped
          ;; into an intangible BOB/EOB: try to move in the other direction.
          (if second-chance
              ;; Actually, we tried already and that failed!
              curpos
            (cursor-sensor-tangible-pos newpos window 'second-chance)))))))

(defun cursor-sensor-move-to-tangible (window)
  (let* ((curpos (window-point window))
         (newpos (cursor-sensor-tangible-pos curpos window)))
    (when newpos (set-window-point window newpos))
    (set-window-parameter window 'cursor-intangible--last-point
                          (or newpos curpos))))

(defun cursor-sensor--move-to-tangible (window)
  (unless cursor-sensor-inhibit
    (cursor-sensor-move-to-tangible window)))

;;;###autoload
(define-minor-mode cursor-intangible-mode
  "Keep cursor outside of any `cursor-intangible' text property."
  nil nil nil
  (if cursor-intangible-mode
      (add-hook 'pre-redisplay-functions #'cursor-sensor--move-to-tangible
                nil t)
    (remove-hook 'pre-redisplay-functions #'cursor-sensor--move-to-tangible t)))

;;; Detect cursor movement.

(defun cursor-sensor--detect (window)
  (unless cursor-sensor-inhibit
    (let* ((point (window-point window))
           ;; It's often desirable to make the cursor-sensor-functions property
           ;; non-sticky on both ends, but that means get-pos-property might
           ;; never see it.
           (new (or (get-char-property point 'cursor-sensor-functions)
                    (unless (<= (point-min) point)
                      (get-char-property (1- point) 'cursor-sensor-functions))))
           (old (window-parameter window 'cursor-sensor--last-state))
           (oldposmark (car old))
           (oldpos (or (if oldposmark (marker-position oldposmark))
                       (point-min)))
           (start (min oldpos point))
           (end (max oldpos point)))
      (unless (or (null old) (eq (marker-buffer oldposmark) (current-buffer)))
        ;; `window' does not display the same buffer any more!
        (setcdr old nil))
      (if (or (and (null new) (null (cdr old)))
              (and (eq new (cdr old))
                   (eq (next-single-property-change
                        start 'cursor-sensor-functions nil end)
                       end)))
          ;; Clearly nothing to do.
          nil
        ;; Maybe something to do.  Let's see exactly what needs to run.
        (let* ((missing-p
                (lambda (f)
                  "Non-nil if F is missing somewhere between START and END."
                  (let ((pos start)
                        (missing nil))
                    (while (< pos end)
                      (setq pos (next-single-property-change
                                 pos 'cursor-sensor-functions
                                 nil end))
                      (unless (memq f (get-char-property
                                       pos 'cursor-sensor-functions))
                        (setq missing t)))
                    missing))))
          (dolist (f (cdr old))
            (unless (and (memq f new) (not (funcall missing-p f)))
              (funcall f window oldpos 'left)))
          (dolist (f new)
            (unless (and (memq f (cdr old)) (not (funcall missing-p f)))
              (funcall f window oldpos 'entered)))))

      ;; Remember current state for next time.
      ;; Re-read cursor-sensor-functions since the functions may have moved
      ;; window-point!
      (if old
          (progn (move-marker (car old) point)
                 (setcdr old new))
        (set-window-parameter window 'cursor-sensor--last-state
                              (cons (copy-marker point) new))))))

;;;###autoload
(define-minor-mode cursor-sensor-mode
  "Handle the `cursor-sensor-functions' text property.
This property should hold a list of functions which react to the motion
of the cursor.  They're called with three arguments (WINDOW OLDPOS DIR)
where WINDOW is the affected window, OLDPOS is the last known position of
the cursor and DIR can be `entered' or `left' depending on whether the cursor
is entering the area covered by the text-property property or leaving it."
  nil nil nil
  (if cursor-sensor-mode
      (add-hook 'pre-redisplay-functions #'cursor-sensor--detect
                nil t)
    (remove-hook  'pre-redisplay-functions #'cursor-sensor--detect
                t)))

(provide 'cursor-sensor)
;;; cursor-sensor.el ends here

debug log:

solving 28b6188 ...
found 28b6188 in https://git.savannah.gnu.org/cgit/emacs.git

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).