5

I am writing a simple syntax highlighting mode for a special file type. In the syntax, a line is only valid if it starts with a & character, otherwise it is treated as a comment (basically it is an 'inverse' comment).

Is there a way of implementing this?

I am by no means a lisp expert (only ever glue bits of code together to get what I want), so looking for answers has been difficult.

WJahn
  • 153
  • 5

2 Answers2

4

Clearly, using a normal syntax table doesn't work in this situation. Fortunately, Emacs allows you to set the syntax-table property on text in a buffer, which takes precedens over the normal syntax entries.

If you assign the variable syntax-propertize-function to a custom function, it will automatically be called whenever Emacs syntax highlight or indent the buffer.

In the example below, newline is statically set to end a comment. The function my-ampersand-propertize marks all lines not starting with an & as comments.

(defvar my-ampersand-syntax-table
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?\n ">   " table)
    table))

(defun my-ampersand-propertize (begin end)
  (save-excursion
    (goto-char begin)
    (while (< (point) end)
      (unless (eolp)
        (unless (eq (following-char) ?&)
          (put-text-property (point) (+ (point) 1)
                             'syntax-table (string-to-syntax "<"))))
      (forward-line))))

(define-derived-mode my-ampersand-mode prog-mode "Ampersand"
  "Major mode for ampersand."
  :syntax-table my-ampersand-syntax-table
  (set (make-local-variable 'syntax-propertize-function)
       'my-ampersand-propertize)
  (setq font-lock-defaults '(nil nil)))
Lindydancer
  • 6,150
  • 1
  • 15
  • 26
  • Thanks! That almost solved it. The only problem is that a line that is preceded by a blank line is now commented, even if it has a & character at the beginning. – WJahn Feb 16 '16 at 13:42
  • 1
    Add a check for blank lines. You should not add the properties if the line is blank. – Jordon Biondo Feb 16 '16 at 14:10
  • 1
    @JordonBiondo, thanks! I updated the answer to include an end-of-line check. – Lindydancer Feb 16 '16 at 14:45
1

Here's an alternative "quick-fix" solution using the built-in hi-lock package.

The solution is to put special Hi-Lock keywords in a comment at the top of the file (just like file-local variable comments starting with -*-). But first we need to configure hi-lock to automatically detect these special keywords without asking any questions.

Step 1: Minor config for hi-lock-mode

Add the below to your config.

(require 'hi-lock)
;; Don't ask before highlighting any Hi-Lock: pattern found in a file
;; Below, (lambda (pattern) t) simply always returns `t' regardless of
;; what the `pattern' input is.
(setq hi-lock-file-patterns-policy (lambda (pattern) t))
(global-hi-lock-mode 1) ; Enable the detection of the Hi-Lock keywords globally

Step 2: Put those keywords in your file

Provide the format for the regexp to match and the face to set the regexp groups to, using the font-lock-keywords format. You can learn more about this format by doing C-h v font-lock-keywords.

Here's an example that I tried out:

# Hi-lock: (("\\(^[^&\n].*$\\)" (0 '(:inherit font-lock-comment-face) prepend)))
# Hi-Lock: end

this is a comment
& this is not a comment

& this is not a comment even if there's a blank line above this line

# this is a comment

& not a comment
  & this is a comment too as the line is preceded with spaces

! blah
% blah
12312312

Outcome

Here is how the comment face is rendered (font face is based on the theme you use):

enter image description here

Addendum

Here is a gist of the font-lock-keywords format from my personal notes:

;; The Hi-Lock regexp forms are in the form of font lock keywords. Do
;; `C-h v font-lock-keywords' to learn more.

;; Hi-Lock: (("<REGEXP>" (<SUBEXP-0> '<FACE-0> [<OVERRIDE> [<LAXMATCH>]])
;;                       (<SUBEXP-1> '<FACE-1> [<OVERRIDE> [<LAXMATCH>]])
;;                       .. ))
;; Hi-Lock: end

;; OVERRIDE and LAXMATCH are flags.
;; If OVERRIDE is t, existing fontification can be overwritten.
;;   If `keep', only parts not already fontified are highlighted.
;;   If `prepend', existing fontification is merged with the new, in
;;     which the new fontification takes precedence.
;;   If `append', existing fontification is merged with the new, in
;;     which the existing fontification takes precedence.
;; If LAXMATCH is non-nil, that means don't signal an error if there is
;; no match for SUBEXP in REGEXP.

;; Examples of Hi-Lock patterns:

;; Highlight outshine headers in `shell-script-mode':
;; # Hi-lock: (("\\(^\\s< *\\**\\)\\(\\* *.*\\)" (1 'org-hide prepend) (2 '(:inherit org-level-1 :height 1.3 :weight bold :overline t :underline t) prepend)))

;; Highlight outshine headers in `emacs-lisp-mode':
;; ;; Hi-lock: (("\\(^;\\{3,\\}\\)\\( *.*\\)" (1 'org-hide prepend) (2 '(:inherit org-level-1 :height 1.3 :weight bold :overline t :underline t) prepend)))

As bonus, you can also set the hi-lock-file-patterns variable directly using .dir-locals.el files if you do not want to put the keywords in the file itself. But for that to work, you would need to put this in your emacs config:

(put 'hi-lock-file-patterns 'safe-local-variable 'identity)
Kaushal Modi
  • 25,651
  • 4
  • 80
  • 183