I have exactly the same issue in a different language (but similarly odd, from the sound of it).
Let's start with a single keyword to match: "write"
You can build up a solution starting with the regular expression provided in this answer (or actually one of its comments) about matching a word and all initial substrings:
w(?:r(?:i(?:t(?:e)?)?)?)?
From this basic pattern, you can generate regular expressions for whatever keyword you need to match. To introduce case insensitivity as described, a quick modification including both lower- and upper-case for each letter is available:
[wW](?:[rR](?:[iI](?:[tT](?:[eE])?)?)?)?
From here the addition of word boundaries \b
and some hideous escaping, we have a regular expression that's ready for deployment as a matcher in an element in font-lock-keywords:
(setq mumps-write-regex "\\b[wW]\\(?:[rR]\\(?:[iI]\\(?:[tT]\\(?:[eE]\\)?\\)?\\)?\\)?\\b")
(setq mumps-font-lock-keywords
`(
(,mumps-write-regex . 'font-lock-keyword-face)
;; ...other matchers and faces here...
))
This should match all of the scenarios described in the question, with case insensitivity, but requires manual generation of the regular expression.
As is explored in the question parts of this are also doable through regexp-opt
, which should generate optimized regular expressions to match sequences of words, if combined with a bit of lisp:
;; function to generate a list of initial substring
(defun initial-substrings (word)
(setq word (downcase word))
(cond
((> (length word) 0) (cons word (initial-substrings (substring word 0 -1) start)))
(t '())
))
;; generate a regular expression that matches that list
(setq mumps-write-regex (regexp-opt (initial-substrings "write") 'words))
⇒ "\\<\\(w\\(?:r\\(?:i\\(?:te?\\)?\\)?\\)?\\)\\>"
The generated regular expression closely resembles our initial case-sensitive regular expression, though it avoids the innermost (?:e)?
and uses probably more proper boundary matching with <
and >
.
To generalize this regexp-opt
approach to case-insensitive matches, it can get a little out of control because one needs to generate all case-permuted versions of all of the strings and substrings.
Instead, a better approach is to use this generated case-sensitive regular expression inside a matcher function -- as Lindydancer shows in another answer -- that sets case-fold-search
to t
just for that font-lock-keywords
element:
;; matcher function leveraging the generated regex with case insensitivity
(defun mumps-write-matcher (limit)
(let ((case-fold-search t))
(re-search-forward mumps-write-regex
limit 'no-error)))
Putting it all together, a regexp-opt
based approach that doesn't involve writing regular expressions manually is:
;; function to generate a list of initial substring
(defun initial-substrings (word)
(setq word (downcase word))
(cond
((> (length word) 0) (cons word (initial-substrings (substring word 0 -1) start)))
(t '())
))
;; generate a regular expression that matches that list
(setq mumps-write-regex (regexp-opt (initial-substrings "write") 'words))
;; matcher function leveraging the generated regex with case insensitivity
(defun mumps-write-matcher (limit)
(let ((case-fold-search t))
(re-search-forward mumps-write-regex
limit 'no-error)))
;; set the local font-lock-keywords variable
(setq mumps-font-lock-keywords
`(
(,'mumps-write-matcher . 'font-lock-keyword-face)
;; ...other matchers and faces here...
))
Neither of the approaches above cover the question's original requirements about accepting a list of different keywords, all of which should be matched together in a case-insensitive fashion.
At a minimum, you could craft a regex for each keyword and separate them with \|
in the regular expression in either approach. In the regexp-opt
aproach, using append
to join the lists returned by initial-substrings
prior to invoking regexp-opt
should work.
C-M-s
)? Is it for completion? Etc. As @Dan indicates, there can be different variables for controlling case-sensitivity, depending on the context.mumps-font-lock-keywords
to be used for highlighting. Some keywords are case-insensitive, some are case-sensitive. I really think the question as it is written would be the solution, but I provided some context to avoid the X/Y problem (which I seem to have fallen into in reverse...).