When editing clojure code in emacs, it's common to tweak font-lock to insert 'fancy' characters for lambdas, sets, anon functions.
This is achieved with some variant of the following (seen in clojure-mode, emacs-live etc.)
(dolist (mode '(clojure-mode clojurescript-mode nrepl-interaction-mode))
(eval-after-load mode
(font-lock-add-keywords
mode '(("(\\(fn\\)[\[[:space:]]" ; anon funcs 1
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "λ")
nil)))
("\\(#\\)(" ; anon funcs 2
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "ƒ")
nil)))
("\\(#\\){" ; sets
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "∈")
nil)))))))
This works great until you make an edit around the fancy character. You can leave hanging characters as per the following screenshot...
How can I get emacs to revert back to the 'real' characters when I've made an edit. The nil(s) in the code above could be a modification function that (presumably) does this and there is a (decompose-region) that seems to fit the bill. How should it be invoked, passing it as symbol doesn't work.
e.g. I've tried this:
(dolist (mode '(clojure-mode clojurescript-mode nrepl-interaction-mode))
(eval-after-load mode
(font-lock-add-keywords
mode '(("(\\(fn\\)[\[[:space:]]" ; anon funcs 1
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "λ")
'decompose-region)))
("\\(#\\)(" ; anon funcs 2
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "ƒ")
'decompose-region)))
("\\(#\\){" ; sets
(0 (progn (compose-region (match-beginning 1)
(match-end 1) "∈")
'decompose-region)))))))
The easiest way is to add (set (make-local-variable 'font-lock-extra-managed-props) '(composition)).
Related
I'm confused why using remove-text-properties to remove the display text property doesn't change the display in the buffer. Instead it seems I must completely remove all the text properties using set-text-properties to nil. For example, why doesn't remove-text-properties work in place of set-text-properties here:
(defvar my-regex "#\\([[:alnum:]]+\\) \\([0-9]+\\)")
(defvar-local my--fontified-p nil)
(defun my-remove-display ()
"Remove the display, eg. '#blah<2020>' -> '#blah 2020."
(save-excursion
(goto-char (point-min))
(while (re-search-forward my-regex nil 'move)
;; why can't I use remove-text-properties here to get rid of 'display?
(set-text-properties (match-beginning 0) (match-end 0) nil))))
(defun my-toggle-display ()
"Toggle font-locking and display of '#blah 2020'."
(interactive)
(if (setq my--fontified-p (not my--fontified-p))
(progn
(font-lock-add-keywords
nil
`((,my-regex
(0 (prog1 nil
(put-text-property
(1+ (match-beginning 0)) (match-end 0)
'display
(format "%s<%s>"
(match-string-no-properties 1)
(match-string-no-properties 2)))))
(0 'font-lock-constant-face t))))
(font-lock-flush)
(font-lock-ensure))
(my-remove-display)
(font-lock-refresh-defaults)))
;;; Example that gets fontified
;; #blah 2020
It works for me:
(defun my-remove-display ()
"Remove the display, eg. '#blah<2020>' -> '#blah 2020."
(save-excursion
(goto-char (point-min))
(while (re-search-forward my-regex nil 'move)
(remove-text-properties (match-beginning 0) (match-end 0) '(display)))))
You didn't show the remove-text-properties code you tried. Is this what you tried? Did you perhaps pass 'display instead of '(display)?
In emacs I can replace function ( with #( in javascript with the following.
(font-lock-add-keywords
'js2-mode `(("\\(function *\\)(" ;; \\(function *\\
(0 (progn (compose-region (match-beginning 1) (match-end 1) "#") ;;ƒ
nil)))))
It's neat. But how would go about making the replacement # ( or something like fn (.
I've tried hacking away at this to understand it... but not yet.
The function (org-heading-components) and (org-element-property) produce integers for the number of stars and also for the priority. I'd like to store the entire headline as a variable and then use re-search-forward (or a similar function) to go back to that heading, but I foresee the problem that will occur when it cannot find an integer. I need to store the whole heading as a variable, because I often have todo entries with duplicate titles but the other components are diferent.
For example, the following todo:
** Active [#A] Ask the geniuses on stackoverflow how to do this. :lawlist:
when evaluated with (org-heading-components) looks like this:
(2 2 "Active" 65 "Ask the geniuses on stackoverflow how to do this." ":lawlist:")
So, when storing that as a variable and later using re-search-forward there will be problems because 2 2 is not the same as **, and 65 is not the same as [#A].
(defun lawlist ()
(interactive)
(let* (
(beg (point))
(complete-heading (org-heading-components) ))
* * *
(goto-char (point-min))
(re-search-forward complete-heading nil t) ))
You should be able to convert the output as follows:
The first # is the current level (# of stars)
The second number is the reduced headline level, applicable if org-odd-levels-only is set, but this is not regarding output.
Todo keyword
Priority character (65 is ASCII code for A)
Headline text
Tags or nil
The following will return the headline string as shown in the buffer. It will not work with re-search-forward but will work with search-forward (It does not escape any characters).
(defun zin/search-test ()
(interactive)
(let ((head (org-element-interpret-data (org-element-at-point))))
(message "%s" (format "%s" (car (split-string head "\n"))))))
This does not set it to any variable, you'll have to wrap it in an appropriate function that will set your desired variable. Then use (search-forward <var> nil t) to match it, without it erroring out if it cannot find it.
There's a brilliant part of org that might suit you: org-id-copy and
org-id-goto. It works with precision across buffers and sessions:
org-id-copy produces a string. You can feed that string to
org-id-goto which will take you to that heading. Even if you've
closed the original buffer. Even if you've restarted Emacs.
EDIT (December 15, 2013): Updated solution based upon the variable org-heading-regexp (defined within org.el) and a modification thereof to include (if it exists) a second line containing a deadline - i.e., lawlist-org-heading-regexp. The revision also includes a nifty function regexp-quote that was just taught to me by #Drew over on superuser: https://superuser.com/questions/688781/how-to-highlight-string-and-unhighlight-string-in-buffer-make-overlay?noredirect=1#comment874515_688781 (buffer-substring-no-properties beg end) is used to set the string as a variable.
EDIT (December 17, 2013): Added isearch-highlight and isearch-dehighlight, and commented out highlight-regexp and unhighlight-regexp. When moving the point around with more complex functions, highlight-regexp does not reliably highlight the entire string -- this may be because the screen has not refreshed, or it may also be caused by other factors -- e.g., hl-line-mode, etc.) -- placing various sit-for 0 did not fix the issue with highlight-regexp -- isearch-highlight works better.
EDIT (January 6, 2014): See also this related thread for a complete regexp to match any element of the entire todo from stars through to the end of the notes: https://stackoverflow.com/a/20960301/2112489
(require 'org)
(defvar lawlist-org-heading-regexp
"^\\(\\*+\\)\\(?: +\\(.*?\\)\\)?[ \t]*\\(\n.*DEADLINE.*$\\)"
"Match headline, plus second line with a deadline.")
(defun example ()
(interactive)
(switch-to-buffer (get-buffer-create "foo"))
(org-mode)
(insert "* Example\n\n")
(insert "** Active [#A] This is an active todo. :lawlist:\n")
(insert " DEADLINE: <2013-12-15 Sun 08:00> SCHEDULED: <2013-12-15 Sun>\n\n")
(insert "** Next-Action [#B] This is an inactive todo. :lawlist:\n")
(insert " DEADLINE: <2013-12-16 Mon 08:00> SCHEDULED: <2013-12-16 Mon>")
(goto-char (point-min))
(sit-for 2)
(re-search-forward (regexp-quote "** Active [#A] "))
(sit-for 2)
(let ((init-pos (point)))
(org-back-to-heading t)
(let* (
lawlist-item-whole
lawlist-item-partial
(beg (point)))
(if (and
(looking-at org-heading-regexp)
(and (looking-at lawlist-org-heading-regexp) (match-string 3)))
(re-search-forward lawlist-org-heading-regexp nil t)
(re-search-forward org-heading-regexp nil t))
(let ((end (point)))
(setq lawlist-item-whole (buffer-substring-no-properties beg end))
(setq lawlist-item-partial (buffer-substring-no-properties beg init-pos))
(re-search-backward (regexp-quote lawlist-item-whole) nil t)
;; (highlight-regexp (regexp-quote lawlist-item-whole))
(isearch-highlight beg end)
(sit-for 2)
;; (unhighlight-regexp (regexp-quote lawlist-item-whole))
(isearch-dehighlight)
(re-search-forward (regexp-quote lawlist-item-partial) nil t)
(sit-for 2)
(kill-buffer "foo")))))
EDIT (October 27, 2013): Prior solution that is being preserved temporarily as a historical part of the evolution process towards a final answer. However, it is no longer a preferred method.
(defun lawlist-org-heading-components ()
(org-back-to-heading t)
(if (let (case-fold-search) (looking-at org-complex-heading-regexp))
(concat
(cond
((equal (org-match-string-no-properties 1) "**")
"^[*][*]")
((equal (org-match-string-no-properties 1) "*")
"^[*]"))
(cond
((and (match-end 2) (aref (match-string 2) 1))
(concat " " (org-match-string-no-properties 2))))
(cond
((and (match-end 3) (aref (match-string 3) 2))
(concat " \\" (org-match-string-no-properties 3))))
(cond
((and (match-end 4) (aref (match-string 4) 3))
(concat " " (org-match-string-no-properties 4))))
(cond
((and (match-end 5) (aref (match-string 5) 4))
(concat " " (org-match-string-no-properties 5)))))))
Following code will visually replace "hello world" with "HW" by passing a progn form to font lock keywords.
(font-lock-add-keywords
nil '(("\\(hello world\\)"
(0 (progn (put-text-property (match-beginning 1) (match-end 1)
'display "HW")
nil)))))
I've look into C-h v font-lock-keywords to see if this is a documented feature of font lock. The hello world element seemed to be of this form:
(MATCHER HIGHLIGHT ...)
which would mean that (0 ...) is HIGHLIGHT and the doc says
HIGHLIGHT should be either MATCH-HIGHLIGHT or MATCH-ANCHORED.
and
MATCH-HIGHLIGHT should be of the form:
(SUBEXP FACENAME [OVERRIDE [LAXMATCH]])
So I guessed 0 was SUBEXP and (progn ...) was FACENAME. But if (progn ..) were a valid FACENAME, the following code would work but it doesn't work.
;; (MATCHER . FACENAME)
(font-lock-add-keywords
nil '(("goodbye lenin"
. (progn (put-text-property (match-beginning 1) (match-end 1)
'display "GL")
nil))))
That brings me to the question of how the first code works and whether it is relying on an undocumented feature.
Update:
Side note: simpler way of visual replacement without font lock errors
(font-lock-add-keywords
nil '(("my llama"
(0 (progn (put-text-property (match-beginning 0) (match-end 0)
'display "ML")
nil)))))
It does work - but your MATCHER is not correct - the result of the match is not stored. This, for example does not work:
(font-lock-add-keywords
nil '(("goodbye lenin"
(0 (progn (put-text-property (match-beginning 1) (match-end 1)
'display "GL")
nil)))))
while this does:
(font-lock-add-keywords
nil '(("\\(goodbye lenin\\)"
. (progn (put-text-property (match-beginning 1) (match-end 1)
'display "GL")
nil))))
The documentation says: "FACENAME is an expression whose value is the face name to use.
Instead of a face, FACENAME can evaluate to a property list
of the form (face FACE PROP1 VAL1 PROP2 VAL2 ...)
in which case all the listed text-properties will be set rather than
just FACE."
Here, the FACENAME expression (progn) evaluates to nil, so no properties or faces are being set - the only effect that caused by put-text-property.
One
problem with ("goodbye lenin" . (progn (put-text-property
(match-beginning 1) (match-end 1) 'display "GL") nil)) is that it is
just another way to write: ("goodbye lenin" progn (put-text-property
(match-beginning 1) (match-end 1) 'display "GL") nil), and these
equivalences can lead to ambiguities, which is why in this case you get
errors.
So the form (MATCHER . HIGHLIGHT), (match . FACENAME) and such
should only be used when HIGHLIGHT and FACENAME are not themselves
lists.
I was reading the source of rainbow-delimiters.el and then I was trying to replace the word rainbow-delimiters with RD using the code I got from somewhere that replaces the word lambda with its greek letter.
This is the code I came up with and it doesn't do what I want.
(defun my-simplify-prefix (prefix)
;; (interactive "sPrefix: ")
(interactive (list "rainbow-delimiters"))
(font-lock-add-keywords
nil `((,(rx-to-string `(group ,prefix))
(0 (progn (compose-region (match-beginning 1) (match-end 1)
"RD")
nil)))))
(font-lock-fontify-buffer))
What it does instead is it replaces rainbow-delimiters with one letter which is a combination of R and D.
(defun my-simplify-prefix (prefix)
(interactive "sPrefix: ")
(font-lock-add-keywords
nil `((,(rx-to-string `(group ,prefix))
(0 (progn (put-text-property (match-beginning 1) (match-end 1)
'display "RD")
nil)))))
(font-lock-fontify-buffer))