There does exist an optional argument override
that you can provide after facespec
which you can read about in Emacs manual: Search-based Fontification, but in this case it won’t matter, because after your keywords are applied there is separate logic in CC Mode that refontifies your keywords as types in certain contexts.
Using Lindydancer’s font-lock-studio you can go step-by-step through the fontification and see what it is overriding the keyword you defined. It is c-font-lock-declarations
which is defined in cc-fonts.el
.
I could think of three ways to overcome this:
Approach 1. Modifying c-not-decl-init-keywords
You can see around L1474 that the way CC Mode avoids fontifying keywords as types is by not proceeding any further if both of the following conditions are true:
- The text at point is fontified with
font-lock-keyword-face
(looking-at c-not-decl-init-keywords)
evaluates to t
.
condition 1 our keywords already pass when this function encounters them, so just we need to figure out how to make 2 evaluate to t
.
c-not-decl-init-keywords
is a regexp so we can just modify it to also match our keywords:
(font-lock-add-keywords
'c++-mode
'(("^\\(trn\\|def\\|mem\\)\\>" 0 'font-lock-keyword-face)))
(defun hack-c-not-decl-init-keywords ()
"Prepend `c-not-decl-init-keywords' with my own regexp."
(setq c-not-decl-init-keywords
(concat "\(^\(trn\|def\|mem\)\>\)\|"
c-not-decl-init-keywords)))
(add-hook 'c++-mode-hook #'hack-c-not-decl-init-keywords)
I just took the same regexp you used for the keywords, put it in a group \\( \\)
and added \\|
at the end, and prepended c-not-decl-init-keywords
with it. It is a buffer-local variable so I attached this to c++-mode-hook
.
Note that using this approach, when you add the keywords with font-lock-add-keywords
do not pass prepend
or append
to override, because this makes (get-text-property (point) 'face)
return a list, and even if font-lock-keyword-face
is on that list, condition 1 will not pass.
Approach 2. Defining your own CC Mode derived mode
If you looked at the place where c-not-decl-init-keywords
is defined in cc-langs.el
you will have noticed it is generated based on the c-lang-const
c-keywords
, which is generated from all the *-kwds
c-lang-const
s in cc-langs.el. Derived modes can add their own keywords to these.
Useful examples for how to make a derived mode:
I modified the example by Rorschach. Looked through cc-langs.el
to see which c-lang-const
I want to append to, and found c-simple-stmt-kwds
, so I appended to that one. You might want to use a different one depending on how the keywords are meant to be used; check the docstring under each one in cc-langs.el
.
(eval-when-compile
(require 'cc-langs)
(require 'cc-fonts))
(require 'cc-mode)
(eval-and-compile (c-add-language 'c++v-mode 'c++-mode))
(c-lang-defconst c-simple-stmt-kwds
c++v
(append
'("trn" "def" "mem")
(c-lang-const c-simple-stmt-kwds)
nil))
(defconst c++v-font-lock-keywords-1 (c-lang-const c-matchers-1 c++v))
(defconst c++v-font-lock-keywords-2 (c-lang-const c-matchers-2 c++v))
(defconst c++v-font-lock-keywords-3 (c-lang-const c-matchers-3 c++v))
(defvar c++v-font-lock-keywords (c-lang-const c-matchers-3 c++v))
(defun c++v-font-lock-keywords ()
(c-compose-keywords-list c++v-font-lock-keywords))
(defvar c++v-mode-syntax-table nil)
(define-derived-mode c++v-mode prog-mode "C++v"
:after-hook (c-update-modeline)
:syntax-table c++-mode-syntax-table
(c-initialize-cc-mode t)
(c-init-language-vars c++v-mode)
(c-common-init 'c++v-mode))
To test this you can save it in a file, e.g. in ~/c++v-mode.el
, eval (load "~/c++v-mode.el")
, then M-x c++v-mode.
This is the safest approach because CC Mode could update to break approaches 1 and 3, but it is unlikely to break 2 as that would break many other derived modes.
Approach 3. Filtering c-record-type-identifiers
(overly complicated, but I spent long enough on this that I am including it anyway in case it is of any use to someone in future)
what eventually causes our keywords to be fontified as types is a call to c-fontify-recorded-types-and-refs
, so another approach could be to advise this function.
This function applies fontification depending on the contents of the variable c-record-type-identifiers
.
To start with we can write advice that will message what the value of that variable is whenever c-fontify-recorded-types-and-refs
gets called:
(defun test-advice (orig-fn &rest args)
(message "@c-record-type-identifiers %S" c-record-type-identifiers)
(apply orig-fn args))
(advice-add 'c-fontify-recorded-types-and-refs :around
#'test-advice)
It starts off as t
then dotted conses representing the pointer position at the start and end of the types/refs to be fontified get added like ((2 . 5) . t)
. I noticed that it sometimes gets duplicate entries ((16 . 19) (16 . 19) . t)
.
This complicates things as this is an improper list and many list functions and even alist functions fail because of that t
at the end.
Around L1321 there is an example of how to remove things from this improper list. They defined a function specifically for this purpose called c-delq-from-dotted-list
.
So my idea was to keep track of the positions of our keywords in the buffer and filter them out of c-record-type-identifiers
.
(defvar custom-cpp-keywords-record nil
"Transient list of positions of custom cpp keywords in the buffer.
Used by `custom-cpp-keywords-advice'.")
(font-lock-add-keywords
'c++-mode
'(("^\(trn\|def\|mem\)\>" 0 ; regexp, subexp
(progn ; facespec {
(add-to-list 'custom-cpp-keywords-record
(cons (match-beginning 0) (match-end 0)))
'font-lock-keyword-face) ; }
prepend))) ; override (optional)
(defun custom-cpp-keywords-advice (orig-fn &rest args)
"Filter c-record-type-identifiers' before ORIG-FN is run. Removes positions in
custom-cpp-keywords-record' out of
c-record-type-identifiers'." (when (consp c-record-type-identifiers) ;; Properise c-record-type-identifiers so that we can iterate over it ;; without error (let ((records c-record-type-identifiers) (proper-records)) (while (consp records) (push (pop records) proper-records)) ;; Iterate over the records (dolist (record proper-records) ;; Remove record from
c-record-type-identifiers' if present in
;; `custom-cpp-keywords-record'
(let ((elt (and (consp record)
(member record custom-cpp-keywords-record)
(assq (car record) c-record-type-identifiers))))
(when elt
(setq c-record-type-identifiers
(c-delq-from-dotted-list
elt c-record-type-identifiers))))))
(apply orig-fn args)))
(defun reset-custom-cpp-keywords-record ()
"Set `custom-cpp-keywords-record' to nil."
(setq custom-cpp-keywords-record nil))
(advice-add 'c-fontify-recorded-types-and-refs :around
#'custom-cpp-keywords-advice)
(add-hook 'c++-mode-hook
(lambda () (add-hook 'font-lock-extend-region-functions
#'reset-custom-cpp-keywords-record)))
Notice that in the facespec we pass a function instead of a face name. This gets evaluated so we can run arbitrary code there, and the return value will be used as the face. This is used here to add the positions font-lock found to our list custom-cpp-keywords-record
.
In the advice we make a properised copy of c-record-type-identifiers
to make it possible to iterate over it without error (see this SE answer by Chris Jester-Young), then go through every record in the list, deleting it from c-record-type-identifiers
if it is also present in our custom-cpp-keywords-record
.
A function to reset the value of custom-cpp-keywords-record
is attached to font-lock-extend-region-functions
because our list needs to be reset before each time the buffer is fontified to ensure up-to-date values. Since the function we attach returns nil
it will not affect the region.
This is far from the elegant solution I was hoping to find, but perhaps it can serve as a starting point for a better one, or give a reader an idea for how to solve a different problem.
By the way, I am interested to know what language you are using.
c++-mode
by being derived from it. Check the value offont-lock-keywords
. I think you have a straight font-lock question, not a question about derived modes. If you instead try to add your entry toc++-mode
, as a test, I suspect you'll encounter the same problem. – Drew Mar 20 '21 at 00:32font-lock-keywords
. Found out that it does indeed fontify but only if noc++-mode
fontification has been triggered. So my question is rather how to I make my fontification higer priority. – Axel Bregnsbo Mar 20 '21 at 14:24