branch: externals/cm-mode
commit 8540daebffe6200a22c8584f931596cde57b07dc
Author: Joost Kremers <[email protected]>
Commit: Joost Kremers <[email protected]>
Initial support for follow changes mode.
---
README.md | 13 +++--
cm-mode.el | 175 +++++++++++++++++++++++++++++++++++++++++++++----------------
2 files changed, 139 insertions(+), 49 deletions(-)
diff --git a/README.md b/README.md
index c19e76e034..231f80c39d 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# CriticMarkup for Emacs #
-`cm-mode` is a minor mode that provides (rudimentary) support for
-[CriticMarkup](http://criticmarkup.com/) in Emacs.
+`cm-mode` is a minor mode that provides support for
[CriticMarkup](http://criticmarkup.com/) in Emacs.
CriticMarkup defines the following patterns for marking changes to a text:
@@ -21,9 +20,16 @@ Activating `cm-mode` provides key{--s--} {++bindings ++}to
insert the {~~pattern
The commands to delete or substitute text operate on the region. The command
to insert a comment can be used with an active region, in which case the text
in the region will be highlighted. It can also be used inside an existing
markup to add a comment to it. If it is used anywhere else, it just adds a lone
comment. The commands for inserting and substituting text and for inserting a
comment all put the cursor at the correct position, so you can start typing
right away.
+## Follow changes mode ##
+
+`cm-mode` also provides a (rudimentary) 'follow changes' mode. When activated,
changes you make to the buffer are automatically marked as insertions or
deletions. Substitutions cannot be made automatically (that is, if you mark a
word, delete it and then type a replacement, it will still be marked as
sequence of deletion+insertion, not as a substitution), but they can still be
made manually with `C-c * s`. You can activate and deactivate follow changes
mode with `C-c * F`. When it's ac [...]
+
+Note that this functionality is in development and not very polished yet.
Multiple deletions in sequence, for example, are not combined, so that deleting
a word with `<backspace>` leaves a string of deletion markups. Deleting a
character with `<del>` also leaves the cursor in the wrong position. Follow
changes mode should also be considered alpha-grade, i.e., it works to the
extent that it works. (If you experience problems with it, please open up an
issue on Github or send me an email.)
+
+
## Accepting or rejecting changes ##
-You can interactively accept or reject a change by putting the cursor inside
it and hitting `C-c * i`. For additions, deletions and substitutions, you get a
choice between `a` to accept the change or `r` to reject it. There are two
other choices, `s` to skip this change or `q` to quit. Both leave the change
untouched and if you're just dealing with the change at point, they are
essentially identical. {>>(They have different functions when accepting or
rejecting all changes interactively, [...]
+One can interactively accept or reject a change by putting the cursor inside
it and hitting `C-c * i`. For additions, deletions and substitutions, you get a
choice between `a` to accept the change or `r` to reject it. There are two
other choices, `s` to skip this change or `q` to quit. Both leave the change
untouched and if you're just dealing with the change at point, they are
essentially identical. {>>(They have different functions when accepting or
rejecting all changes interactively, [...]
For comments and highlights, the choices are different: `d` to delete the
comment or highlight (whereby the latter of course retains the highlighted
text, but the comment and the markup are removed), or `k` to keep the comment
or highlight. Again `q` quits and is essentially identical to `k`. (Note that
you can also use `s` instead of `k`, in case you get used to skipping changes
that way.)
@@ -42,5 +48,4 @@ To mitigate this problem, you can use soft wrap (with
`visual-line-mode`). Since
## TODO ##
- Commands to accept or reject all changes in one go. {>>These won't be bound
to keys, though.<<}
-- Follow changes mode: automatically insert CriticMarkup when changes are made
to the buffer.
- Mouse support?
diff --git a/cm-mode.el b/cm-mode.el
index c333f18a07..9b4c168426 100644
--- a/cm-mode.el
+++ b/cm-mode.el
@@ -35,27 +35,26 @@
;; CriticMarkup for Emacs
;; ======================
-;;
-;; cm-mode is a minor mode that provides (rudimentary) support for
-;; CriticMarkup in Emacs.
-;;
+;;
+;; cm-mode is a minor mode that provides support for CriticMarkup in Emacs.
+;;
;; CriticMarkup defines the following patterns for marking changes to a
;; text:
-;;
+;;
;; - Addition {++ ++}
;; - Deletion {-- --}
;; - Substitution {~~ ~> ~~}
;; - Comment {>> <<}
;; - Highlight {{ }}{>> <<}
-;;
+;;
;; Activating cm-mode provides key bindings to insert the markup above and
;; thus mark one's changes to the text. The provided key bindings are:
-;;
+;;
;; - C-c * a: add text
;; - C-c * d: delete text
;; - C-c * s: substitute text
;; - C-c * c: insert a comment (possibly with highlight)
-;;
+;;
;; The commands to delete or substitute text operate on the region. The
;; command to insert a comment can be used with an active region, in which
;; case the text in the region will be highlighted. It can also be used
@@ -63,54 +62,71 @@
;; else, it just adds a lone comment. The commands for inserting and
;; substituting text and for inserting a comment all put the cursor at the
;; correct position, so you can start typing right away.
-;;
+;;
+;; Follow changes mode
+;; -------------------
+;;
+;; cm-mode also provides a (rudimentary) 'follow changes' mode. When
+;; activated, changes you make to the buffer are automatically marked as
+;; insertions or deletions. Substitutions cannot be made automatically
+;; (that is, if you mark a word, delete it and then type a replacement, it
+;; will still be marked as sequence of deletion+insertion, not as a
+;; substitution), but they can still be made manually with C-c * s. You can
+;; activate and deactivate follow changes mode with C-c * F. When it's
+;; active, the modeline indicator for cm-mode changes from cm to cm*.
+;;
+;; Note that this functionality is in development and not very polished
+;; yet. Multiple deletions in sequence, for example, are not combined, so
+;; that deleting a word with <backspace> leaves a string of deletion
+;; markups. Deleting a character with <del> also leaves the cursor in the
+;; wrong position. Follow changes mode should also be considered
+;; alpha-grade, i.e., it works to the extent that it works. (If you
+;; experience problems with it, please open up an issue on Github or send
+;; me an email.)
+;;
;; Accepting or rejecting changes
;; ------------------------------
-;;
-;; You can interactively accept or reject a change by putting the cursor
+;;
+;; One can interactively accept or reject a change by putting the cursor
;; inside it and hitting C-c * i. For additions, deletions and
;; substitutions, you get a choice between a to accept the change or r to
;; reject it. There are two other choices, s to skip this change or q to
;; quit. Both leave the change untouched and if you're just dealing with
-;; the change at point, they are essentially identical. (They have
-;; different functions when accepting or rejecting all changes
-;; interactively, though.)
-;;
+;; the change at point, they are essentially identical.
+;;
;; For comments and highlights, the choices are different: d to delete the
;; comment or highlight (whereby the latter of course retains the
;; highlighted text, but the comment and the markup are removed), or k to
;; keep the comment or highlight. Again q quits and is essentially
;; identical to k. (Note that you can also use s instead of k, in case you
;; get used to skipping changes that way.)
-;;
+;;
;; You can interactively accept or reject all changes with C-c * I (that is
;; a capital i). This will go through each change asking you whether you
;; want to accept, reject or skip it, or delete or keep it. Typing q quits
;; the accept/reject session.
-;;
+;;
;; Font lock
;; ---------
-;;
+;;
;; cm-mode also adds the markup patterns defined by CriticMarkup to
;; font-lock-keywords and provides customisable faces to highlight them.
;; The customisation group is called criticmarkup.
-;;
+;;
;; 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.
-;;
+;;
;; To mitigate this problem, you can use soft wrap (with visual-line-mode).
;; Since each paragraph is then essentially a single line, font lock works
;; even across multiple (visual) lines.
-;;
+;;
;; TODO
;; ----
-;;
-;; - Commands to accept or reject all changes in one go
-;; - Follow changes mode: automatically insert CriticMarkup when changes
-;; - are made to the buffer.
+;;
+;; - Commands to accept or reject all changes in one go.
;; - Mouse support?
;;; Code:
@@ -124,6 +140,19 @@
(cm-highlight "{{" "}}"))
"CriticMarkup Delimiters.")
+(defvar cm-follow-changes nil
+ "Flag indicating whether follow changes mode is active.")
+
+(defvar cm-change-text nil
+ "Deleted text in follow changes mode.")
+
+(defvar cm-change-no-record nil
+ "Flag indicating whether to actually record a change.
+In follow changes mode, some operations that change the buffer
+must not be recorded with markup. Such functions can set this
+flag to indicate this. (Though they should actually use the macro
+`cm-without-following-changes'.)")
+
(defvar cm-addition-regexp "\\(?:{\\+\\+.*?\\+\\+}\\)"
"CriticMarkup addition regexp.")
@@ -188,13 +217,14 @@
(define-key map "\C-c*c" 'cm-comment)
(define-key map "\C-c*i" 'cm-accept/reject-change-at-point)
(define-key map "\C-c*I" 'cm-accept/reject-all-changes)
+ (define-key map "\C-c*F" 'cm-follow-changes)
map)
"Keymap for cm-mode.")
;;;###autoload
(define-minor-mode cm-mode
"Minor mode for CriticMarkup."
- :init-value nil :lighter " cm" :global nil
+ :init-value nil :lighter (:eval (concat " cm" cm-follow-changes)) :global nil
(cond
(cm-mode ; cm-mode is turned on
(font-lock-add-keywords nil `((,cm-addition-regexp . cm-addition-face)
@@ -212,6 +242,57 @@
(,cm-highlight-regexp .
cm-highlight-face)))
(remove-overlays))))
+(defun cm-follow-changes ()
+ "Record changes."
+ (interactive)
+ (if cm-follow-changes
+ (progn
+ (setq before-change-functions (delq 'cm-before-change
before-change-functions))
+ (setq after-change-functions (delq 'cm-after-change
after-change-functions))
+ (ad-deactivate 'undo)
+ (setq cm-follow-changes nil)
+ (message "Follow changes mode deactivated."))
+ (add-to-list 'before-change-functions 'cm-before-change t)
+ (add-to-list 'after-change-functions 'cm-after-change)
+ (ad-activate 'undo t)
+ (setq cm-follow-changes "*")
+ (message "Follow changes mode activated.")))
+
+(defun cm-before-change (beg end)
+ "Function to execute before a buffer change.
+In the case of an addition, this function adds the relevant
+markup. For a deletion, the deleted text is stored so that
+cm-after-change can insert it again."
+ (unless (or cm-change-no-record ; do not record this change
+ (and (= beg (point-min)) (= end (point-max))) ; this happens on
buffer switches
+ (cm-markup-at-point)) ; if we're already inside a change, don't
do anything special
+ (if (not (= beg end)) ; deletion
+ (setq cm-change-text (buffer-substring beg end))
+ (insert "{++++}")
+ (forward-char -3))))
+
+(defun cm-after-change (beg end length)
+ "Function to execute after a buffer change.
+This function marks deletions. See cm-before-change for
+details."
+ (unless (or cm-change-no-record
+ (not cm-change-text))
+ (save-excursion
+ (goto-char beg)
+ (insert (concat "{--" cm-change-text "--}"))
+ (setq cm-change-text nil))))
+
+(defmacro cm-without-following-changes (&rest body)
+ "Execute BODY without following changes."
+ (declare (indent defun))
+ `(let ((cm-change-no-record t))
+ ,@body))
+
+(defadvice undo (around cm-no-follow (&optional arg))
+ "Temporarily remove cm-record-change from before-change-functions."
+ (cm-without-following-changes
+ ad-do-it))
+
;;;###autoload
(defun turn-on-cm ()
"Unconditionally turn on cm-mode."
@@ -228,43 +309,47 @@
(interactive)
(when (cm-markup-at-point)
(error "Already inside a change"))
- (insert "{++++}")
- (backward-char 3))
+ (cm-without-following-changes
+ (insert "{++++}")
+ (backward-char 3)))
(defun cm-deletion (beg end)
"Mark text for deletion."
(interactive "r")
(when (cm-markup-at-point)
(error "Already inside a change"))
- (let ((text (delete-and-extract-region beg end)))
- (insert (concat "{--" text "--}"))))
+ (cm-without-following-changes
+ (let ((text (delete-and-extract-region beg end)))
+ (insert (concat "{--" text "--}")))))
(defun cm-substitution (beg end)
"Mark a substitution."
(interactive "r")
(when (cm-markup-at-point)
(error "Already inside a change"))
- (let ((text (delete-and-extract-region beg end)))
- (insert (concat "{~~" text "~>~~}"))
- (backward-char 3)))
+ (cm-without-following-changes
+ (let ((text (delete-and-extract-region beg end)))
+ (insert (concat "{~~" text "~>~~}"))
+ (backward-char 3))))
(defun cm-comment (beg end)
"Add a comment.
If the region is active, the text in the region is highlighted.
If point is in an existing change, the comment is added after it."
(interactive "r")
- (let ((change (cm-markup-at-point))
- text)
- (cond
- (change
- (deactivate-mark) ; we don't want the region active
- (cm-forward-markup (car change)))
- ;; note: we do not account for the possibility that the region
- ;; contains a change but point is outside of it...
- ((use-region-p)
- (setq text (delete-and-extract-region beg end))))
- (insert (if text (concat "{{" text "}}") "") "{>><<}")
- (backward-char 3)))
+ (cm-without-following-changes
+ (let ((change (cm-markup-at-point))
+ text)
+ (cond
+ (change
+ (deactivate-mark) ; we don't want the region active
+ (cm-forward-markup (car change)))
+ ;; note: we do not account for the possibility that the region
+ ;; contains a change but point is outside of it...
+ ((use-region-p)
+ (setq text (delete-and-extract-region beg end))))
+ (insert (if text (concat "{{" text "}}") "") "{>><<}")
+ (backward-char 3))))
(defun cm-forward-markup (type &optional n)
"Move forward N markups of TYPE.