Replacing text in buffer view - emacs

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.

Related

Creating emacs mode: defining indentation

I'm writing a simple mode for a Lisp-like language, and am having trouble setting up indentation. I've been following the emacswiki mode tutorial.
However, I can't figure out how to adapt their example indentation to my needs because they don't do any form of counting.
Basically, I just need to add 2 spaces to my indentation count every time I see a { or (, even if there are multiple on the same line, and subtract 2 spaces when I see closures of the above. I'm new to elisp; how can I adapt their example to count braces and brackets?
For convenience, here is the code they are using (for a non-bracket language):
(defun wpdl-indent-line ()
"Indent current line as WPDL code"
(interactive)
(beginning-of-line)
(if (bobp) ; Check for rule 1
(indent-line-to 0)
(let ((not-indented t) cur-indent)
(if (looking-at "^[ \t]*END_") ; Check for rule 2
(progn
(save-excursion
(forward-line -1)
(setq cur-indent (- (current-indentation) default-tab-width)))
(if (< cur-indent 0)
(setq cur-indent 0)))
(save-excursion
(while not-indented
(forward-line -1)
(if (looking-at "^[ \t]*END_") ; Check for rule 3
(progn
(setq cur-indent (current-indentation))
(setq not-indented nil))
; Check for rule 4
(if (looking-at "^[ \t]*\\(PARTICIPANT\\|MODEL\\|APPLICATION\\|WORKFLOW\\|ACTIVITY\\|DATA\\|TOOL_LIST\\|TRANSITION\\)")
(progn
(setq cur-indent (+ (current-indentation) default-tab-width))
(setq not-indented nil))
(if (bobp) ; Check for rule 5
(setq not-indented nil)))))))
(if cur-indent
(indent-line-to cur-indent)
(indent-line-to 0))))) ; If we didn't see an indentation hint, then allow no indentation
How can I just implement lisp-like indentation (but also with curly braces)?
If you want something simple for a Lisp-style language, I suggest you start with (syntax-ppss) which returns the "parsing state" at point. The first element of that state is the current paren-nesting depth. While I used the word "paren", this doesn't really count parens but counts those chars which the syntax-table defines as paren-like, so if you set your syntax-table such that { and } are declared as paren-like, then those will also be counted.
So you could start with something like
(defun foo-indent-function ()
(save-excursion
(beginning-of-line)
(indent-line-to (* 2 (car (syntax-ppss))))))
Do not define this as interactive, since the way to use it is by adding
(set (make-local-variable 'indent-line-function) #'foo-indent-function)
in your major-mode function.
But maybe a better option is to simply do:
(require 'smie)
...
(define-derived-mode foo-mode "Foo"
...
(smie-setup nil #'ignore)
...)
This will use an indentation step of 4 (configured in smie-indent-basic).

How does a progn form in font-lock-keywords work?

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.

How can I revert 'fancy lambdas' after an edit

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)).

How to make a long word get displayed as a shorter word in an emacs buffer?

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))

Context-sensitive font-locking in emacs

Basically, I'm trying to syntax highlight the following piece of coffeescript code the way I want it. Explanation of the syntax of coffeescript functions can be found here.
nameHere = (tstamp, moo, boo) ->
...
The names tstamp, moo and boo should be colored pink (and nothing else, not the commas and not the brackets) because they are parameters to a lambda function.
highOrderFun ((x) -> x * x) someList
Here it is the first x that is the parameter. Parameters can have default arguments:
class Foo
meth: (msg = "Hello", bar = "foo") ->
....
Default arguments can be variables themselves:
defColor = "red"
print = (msg, color = defColor) ->
...
So msg and color above should be highlighted, but not defColor. An even trickier case is functions with default arguments that themselves are functions. I think that is to hard for emacs' font-lock to highlight correctly, but I'm including it anyway:
funTakingFuns = (f1 = ((a, b) -> a*b), f2 = ((c, d) -> c/d)) ->
...
This appears to be pretty complicated to achieve in emacs because you want the highlighting to be context sensitive. I've read up on the documentation on font-lock but haven't been able to figure it out.
I'd be grateful if someone could show me what to set font-lock-defaults to make it syntax highlight the way I want it.
Update Showing more coffeescript syntax examples.
font-lock-keywords allows function values in the MATCHER field:
where MATCHER can be either the regexp to search for, or the function name to
call to make the search (called with one argument, the limit of the search;
it should return non-nil, move point, and set match-data appropriately if
it succeeds; like re-search-forward would).
So we need to write a function that would search for the next function argument in the buffer.
Something like this:
(defun coffee-match-next-argument (limit)
(let ((start (point)))
;; Look for the arrow.
(when (re-search-forward ") *->" limit t)
;; Save the position of the closing paren.
(let ((stop (point)))
(goto-char (match-beginning 0))
;; Go to the opening paren.
(goto-char (nth 1 (syntax-ppss)))
;; If we're before our initial position, go forward.
;; We don't want to find the same symbols again.
(when (> start (point))
(goto-char start))
;; Look for the next symbol until the arrow.
(or (re-search-forward "\\((\\|,\\) *\\(\\(\\sw\\|_\\)+\\)" stop 'mv)
(coffee-match-next-argument limit))))))
And the setup, to use with existing coffee-mode:
(font-lock-add-keywords
'coffee-mode
'((coffee-match-next-argument 2 font-lock-variable-name-face)))
You can also use this in font-lock-defaults, of course.
This will likely use some other color than pink, but that's easy to change.
This is more kind of a hack, it's far from optimal (as I am not familiar at all with coffeescript), but perhaps with a little tweaking yourself, you can get this done.
All the ingredients are there.
The triggering of the commands/functions are based on the assumption that you use coffee-mode.
If you do not, this is not a big trouble, you'll just have to hook these things differently.
Put the following line in your .emacs:
(eval-after-load 'coffee '(load "/PATH/custom-coffee-font-lock.el"))
You can just save the below text as a file, and it will:
(1) Font lock when you trigger coffee-mode
(2) Font lock current line when you type the ">" as part of "->"
(3) Allow to font-lock the buffer by running M-x coffee-init-font-lock
;;;; custom-coffee-font-lock
;; Firstly, create a new font for this.
(make-face 'font-lock-coffeescript-face)
(set-face-foreground 'font-lock-coffeescript-face "pink")
;; Next, one function that should be ran after a file is identified as
;; a coffeescript file. It will do the font-locking you want on
;; the whole buffer. It is also possible to run it manually.
(defun coffee-init-font-lock ()
(interactive)
(save-excursion
(goto-char 1)
(while (search-forward-regexp "=.+->" nil t)
(search-backward-regexp "(")
(forward-char 1)
(add-text-properties
(point) (- (search-forward-regexp "," nil nil) 1)
'(font-lock-face font-lock-coffeescript-face))
(add-text-properties
(point) (- (search-forward-regexp "," nil nil) 1)
'(font-lock-face font-lock-coffeescript-face))
(add-text-properties
(point) (- (search-forward-regexp ")" nil nil) 1)
'(font-lock-face font-lock-coffeescript-face))
(move-end-of-line 1)))
)
;; This actually runs that function.
(coffee-init-font-lock)
;; This advice will be ran everytime you write something. It will check
;; whether "->" is before it, so when you type the final ">", it will
;; do the font locking for the current line (it also checks for your mode).
(defadvice self-insert-command (after coffee-font-lock activate)
(when (and (looking-back "->") (eq major-mode 'coffee-mode))
(save-excursion
(search-backward-regexp "(")
(forward-char 1)
(add-text-properties
(point) (- (search-forward-regexp "," nil nil) 1)
'(font-lock-face font-lock-coffeescript-face))
(add-text-properties
(point) (- (search-forward-regexp "," nil nil) 1)
'(font-lock-face font-lock-coffeescript-face))
(add-text-properties
(point) (- (search-forward-regexp ")" nil nil) 1)
'(font-lock-face font-lock-coffeescript-face))))
)
(provide 'custom-coffee-font-lock)
;;; custom-coffee-font-lock.el
If you have any requests, let me know. Like I said, I do not use CoffeeScript, so this might throw huge errors your way. At the very least it should help with some basic ideas.
Result: