Skip to content

Commit e158c62

Browse files
committed
Merge pull request #1050 from gracjan/pr-syntax-propertize
Use syntax-propertize-function
2 parents 45a3f88 + 66d831a commit e158c62

File tree

7 files changed

+274
-173
lines changed

7 files changed

+274
-173
lines changed

doc/anim/string-escape-highlight.gif

164 KB
Loading

doc/haskell-mode.texi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,14 @@ control @code{font-lock-mode}.
250250
@image{anim/font-lock}
251251
@end ifhtml
252252

253+
Syntax highlighting facilities parse strings and string escape sequences
254+
and are able to highlight unrecognized constructs.
255+
256+
@ifhtml
257+
@image{anim/string-escape-highlight}
258+
@end ifhtml
259+
260+
253261
@section Managing imports
254262

255263
There are a few functions for managing imports.

haskell-font-lock.el

Lines changed: 65 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,21 @@
2828

2929
(require 'cl-lib)
3030
(require 'haskell-compat)
31+
(require 'haskell-lexeme)
3132
(require 'font-lock)
3233

34+
;;;###autoload
35+
(defgroup haskell-appearance nil
36+
"Haskell Appearance."
37+
:group 'haskell)
38+
39+
3340
(defcustom haskell-font-lock-symbols nil
3441
"Display \\ and -> and such using symbols in fonts.
3542
3643
This may sound like a neat trick, but be extra careful: it changes the
3744
alignment and can thus lead to nasty surprises with regards to layout."
38-
:group 'haskell
45+
:group 'haskell-appearance
3946
:type 'boolean)
4047

4148
(defcustom haskell-font-lock-symbols-alist
@@ -77,7 +84,7 @@ PREDICATE if present is a function of one argument (the start position
7784
of the symbol) which should return non-nil if this mapping should
7885
be disabled at that position."
7986
:type '(alist string string)
80-
:group 'haskell)
87+
:group 'haskell-appearance)
8188

8289
(defun haskell-font-lock-dot-is-not-composition (start)
8390
"Return non-nil if the \".\" at START is not a composition operator.
@@ -107,28 +114,28 @@ This is the case if the \".\" is part of a \"forall <tvar> . <type>\"."
107114
108115
If a quasi quote is seen in Haskell code its contents will have
109116
font faces assigned as if respective mode was enabled."
110-
:group 'haskell
117+
:group 'haskell-appearance
111118
:type '(repeat (cons string symbol)))
112119

113120
;;;###autoload
114121
(defface haskell-keyword-face
115122
'((t :inherit font-lock-keyword-face))
116123
"Face used to highlight Haskell keywords."
117-
:group 'haskell)
124+
:group 'haskell-appearance)
118125

119126
;;;###autoload
120127
(defface haskell-constructor-face
121128
'((t :inherit font-lock-type-face))
122129
"Face used to highlight Haskell constructors."
123-
:group 'haskell)
130+
:group 'haskell-appearance)
124131

125132
;; This used to be `font-lock-variable-name-face' but it doesn't result in
126133
;; a highlighting that's consistent with other modes (it's mostly used
127134
;; for function defintions).
128135
(defface haskell-definition-face
129136
'((t :inherit font-lock-function-name-face))
130137
"Face used to highlight Haskell definitions."
131-
:group 'haskell)
138+
:group 'haskell-appearance)
132139

133140
;; This is probably just wrong, but it used to use
134141
;; `font-lock-function-name-face' with a result that was not consistent with
@@ -137,28 +144,28 @@ font faces assigned as if respective mode was enabled."
137144
(defface haskell-operator-face
138145
'((t :inherit font-lock-variable-name-face))
139146
"Face used to highlight Haskell operators."
140-
:group 'haskell)
147+
:group 'haskell-appearance)
141148

142149
;;;###autoload
143150
(defface haskell-pragma-face
144151
'((t :inherit font-lock-preprocessor-face))
145152
"Face used to highlight Haskell pragmas."
146-
:group 'haskell)
153+
:group 'haskell-appearance)
147154

148155
;;;###autoload
149156
(defface haskell-literate-comment-face
150157
'((t :inherit font-lock-doc-face))
151158
"Face with which to fontify literate comments.
152159
Inherit from `default' to avoid fontification of them."
153-
:group 'haskell)
160+
:group 'haskell-appearance)
154161

155162
(defface haskell-quasi-quote-face
156163
'((t :inherit font-lock-string-face))
157164
"Generic face for quasiquotes.
158165
159166
Some quote types are fontified according to other mode defined in
160167
`haskell-font-lock-quasi-quote-modes'."
161-
:group 'haskell)
168+
:group 'haskell-appearance)
162169

163170
(defun haskell-font-lock-compose-symbol (alist)
164171
"Compose a sequence of ascii chars into a symbol.
@@ -199,13 +206,13 @@ Regexp match data 0 points to the chars."
199206
;; no face. So force evaluation by using `keep'.
200207
keep)))))
201208

202-
;; The font lock regular expressions.
203-
(defun haskell-font-lock-keywords-create (literate)
204-
"Create fontification definitions for Haskell scripts.
205-
Returns keywords suitable for `font-lock-keywords'."
209+
(defun haskell-font-lock-keywords ()
210+
;; this has to be a function because it depends on global value of
211+
;; `haskell-font-lock-symbols'
212+
"Generate font lock eywords."
206213
(let* (;; Bird-style literate scripts start a line of code with
207214
;; "^>", otherwise a line of code starts with "^".
208-
(line-prefix (if (eq literate 'bird) "^> ?" "^"))
215+
(line-prefix "^\\(?:> ?\\)?")
209216

210217
(varid "\\b[[:lower:]_][[:alnum:]'_]*\\b")
211218
;; We allow ' preceding conids because of DataKinds/PolyKinds
@@ -233,8 +240,7 @@ Returns keywords suitable for `font-lock-keywords'."
233240
(topdecl-var
234241
(concat line-prefix "\\(" varid "\\(?:\\s-*,\\s-*" varid "\\)*" "\\)\\s-*"
235242
;; optionally allow for a single newline after identifier
236-
;; NOTE: not supported for bird-style .lhs files
237-
(if (eq literate 'bird) nil "\\([\n]\\s-+\\)?")
243+
"\\([\n]\\s-+\\)?"
238244
;; A toplevel declaration can be followed by a definition
239245
;; (=), a type (::) or (∷), a guard, or a pattern which can
240246
;; either be a variable, a constructor, a parenthesized
@@ -323,90 +329,6 @@ Returns keywords suitable for `font-lock-keywords'."
323329
'haskell-operator-face))))
324330
keywords))
325331

326-
(defconst haskell-basic-syntactic-keywords
327-
'(;; Character constants (since apostrophe can't have string syntax).
328-
;; Beware: do not match something like 's-}' or '\n"+' since the first '
329-
;; might be inside a comment or a string.
330-
;; This still gets fooled with "'"'"'"'"'"', but ... oh well.
331-
("\\Sw\\('\\)\\([^\\'\n]\\|\\\\.[^\\'\n \"}]*\\)\\('\\)" (1 "\"") (3 "\""))
332-
;; Deal with instances of `--' which don't form a comment
333-
("[!#$%&*+./:<=>?@^|~\\]*--[!#$%&*+./:<=>?@^|~\\-]*" (0 (cond ((or (nth 3 (syntax-ppss)) (numberp (nth 4 (syntax-ppss))))
334-
;; There are no such instances inside
335-
;; nestable comments or strings
336-
nil)
337-
((string-match "\\`-*\\'" (match-string 0))
338-
;; Sequence of hyphens. Do nothing in
339-
;; case of things like `{---'.
340-
nil)
341-
((string-match "\\`[^-]+--.*" (match-string 0))
342-
;; Extra characters before comment starts
343-
".")
344-
(t ".")))) ; other symbol sequence
345-
346-
;; Implement Haskell Report 'escape' and 'gap' rules. Backslash
347-
;; inside of a string is escaping unless it is preceeded by
348-
;; another escaping backslash. There can be whitespace between
349-
;; those two.
350-
;;
351-
;; Backslashes outside of string never escape.
352-
;;
353-
;; Note that (> 0 (skip-syntax-backward ".")) this skips over *escaping*
354-
;; backslashes only.
355-
("\\\\" (0 (when (save-excursion (and (nth 3 (syntax-ppss))
356-
(goto-char (match-beginning 0))
357-
(skip-syntax-backward "->")
358-
(or (not (eq ?\\ (char-before)))
359-
(> 0 (skip-syntax-backward ".")))))
360-
"\\")))
361-
362-
;; QuasiQuotes syntax: [quoter| string |], quoter is unqualified
363-
;; name, no spaces, string is arbitrary (including newlines),
364-
;; finishes at the first occurence of |], no escaping is provided.
365-
;;
366-
;; The quoter cannot be "e", "t", "d", or "p", since those overlap
367-
;; with Template Haskell quotations.
368-
;;
369-
;; QuasiQuotes opens only when outside of a string or a comment
370-
;; and closes only when inside a quasiquote.
371-
;;
372-
;; (syntax-ppss) returns list with two interesting elements:
373-
;; nth 3. non-nil if inside a string. (it is the character that will
374-
;; terminate the string, or t if the string should be terminated
375-
;; by a generic string delimiter.)
376-
;; nth 4. nil if outside a comment, t if inside a non-nestable comment,
377-
;; else an integer (the current comment nesting).
378-
;;
379-
;; Note also that we need to do in in a single pass, hence a regex
380-
;; that covers both the opening and the ending of a quasiquote.
381-
382-
("\\(\\[[[:alnum:]]+\\)?\\(|\\)\\(]\\)?"
383-
(2 (save-excursion
384-
(goto-char (match-beginning 0))
385-
(if (eq ?\[ (char-after))
386-
;; opening case
387-
(unless (or (nth 3 (syntax-ppss))
388-
(nth 4 (syntax-ppss))
389-
(member (match-string 1)
390-
'("[e" "[t" "[d" "[p")))
391-
"\"")
392-
;; closing case
393-
(when (and (eq ?| (nth 3 (syntax-ppss)))
394-
(equal "]" (match-string 3))
395-
)
396-
"\"")))))
397-
))
398-
399-
(defconst haskell-bird-syntactic-keywords
400-
(cons '("^[^\n>]" (0 "<"))
401-
haskell-basic-syntactic-keywords))
402-
403-
(defconst haskell-latex-syntactic-keywords
404-
(append
405-
'(("^\\\\begin{code}\\(\n\\)" 1 "!")
406-
;; Note: buffer is widened during font-locking.
407-
("\\`\\(.\\|\n\\)" (1 "!")) ; start comment at buffer start
408-
("^\\(\\\\\\)end{code}$" 1 "!"))
409-
haskell-basic-syntactic-keywords))
410332

411333
(defun haskell-font-lock-fontify-block (lang-mode start end)
412334
"Fontify a block as LANG-MODE."
@@ -455,20 +377,51 @@ Returns keywords suitable for `font-lock-keywords'."
455377
nil)
456378
;; fontify normally as string because lang-mode is not present
457379
'haskell-quasi-quote-face))
458-
'font-lock-string-face))
459-
;; Else comment. If it's from syntax table, use default face.
460-
((or (eq 'syntax-table (nth 7 state))
461-
(and (eq haskell-literate 'bird)
462-
(memq (char-before (nth 8 state)) '(nil ?\n))))
380+
(save-excursion
381+
(let
382+
((state2
383+
(parse-partial-sexp (point) (point-max) nil nil state
384+
'syntax-table))
385+
(end-of-string (point)))
386+
387+
(put-text-property (nth 8 state) (point)
388+
'face 'font-lock-string-face)
389+
390+
391+
(if (or (equal t (nth 3 state)) (nth 3 state2))
392+
;; This is an unterminated string constant, use warning
393+
;; face for the opening quote.
394+
(put-text-property (nth 8 state) (1+ (nth 8 state))
395+
'face 'font-lock-warning-face))
396+
397+
(goto-char (1+ (nth 8 state)))
398+
(while (re-search-forward "\\\\" end-of-string t)
399+
400+
(goto-char (1- (point)))
401+
402+
(if (looking-at haskell-lexeme-string-literal-inside-item)
403+
(goto-char (match-end 0))
404+
405+
;; We are looking at an unacceptable escape
406+
;; sequence. Use warning face to highlight that.
407+
(put-text-property (point) (1+ (point))
408+
'face 'font-lock-warning-face)
409+
(goto-char (1+ (point)))))))
410+
;; must return nil here so that it is not fontified again as string
411+
nil))
412+
;; Detect literate comment lines starting with syntax class '<'
413+
((save-excursion
414+
(goto-char (nth 8 state))
415+
(equal (string-to-syntax "<") (syntax-after (point))))
463416
'haskell-literate-comment-face)
464417
;; Detect pragmas. A pragma is enclosed in special comment
465418
;; delimeters {-# .. #-}.
466419
((save-excursion
467420
(goto-char (nth 8 state))
468-
(and (looking-at "{-#")
421+
(and (looking-at-p "{-#")
469422
(forward-comment 1)
470423
(goto-char (- (point) 3))
471-
(looking-at "#-}")))
424+
(looking-at-p "#-}")))
472425
'haskell-pragma-face)
473426
;; Haddock comment start with either "-- [|^*$]" or "{- ?[|^*$]"
474427
;; (note space optional for nested comments and mandatory for
@@ -484,8 +437,8 @@ Returns keywords suitable for `font-lock-keywords'."
484437
;; comments newline is outside of comment.
485438
((save-excursion
486439
(goto-char (nth 8 state))
487-
(or (looking-at "\\(?:{- ?\\|-- \\)[|^*$]")
488-
(and (looking-at "--") ; are we at double dash comment
440+
(or (looking-at-p "\\(?:{- ?\\|-- \\)[|^*$]")
441+
(and (looking-at-p "--") ; are we at double dash comment
489442
(forward-line -1) ; this is nil on first line
490443
(eq (get-text-property (line-end-position) 'face)
491444
'font-lock-doc-face) ; is a doc face
@@ -495,40 +448,11 @@ Returns keywords suitable for `font-lock-keywords'."
495448
'font-lock-doc-face)
496449
(t 'font-lock-comment-face)))
497450

498-
(defconst haskell-font-lock-keywords
499-
(haskell-font-lock-keywords-create nil)
500-
"Font lock definitions for non-literate Haskell.")
501-
502-
(defconst haskell-font-lock-bird-literate-keywords
503-
(haskell-font-lock-keywords-create 'bird)
504-
"Font lock definitions for Bird-style literate Haskell.")
505-
506-
(defconst haskell-font-lock-latex-literate-keywords
507-
(haskell-font-lock-keywords-create 'latex)
508-
"Font lock definitions for LaTeX-style literate Haskell.")
509-
510-
;;;###autoload
511-
(defun haskell-font-lock-choose-keywords ()
512-
(let ((literate (if (boundp 'haskell-literate) haskell-literate)))
513-
(cl-case literate
514-
(bird haskell-font-lock-bird-literate-keywords)
515-
((latex tex) haskell-font-lock-latex-literate-keywords)
516-
(t haskell-font-lock-keywords))))
517-
518-
(defun haskell-font-lock-choose-syntactic-keywords ()
519-
(let ((literate (if (boundp 'haskell-literate) haskell-literate)))
520-
(cl-case literate
521-
(bird haskell-bird-syntactic-keywords)
522-
((latex tex) haskell-latex-syntactic-keywords)
523-
(t haskell-basic-syntactic-keywords))))
524-
525451
(defun haskell-font-lock-defaults-create ()
526452
"Locally set `font-lock-defaults' for Haskell."
527453
(set (make-local-variable 'font-lock-defaults)
528-
'(haskell-font-lock-choose-keywords
529-
nil nil ((?\' . "w") (?_ . "w")) nil
530-
(font-lock-syntactic-keywords
531-
. haskell-font-lock-choose-syntactic-keywords)
454+
'((haskell-font-lock-keywords)
455+
nil nil nil nil
532456
(font-lock-syntactic-face-function
533457
. haskell-syntactic-face-function)
534458
;; Get help from font-lock-syntactic-keywords.

0 commit comments

Comments
 (0)