branch: externals/cm-mode
commit 975f0ee8454786e5d3b71ddbe6df0086589a00b8
Author: Joost Kremers <[email protected]>
Commit: Joost Kremers <[email protected]>

    Make CM tags read-only.
---
 README.md  |  2 +-
 cm-mode.el | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 72 insertions(+), 14 deletions(-)

diff --git a/README.md b/README.md
index bd5bd7e9ef..8a56e8605a 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ You can interactively accept or reject all changes with `C-c 
* I` (that is a cap
 
 ## Font lock ##
 
-`cm-mode` also adds the markup tags defined by CriticMarkup to 
`font-lock-keywords` and provides customisable faces to highlight them. The 
customisation group is called `criticmarkup-faces`.
+`cm-mode` also adds the markup tags defined by CriticMarkup to 
`font-lock-keywords` and provides customisable faces to highlight them. The 
customisation group is called `criticmarkup-faces`. Note that `cm-mode` also 
makes the markup tags read-only so that you cannot inadvertently modify them.
 
 You may notice that changes that span multiple lines are not highlighted. The 
reason for this is that multiline font lock in Emacs is not straightforward. 
There are ways to deal with this, but since `cm-mode` is a minor mode, it could 
interfere with the major mode's font locking mechanism if it did that. Besides, 
one is advised not to include newlines inside CriticMarkup tags anyway.
 
diff --git a/cm-mode.el b/cm-mode.el
index 9549decb92..14116a29b9 100644
--- a/cm-mode.el
+++ b/cm-mode.el
@@ -219,21 +219,64 @@ it is added automatically."
   :init-value nil :lighter (:eval (concat " CM" (if cm-author (concat "@" 
cm-author)) (if cm-follow-changes "*"))) :global nil
   (cond
    (cm-mode                             ; cm-mode is turned on
-    (font-lock-add-keywords nil `((,cm-addition-regexp 0 cm-addition-face 
prepend)
-                                  (,cm-deletion-regexp 0 cm-deletion-face 
prepend)
-                                  (,cm-substitution-regexp 0 
cm-substitution-face prepend)
-                                  (,cm-comment-regexp 0 cm-comment-face 
prepend)
-                                  (,cm-highlight-regexp 0 cm-highlight-face 
prepend)) t)
+    (font-lock-add-keywords nil (cm-font-lock-keywords) t)
+    (add-to-list 'font-lock-extra-managed-props 'read-only)
+    (add-to-list 'font-lock-extra-managed-props 'rear-nonsticky)
+    (font-lock-fontify-buffer) ; usually sufficient to make the fontifications 
appear immediately
     (setq cm-current-markup-overlay (make-overlay 1 1))
     (overlay-put cm-current-markup-overlay 'face 'highlight))
    ((not cm-mode)                       ; cm-mode is turned off
-    (font-lock-remove-keywords nil `((,cm-addition-regexp 0 cm-addition-face 
prepend)
-                                     (,cm-deletion-regexp 0 cm-deletion-face 
prepend)
-                                     (,cm-substitution-regexp 0 
cm-substitution-face prepend)
-                                     (,cm-comment-regexp 0 cm-comment-face 
prepend)
-                                     (,cm-highlight-regexp 0 cm-highlight-face 
prepend)))
+    (font-lock-remove-keywords nil (cm-font-lock-keywords))
+    (setq font-lock-extra-managed-props (delq 'read-only (delq 'rear-nonsticky 
font-lock-extra-managed-props)))
+    (cm-make-markups-writable) ; we need to remove the read-only property by 
hand; it's cumbersome to do it with font-lock
+    (font-lock-fontify-buffer) ; usually sufficient to make the fontifications 
disappear immediately
     (remove-overlays))))
 
+(defun cm-font-lock-for-markup (type)
+  "Create a font lock entry for markup TYPE."
+  (let ((markup (cdr type))
+        font-lock)
+    (add-to-list 'font-lock (mapconcat #'(lambda (elt) ; first we create the 
regexp to match
+                                           (regexp-opt (list elt) t))
+                                       markup
+                                       ".*?"))
+    (add-to-list 'font-lock `(0 ,(intern (concat (symbol-name (car type)) 
"-face"))) t) ; the highlighter for the entire change
+    (dotimes (n (length markup))
+      (add-to-list 'font-lock `(,(1+ n) '(face default read-only t)) t) ; make 
the tags read-only
+      (add-to-list 'font-lock `("." (progn ; and make the read-only property 
of the final character rear-nonsticky
+                                      (goto-char (1- (match-end ,(1+ n))))
+                                      (1+ (point)))
+                                nil
+                                (0 '(face default rear-nonsticky 
(read-only)))) t))
+    font-lock))
+
+;; `cm-font-lock-for-markup' produces a font-lock entry that can be given
+;; to `font-lock-add-keywords'. To illustrate, the entry it produces for
+;; additions is the following:
+;; 
+;; ("\\({\\+\\+\\).*?\\(\\+\\+}\\)"
+;;  (0 cm-addition-face)
+;;  (1 '(face default read-only t))
+;;  ("." (progn (goto-char (1- (match-end 1)))
+;;              (1+ (point)))
+;;   nil
+;;   (0 '(face default rear-nonsticky (read-only))))
+;;  (2 '(face default read-only t))
+;;  ("." (progn (goto-char (1- (match-end 2)))
+;;              (1+ (point)))
+;;   nil
+;;   (0 '(face default rear-nonsticky (read-only)))))
+;;
+;; This does some nice magic: it adds cm-addition-face to addition markups,
+;; it makes the tags themselves, `{++' and `++}' read-only, and it gives
+;; the last character of the tags the text property (rear-nonsticky
+;; (read-only)), so that it's possible to add characters after the tag.
+;;
+
+(defun cm-font-lock-keywords ()
+  "Return a list of font lock keywords."
+  (mapcar #'cm-font-lock-for-markup cm-delimiters))
+
 (defun cm-follow-changes (&optional arg)
   "Record changes."
   (interactive (list (or current-prefix-arg 'toggle)))
@@ -283,6 +326,20 @@ details."
   (cm-without-following-changes
     ad-do-it))
 
+(defun cm-make-markups-writable ()
+  "Make all CM markup delimiters in the current buffer writable."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((delims-regexp (concat (regexp-opt (mapcar #'second cm-delimiters) t)
+                                 ".*?"
+                                 "\\(?:\\(~>\\).*?\\)?"
+                                 (regexp-opt (mapcar #'cm-last1 cm-delimiters) 
t)))
+          (inhibit-read-only t))
+      (while (re-search-forward delims-regexp nil t)
+        (dolist (n '(1 2 3))
+          (when (match-string n)
+            (remove-text-properties (match-beginning n) (match-end n) 
'(read-only nil rear-nonsticky nil))))))))
+
 (defun cm-insert-markup (type &optional text)
   "Insert CriticMarkup of TYPE.
 Also insert TEXT if non-NIL. For deletions, TEXT is the deleted
@@ -725,9 +782,10 @@ is NIL."
           (throw 'quit nil))                         ; get out
         (cond
          ((memq action '(?a ?r ?d))
-          (cm-without-following-changes
-            (delete-region (third change) (fourth change))
-            (insert (cm-substitution-string change action)))
+          (let ((inhibit-read-only t))
+            (cm-without-following-changes
+              (delete-region (third change) (fourth change))
+              (insert (cm-substitution-string change action))))
           (point))
          ((eq action ?s)
           (fourth change)))))))

Reply via email to