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.

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)))
  • 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. – user2426172 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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.