712 lines
30 KiB
EmacsLisp
712 lines
30 KiB
EmacsLisp
|
;;; haskell-font-lock.el --- Font locking module for Haskell Mode -*- lexical-binding: t -*-
|
|||
|
|
|||
|
;; Copyright 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
|
|||
|
;; Copyright 1997-1998 Graeme E Moss, and Tommy Thorn
|
|||
|
|
|||
|
;; Author: 1997-1998 Graeme E Moss <gem@cs.york.ac.uk>
|
|||
|
;; 1997-1998 Tommy Thorn <thorn@irisa.fr>
|
|||
|
;; 2003 Dave Love <fx@gnu.org>
|
|||
|
;; Keywords: faces files Haskell
|
|||
|
|
|||
|
;; This file is not part of GNU Emacs.
|
|||
|
|
|||
|
;; This file is free software; you can redistribute it and/or modify
|
|||
|
;; it under the terms of the GNU General Public License as published by
|
|||
|
;; the Free Software Foundation; either version 3, or (at your option)
|
|||
|
;; any later version.
|
|||
|
|
|||
|
;; This file is distributed in the hope that it will be useful,
|
|||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
;; GNU General Public License for more details.
|
|||
|
|
|||
|
;; You should have received a copy of the GNU General Public License
|
|||
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
|
|||
|
|
|||
|
;;; Code:
|
|||
|
|
|||
|
(require 'cl-lib)
|
|||
|
(require 'haskell-compat)
|
|||
|
(require 'haskell-lexeme)
|
|||
|
(require 'font-lock)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defgroup haskell-appearance nil
|
|||
|
"Haskell Appearance."
|
|||
|
:group 'haskell)
|
|||
|
|
|||
|
|
|||
|
(defcustom haskell-font-lock-symbols nil
|
|||
|
"Display \\ and -> and such using symbols in fonts.
|
|||
|
|
|||
|
This may sound like a neat trick, but be extra careful: it changes the
|
|||
|
alignment and can thus lead to nasty surprises with regards to layout."
|
|||
|
:group 'haskell-appearance
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom haskell-font-lock-symbols-alist
|
|||
|
'(("\\" . "λ")
|
|||
|
("not" . "¬")
|
|||
|
("->" . "→")
|
|||
|
("<-" . "←")
|
|||
|
("=>" . "⇒")
|
|||
|
("()" . "∅")
|
|||
|
("==" . "≡")
|
|||
|
("/=" . "≢")
|
|||
|
(">=" . "≥")
|
|||
|
("<=" . "≤")
|
|||
|
("!!" . "‼")
|
|||
|
("&&" . "∧")
|
|||
|
("||" . "∨")
|
|||
|
("sqrt" . "√")
|
|||
|
("undefined" . "⊥")
|
|||
|
("pi" . "π")
|
|||
|
("~>" . "⇝") ;; Omega language
|
|||
|
;; ("~>" "↝") ;; less desirable
|
|||
|
("-<" . "↢") ;; Paterson's arrow syntax
|
|||
|
;; ("-<" "⤙") ;; nicer but uncommon
|
|||
|
("::" . "∷")
|
|||
|
("." "∘" ; "○"
|
|||
|
;; Need a predicate here to distinguish the . used by
|
|||
|
;; forall <foo> . <bar>.
|
|||
|
haskell-font-lock-dot-is-not-composition)
|
|||
|
("forall" . "∀"))
|
|||
|
"Alist mapping Haskell symbols to chars.
|
|||
|
|
|||
|
Each element has the form (STRING . COMPONENTS) or (STRING
|
|||
|
COMPONENTS PREDICATE).
|
|||
|
|
|||
|
STRING is the Haskell symbol.
|
|||
|
COMPONENTS is a representation specification suitable as an argument to
|
|||
|
`compose-region'.
|
|||
|
PREDICATE if present is a function of one argument (the start position
|
|||
|
of the symbol) which should return non-nil if this mapping should
|
|||
|
be disabled at that position."
|
|||
|
:type '(alist string string)
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
(defcustom haskell-font-lock-keywords
|
|||
|
;; `as', `hiding', and `qualified' are part of the import
|
|||
|
;; spec syntax, but they are not reserved.
|
|||
|
;; `_' can go in here since it has temporary word syntax.
|
|||
|
'("case" "class" "data" "default" "deriving" "do"
|
|||
|
"else" "if" "import" "in" "infix" "infixl"
|
|||
|
"infixr" "instance" "let" "module" "mdo" "newtype" "of"
|
|||
|
"rec" "pattern" "proc" "signature" "then" "type" "where" "_")
|
|||
|
"Identifiers treated as reserved keywords in Haskell."
|
|||
|
:group 'haskell-appearance
|
|||
|
:type '(repeat string))
|
|||
|
|
|||
|
|
|||
|
(defun haskell-font-lock-dot-is-not-composition (start)
|
|||
|
"Return non-nil if the \".\" at START is not a composition operator.
|
|||
|
This is the case if the \".\" is part of a \"forall <tvar> . <type>\"."
|
|||
|
(save-excursion
|
|||
|
(goto-char start)
|
|||
|
(or (re-search-backward "\\<forall\\>[^.\"]*\\="
|
|||
|
(line-beginning-position) t)
|
|||
|
(not (or
|
|||
|
(string= " " (string (char-after start)))
|
|||
|
(null (char-before start))
|
|||
|
(string= " " (string (char-before start))))))))
|
|||
|
|
|||
|
(defvar haskell-yesod-parse-routes-mode-keywords
|
|||
|
'(("^\\([^ \t\n]+\\)\\(?:[ \t]+\\([^ \t\n]+\\)\\)?"
|
|||
|
(1 'font-lock-string-face)
|
|||
|
(2 'haskell-constructor-face nil lax))))
|
|||
|
|
|||
|
(define-derived-mode haskell-yesod-parse-routes-mode text-mode "Yesod parseRoutes mode"
|
|||
|
"Mode for parseRoutes from Yesod."
|
|||
|
(setq-local font-lock-defaults '(haskell-yesod-parse-routes-mode-keywords t t nil nil)))
|
|||
|
|
|||
|
(defcustom haskell-font-lock-quasi-quote-modes
|
|||
|
`(("hsx" . xml-mode)
|
|||
|
("hamlet" . shakespeare-hamlet-mode)
|
|||
|
("shamlet" . shakespeare-hamlet-mode)
|
|||
|
("whamlet" . shakespeare-hamlet-mode)
|
|||
|
("xmlQQ" . xml-mode)
|
|||
|
("xml" . xml-mode)
|
|||
|
("cmd" . shell-mode)
|
|||
|
("sh_" . shell-mode)
|
|||
|
("jmacro" . javascript-mode)
|
|||
|
("jmacroE" . javascript-mode)
|
|||
|
("r" . ess-mode)
|
|||
|
("rChan" . ess-mode)
|
|||
|
("sql" . sql-mode)
|
|||
|
("json" . json-mode)
|
|||
|
("aesonQQ" . json-mode)
|
|||
|
("parseRoutes" . haskell-yesod-parse-routes-mode))
|
|||
|
"Mapping from quasi quoter token to fontification mode.
|
|||
|
|
|||
|
If a quasi quote is seen in Haskell code its contents will have
|
|||
|
font faces assigned as if respective mode was enabled."
|
|||
|
:group 'haskell-appearance
|
|||
|
:type '(repeat (cons string symbol)))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-keyword-face
|
|||
|
'((t :inherit font-lock-keyword-face))
|
|||
|
"Face used to highlight Haskell keywords."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-type-face
|
|||
|
'((t :inherit font-lock-type-face))
|
|||
|
"Face used to highlight Haskell types"
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-constructor-face
|
|||
|
'((t :inherit font-lock-type-face))
|
|||
|
"Face used to highlight Haskell constructors."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;; This used to be `font-lock-variable-name-face' but it doesn't result in
|
|||
|
;; a highlighting that's consistent with other modes (it's mostly used
|
|||
|
;; for function defintions).
|
|||
|
(defface haskell-definition-face
|
|||
|
'((t :inherit font-lock-function-name-face))
|
|||
|
"Face used to highlight Haskell definitions."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;; This is probably just wrong, but it used to use
|
|||
|
;; `font-lock-function-name-face' with a result that was not consistent with
|
|||
|
;; other major modes, so I just exchanged with `haskell-definition-face'.
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-operator-face
|
|||
|
'((t :inherit font-lock-variable-name-face))
|
|||
|
"Face used to highlight Haskell operators."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-pragma-face
|
|||
|
'((t :inherit font-lock-preprocessor-face))
|
|||
|
"Face used to highlight Haskell pragmas ({-# ... #-})."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-liquid-haskell-annotation-face
|
|||
|
'((t :inherit haskell-pragma-face))
|
|||
|
"Face used to highlight LiquidHaskell annotations ({-@ ... @-})."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defface haskell-literate-comment-face
|
|||
|
'((t :inherit font-lock-doc-face))
|
|||
|
"Face with which to fontify literate comments.
|
|||
|
Inherit from `default' to avoid fontification of them."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
(defface haskell-quasi-quote-face
|
|||
|
'((t :inherit font-lock-string-face))
|
|||
|
"Generic face for quasiquotes.
|
|||
|
|
|||
|
Some quote types are fontified according to other mode defined in
|
|||
|
`haskell-font-lock-quasi-quote-modes'."
|
|||
|
:group 'haskell-appearance)
|
|||
|
|
|||
|
(defun haskell-font-lock-compose-symbol (alist)
|
|||
|
"Compose a sequence of ascii chars into a symbol.
|
|||
|
Regexp match data 0 points to the chars."
|
|||
|
;; Check that the chars should really be composed into a symbol.
|
|||
|
(let* ((start (match-beginning 0))
|
|||
|
(end (match-end 0))
|
|||
|
(syntaxes (cond
|
|||
|
((eq (char-syntax (char-after start)) ?w) '(?w))
|
|||
|
((eq (char-syntax (char-after start)) ?.) '(?.))
|
|||
|
;; Special case for the . used for qualified names.
|
|||
|
((and (eq (char-after start) ?\.) (= end (1+ start)))
|
|||
|
'(?_ ?\\ ?w))
|
|||
|
(t '(?_ ?\\))))
|
|||
|
sym-data)
|
|||
|
(if (or (memq (char-syntax (or (char-before start) ?\ )) syntaxes)
|
|||
|
(memq (char-syntax (or (char-after end) ?\ )) syntaxes)
|
|||
|
(or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4))
|
|||
|
(and (consp (setq sym-data (cdr (assoc (match-string 0) alist))))
|
|||
|
(let ((pred (cadr sym-data)))
|
|||
|
(setq sym-data (car sym-data))
|
|||
|
(funcall pred start))))
|
|||
|
;; No composition for you. Let's actually remove any composition
|
|||
|
;; we may have added earlier and which is now incorrect.
|
|||
|
(remove-text-properties start end '(composition))
|
|||
|
;; That's a symbol alright, so add the composition.
|
|||
|
(compose-region start end sym-data)))
|
|||
|
;; Return nil because we're not adding any face property.
|
|||
|
nil)
|
|||
|
|
|||
|
(defun haskell-font-lock-symbols-keywords ()
|
|||
|
(when (and haskell-font-lock-symbols
|
|||
|
haskell-font-lock-symbols-alist)
|
|||
|
`((,(regexp-opt (mapcar 'car haskell-font-lock-symbols-alist) t)
|
|||
|
(0 (haskell-font-lock-compose-symbol ',haskell-font-lock-symbols-alist)
|
|||
|
;; In Emacs-21, if the `override' field is nil, the face
|
|||
|
;; expressions is only evaluated if the text has currently
|
|||
|
;; no face. So force evaluation by using `keep'.
|
|||
|
keep)))))
|
|||
|
|
|||
|
(defun haskell-font-lock--forward-type (&optional ignore)
|
|||
|
"Find where does this type declaration end.
|
|||
|
|
|||
|
Moves the point to the end of type declaration. It should be
|
|||
|
invoked with point just after one of type introducing keywords
|
|||
|
like ::, class, instance, data, newtype, type."
|
|||
|
(interactive)
|
|||
|
(let ((cont t)
|
|||
|
(end (point))
|
|||
|
(token nil)
|
|||
|
;; we are starting right after ::
|
|||
|
(last-token-was-operator t)
|
|||
|
(last-token-was-newline nil)
|
|||
|
(open-parens 0))
|
|||
|
(while cont
|
|||
|
(setq token (haskell-lexeme-looking-at-token 'newline))
|
|||
|
|
|||
|
(cond
|
|||
|
((null token)
|
|||
|
(setq cont nil))
|
|||
|
((member token '(newline))
|
|||
|
(setq last-token-was-newline (not last-token-was-operator))
|
|||
|
(setq end (match-end 0))
|
|||
|
(goto-char (match-end 0)))
|
|||
|
((member (match-string-no-properties 0)
|
|||
|
'(")" "]" "}"))
|
|||
|
(setq open-parens (1- open-parens))
|
|||
|
(if (< open-parens 0)
|
|||
|
;; unmatched closing parenthesis closes type declaration
|
|||
|
(setq cont nil)
|
|||
|
(setq end (match-end 0))
|
|||
|
(goto-char end))
|
|||
|
(setq last-token-was-newline nil))
|
|||
|
((and (member (match-string-no-properties 0)
|
|||
|
'("," ";" "|"))
|
|||
|
(not (member (match-string-no-properties 0) ignore)))
|
|||
|
(if (equal 0 open-parens)
|
|||
|
(setq cont nil)
|
|||
|
(setq last-token-was-operator t)
|
|||
|
(setq end (match-end 0))
|
|||
|
(goto-char end))
|
|||
|
(setq last-token-was-newline nil))
|
|||
|
((and (or (member (match-string-no-properties 0)
|
|||
|
'("<-" "=" "←"))
|
|||
|
(member (match-string-no-properties 0) haskell-font-lock-keywords))
|
|||
|
(not (member (match-string-no-properties 0) ignore)))
|
|||
|
(setq cont nil)
|
|||
|
(setq last-token-was-newline nil))
|
|||
|
((member (match-string-no-properties 0)
|
|||
|
'("(" "[" "{"))
|
|||
|
(if last-token-was-newline
|
|||
|
(setq cont nil)
|
|||
|
(setq open-parens (1+ open-parens))
|
|||
|
(setq end (match-end 0))
|
|||
|
(goto-char end)
|
|||
|
(setq last-token-was-newline nil)))
|
|||
|
((member token '(qsymid char string number template-haskell-quote template-haskell-quasi-quote))
|
|||
|
(setq last-token-was-operator (member (haskell-lexeme-classify-by-first-char (char-after (match-beginning 1)))
|
|||
|
'(varsym consym)))
|
|||
|
(if (and (not last-token-was-operator) last-token-was-newline)
|
|||
|
(setq cont nil)
|
|||
|
|
|||
|
(goto-char (match-end 0))
|
|||
|
(setq end (point)))
|
|||
|
(setq last-token-was-newline nil))
|
|||
|
((member token '(comment nested-comment literate-comment))
|
|||
|
(goto-char (match-end 0))
|
|||
|
(setq end (point)))
|
|||
|
(t
|
|||
|
(goto-char (match-end 0))
|
|||
|
(setq end (point))
|
|||
|
(setq last-token-was-newline nil))))
|
|||
|
(goto-char end)))
|
|||
|
|
|||
|
|
|||
|
(defun haskell-font-lock--select-face-on-type-or-constructor ()
|
|||
|
"Private function used to select either type or constructor face
|
|||
|
on an uppercase identifier."
|
|||
|
(cl-case (haskell-lexeme-classify-by-first-char (char-after (match-beginning 1)))
|
|||
|
(varid (let ((word (match-string-no-properties 0)))
|
|||
|
(cond
|
|||
|
((member word haskell-font-lock-keywords)
|
|||
|
;; Note: keywords parse as keywords only when not qualified.
|
|||
|
;; GHC parses Control.let as a single but illegal lexeme.
|
|||
|
(when (member word '("class" "instance" "type" "data" "newtype"))
|
|||
|
(save-excursion
|
|||
|
(goto-char (match-end 0))
|
|||
|
(save-match-data
|
|||
|
(haskell-font-lock--forward-type
|
|||
|
(cond
|
|||
|
((member word '("class" "instance"))
|
|||
|
'("|"))
|
|||
|
((member word '("type"))
|
|||
|
;; Need to support 'type instance'
|
|||
|
'("=" "instance")))))
|
|||
|
(add-text-properties (match-end 0) (point) '(font-lock-multiline t haskell-type t))))
|
|||
|
'haskell-keyword-face)
|
|||
|
((member word '("forall"))
|
|||
|
(when (get-text-property (match-beginning 0) 'haskell-type)
|
|||
|
'haskell-keyword-face)))))
|
|||
|
(conid (if (get-text-property (match-beginning 0) 'haskell-type)
|
|||
|
'haskell-type-face
|
|||
|
'haskell-constructor-face))
|
|||
|
(varsym (unless (and (member (match-string 0) '("-" "+" "."))
|
|||
|
(equal (string-to-syntax "w") (syntax-after (match-beginning 0))))
|
|||
|
;; We need to protect against the case of
|
|||
|
;; plus, minus or dot inside a floating
|
|||
|
;; point number.
|
|||
|
'haskell-operator-face))
|
|||
|
(consym (if (not (member (match-string 1) '("::" "∷")))
|
|||
|
(if (get-text-property (match-beginning 0) 'haskell-type)
|
|||
|
'haskell-type-face
|
|||
|
'haskell-constructor-face)
|
|||
|
(save-excursion
|
|||
|
(goto-char (match-end 0))
|
|||
|
(save-match-data
|
|||
|
(haskell-font-lock--forward-type))
|
|||
|
(add-text-properties (match-end 0) (point) '(font-lock-multiline t haskell-type t)))
|
|||
|
'haskell-operator-face))))
|
|||
|
|
|||
|
(defun haskell-font-lock--put-face-on-type-or-constructor ()
|
|||
|
"Private function used to put either type or constructor face
|
|||
|
on an uppercase identifier."
|
|||
|
(let ((face (haskell-font-lock--select-face-on-type-or-constructor)))
|
|||
|
(when (and face
|
|||
|
(not (text-property-not-all (match-beginning 0) (match-end 0) 'face nil)))
|
|||
|
(put-text-property (match-beginning 0) (match-end 0) 'face face))))
|
|||
|
|
|||
|
|
|||
|
(defun haskell-font-lock-keywords ()
|
|||
|
;; this has to be a function because it depends on global value of
|
|||
|
;; `haskell-font-lock-symbols'
|
|||
|
"Generate font lock eywords."
|
|||
|
(let* (;; Bird-style literate scripts start a line of code with
|
|||
|
;; "^>", otherwise a line of code starts with "^".
|
|||
|
(line-prefix "^\\(?:> ?\\)?")
|
|||
|
|
|||
|
(varid "[[:lower:]_][[:alnum:]'_]*")
|
|||
|
;; We allow ' preceding conids because of DataKinds/PolyKinds
|
|||
|
(conid "'?[[:upper:]][[:alnum:]'_]*")
|
|||
|
(sym "\\s.+")
|
|||
|
|
|||
|
;; Top-level declarations
|
|||
|
(topdecl-var
|
|||
|
(concat line-prefix "\\(" varid "\\(?:\\s-*,\\s-*" varid "\\)*" "\\)"
|
|||
|
;; optionally allow for a single newline after identifier
|
|||
|
"\\(\\s-+\\|\\s-*[\n]\\s-+\\)"
|
|||
|
;; A toplevel declaration can be followed by a definition
|
|||
|
;; (=), a type (::) or (∷), a guard, or a pattern which can
|
|||
|
;; either be a variable, a constructor, a parenthesized
|
|||
|
;; thingy, or an integer or a string.
|
|||
|
"\\(" varid "\\|" conid "\\|::\\|∷\\|=\\||\\|\\s(\\|[0-9\"']\\)"))
|
|||
|
(topdecl-var2
|
|||
|
(concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*`\\(" varid "\\)`"))
|
|||
|
(topdecl-bangpat
|
|||
|
(concat line-prefix "\\(" varid "\\)\\s-*!"))
|
|||
|
(topdecl-sym
|
|||
|
(concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*\\(" sym "\\)"))
|
|||
|
(topdecl-sym2 (concat line-prefix "(\\(" sym "\\))"))
|
|||
|
|
|||
|
keywords)
|
|||
|
|
|||
|
(setq keywords
|
|||
|
`(;; NOTICE the ordering below is significant
|
|||
|
;;
|
|||
|
("^#\\(?:[^\\\n]\\|\\\\\\(?:.\\|\n\\|\\'\\)\\)*\\(?:\n\\|\\'\\)" 0 'font-lock-preprocessor-face t)
|
|||
|
|
|||
|
,@(haskell-font-lock-symbols-keywords)
|
|||
|
|
|||
|
;; Special case for `as', `hiding', `safe' and `qualified', which are
|
|||
|
;; keywords in import statements but are not otherwise reserved.
|
|||
|
("\\<import[ \t]+\\(?:\\(safe\\>\\)[ \t]*\\)?\\(?:\\(qualified\\>\\)[ \t]*\\)?\\(?:\"[^\"]*\"[\t ]*\\)?[^ \t\n()]+[ \t]*\\(?:\\(\\<as\\>\\)[ \t]*[^ \t\n()]+[ \t]*\\)?\\(\\<hiding\\>\\)?"
|
|||
|
(1 'haskell-keyword-face nil lax)
|
|||
|
(2 'haskell-keyword-face nil lax)
|
|||
|
(3 'haskell-keyword-face nil lax)
|
|||
|
(4 'haskell-keyword-face nil lax))
|
|||
|
|
|||
|
;; Special case for `foreign import'
|
|||
|
;; keywords in foreign import statements but are not otherwise reserved.
|
|||
|
("\\<\\(foreign\\)[ \t]+\\(import\\)[ \t]+\\(?:\\(ccall\\|stdcall\\|cplusplus\\|jvm\\|dotnet\\)[ \t]+\\)?\\(?:\\(safe\\|unsafe\\|interruptible\\)[ \t]+\\)?"
|
|||
|
(1 'haskell-keyword-face nil lax)
|
|||
|
(2 'haskell-keyword-face nil lax)
|
|||
|
(3 'haskell-keyword-face nil lax)
|
|||
|
(4 'haskell-keyword-face nil lax))
|
|||
|
|
|||
|
;; Special case for `foreign export'
|
|||
|
;; keywords in foreign export statements but are not otherwise reserved.
|
|||
|
("\\<\\(foreign\\)[ \t]+\\(export\\)[ \t]+\\(?:\\(ccall\\|stdcall\\|cplusplus\\|jvm\\|dotnet\\)[ \t]+\\)?"
|
|||
|
(1 'haskell-keyword-face nil lax)
|
|||
|
(2 'haskell-keyword-face nil lax)
|
|||
|
(3 'haskell-keyword-face nil lax))
|
|||
|
|
|||
|
;; Special case for `type family' and `data family'.
|
|||
|
;; `family' is only reserved in these contexts.
|
|||
|
("\\<\\(type\\|data\\)[ \t]+\\(family\\>\\)"
|
|||
|
(1 'haskell-keyword-face nil lax)
|
|||
|
(2 'haskell-keyword-face nil lax))
|
|||
|
|
|||
|
;; Special case for `type role'
|
|||
|
;; `role' is only reserved in this context.
|
|||
|
("\\<\\(type\\)[ \t]+\\(role\\>\\)"
|
|||
|
(1 'haskell-keyword-face nil lax)
|
|||
|
(2 'haskell-keyword-face nil lax))
|
|||
|
|
|||
|
;; Toplevel Declarations.
|
|||
|
;; Place them *before* generic id-and-op highlighting.
|
|||
|
(,topdecl-var (1 (unless (member (match-string 1) haskell-font-lock-keywords)
|
|||
|
'haskell-definition-face)))
|
|||
|
(,topdecl-var2 (2 (unless (member (match-string 2) haskell-font-lock-keywords)
|
|||
|
'haskell-definition-face)))
|
|||
|
(,topdecl-bangpat (1 (unless (member (match-string 1) haskell-font-lock-keywords)
|
|||
|
'haskell-definition-face)))
|
|||
|
(,topdecl-sym (2 (unless (member (match-string 2) '("\\" "=" "->" "→" "<-" "←" "::" "∷" "," ";" "`"))
|
|||
|
'haskell-definition-face)))
|
|||
|
(,topdecl-sym2 (1 (unless (member (match-string 1) '("\\" "=" "->" "→" "<-" "←" "::" "∷" "," ";" "`"))
|
|||
|
'haskell-definition-face)))
|
|||
|
|
|||
|
;; These four are debatable...
|
|||
|
("(\\(,*\\|->\\))" 0 'haskell-constructor-face)
|
|||
|
("\\[\\]" 0 'haskell-constructor-face)
|
|||
|
|
|||
|
("`"
|
|||
|
(0 (if (or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4))
|
|||
|
(parse-partial-sexp (point) (point-max) nil nil (syntax-ppss)
|
|||
|
'syntax-table)
|
|||
|
(when (save-excursion
|
|||
|
(goto-char (match-beginning 0))
|
|||
|
(haskell-lexeme-looking-at-backtick))
|
|||
|
(goto-char (match-end 0))
|
|||
|
(unless (text-property-not-all (match-beginning 1) (match-end 1) 'face nil)
|
|||
|
(put-text-property (match-beginning 1) (match-end 1) 'face 'haskell-operator-face))
|
|||
|
(unless (text-property-not-all (match-beginning 2) (match-end 2) 'face nil)
|
|||
|
(put-text-property (match-beginning 2) (match-end 2) 'face 'haskell-operator-face))
|
|||
|
(unless (text-property-not-all (match-beginning 4) (match-end 4) 'face nil)
|
|||
|
(put-text-property (match-beginning 4) (match-end 4) 'face 'haskell-operator-face))
|
|||
|
(add-text-properties
|
|||
|
(match-beginning 0) (match-end 0)
|
|||
|
'(font-lock-fontified t fontified t font-lock-multiline t))))))
|
|||
|
|
|||
|
(,haskell-lexeme-idsym-first-char
|
|||
|
(0 (if (or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4))
|
|||
|
(parse-partial-sexp (point) (point-max) nil nil (syntax-ppss)
|
|||
|
'syntax-table)
|
|||
|
(when (save-excursion
|
|||
|
(goto-char (match-beginning 0))
|
|||
|
(haskell-lexeme-looking-at-qidsym))
|
|||
|
(goto-char (match-end 0))
|
|||
|
;; note that we have to put face ourselves here because font-lock
|
|||
|
;; will use match data from the original matcher
|
|||
|
(haskell-font-lock--put-face-on-type-or-constructor)))))))
|
|||
|
keywords))
|
|||
|
|
|||
|
|
|||
|
(defun haskell-font-lock-fontify-block (lang-mode start end)
|
|||
|
"Fontify a block as LANG-MODE."
|
|||
|
(let ((string (buffer-substring-no-properties start end))
|
|||
|
(modified (buffer-modified-p))
|
|||
|
(org-buffer (current-buffer)) pos next)
|
|||
|
(remove-text-properties start end '(face nil))
|
|||
|
(with-current-buffer
|
|||
|
(get-buffer-create
|
|||
|
(concat " haskell-font-lock-fontify-block:" (symbol-name lang-mode)))
|
|||
|
(delete-region (point-min) (point-max))
|
|||
|
(insert string " ") ;; so there's a final property change
|
|||
|
(cl-letf (((symbol-function 'message)
|
|||
|
(lambda (_fmt &rest _args))))
|
|||
|
;; silence messages
|
|||
|
(unless (eq major-mode lang-mode) (funcall lang-mode))
|
|||
|
(font-lock-ensure))
|
|||
|
(setq pos (point-min))
|
|||
|
(while (setq next (next-single-property-change pos 'face))
|
|||
|
(put-text-property
|
|||
|
(+ start (1- pos)) (1- (+ start next)) 'face
|
|||
|
(or (get-text-property pos 'face) 'default) org-buffer)
|
|||
|
(setq pos next))
|
|||
|
(unless (equal pos (point-max))
|
|||
|
(put-text-property
|
|||
|
(+ start (1- pos)) (1- (+ start (point-max))) 'face
|
|||
|
'default org-buffer)))
|
|||
|
(add-text-properties
|
|||
|
start end
|
|||
|
'(font-lock-fontified t fontified t font-lock-multiline t))
|
|||
|
(set-buffer-modified-p modified)))
|
|||
|
|
|||
|
(defun haskell-syntactic-face-function (state)
|
|||
|
"`font-lock-syntactic-face-function' for Haskell."
|
|||
|
(cond
|
|||
|
((nth 3 state)
|
|||
|
(if (equal ?| (nth 3 state))
|
|||
|
;; find out what kind of QuasiQuote is this
|
|||
|
(let* ((qqname (save-excursion
|
|||
|
(goto-char (nth 8 state))
|
|||
|
(skip-syntax-backward "w._")
|
|||
|
(buffer-substring-no-properties (point) (nth 8 state))))
|
|||
|
(lang-mode (cdr (assoc qqname haskell-font-lock-quasi-quote-modes))))
|
|||
|
|
|||
|
(if (and lang-mode
|
|||
|
(fboundp lang-mode))
|
|||
|
(save-excursion
|
|||
|
;; find the end of the QuasiQuote
|
|||
|
(parse-partial-sexp (point) (point-max) nil nil state
|
|||
|
'syntax-table)
|
|||
|
(haskell-font-lock-fontify-block lang-mode (1+ (nth 8 state)) (1- (point)))
|
|||
|
;; must return nil here so that it is not fontified again as string
|
|||
|
nil)
|
|||
|
;; fontify normally as string because lang-mode is not present
|
|||
|
'haskell-quasi-quote-face))
|
|||
|
(save-excursion
|
|||
|
(let
|
|||
|
((state2
|
|||
|
(parse-partial-sexp (point) (point-max) nil nil state
|
|||
|
'syntax-table))
|
|||
|
(end-of-string (point)))
|
|||
|
|
|||
|
(put-text-property (nth 8 state) (point)
|
|||
|
'face 'font-lock-string-face)
|
|||
|
|
|||
|
|
|||
|
(if (or (equal t (nth 3 state)) (nth 3 state2))
|
|||
|
;; This is an unterminated string constant, use warning
|
|||
|
;; face for the opening quote.
|
|||
|
(put-text-property (nth 8 state) (1+ (nth 8 state))
|
|||
|
'face 'font-lock-warning-face))
|
|||
|
|
|||
|
(goto-char (1+ (nth 8 state)))
|
|||
|
(while (re-search-forward "\\\\" end-of-string t)
|
|||
|
|
|||
|
(goto-char (1- (point)))
|
|||
|
|
|||
|
(if (looking-at haskell-lexeme-string-literal-inside-item)
|
|||
|
(goto-char (match-end 0))
|
|||
|
|
|||
|
;; We are looking at an unacceptable escape
|
|||
|
;; sequence. Use warning face to highlight that.
|
|||
|
(put-text-property (point) (1+ (point))
|
|||
|
'face 'font-lock-warning-face)
|
|||
|
(goto-char (1+ (point)))))))
|
|||
|
;; must return nil here so that it is not fontified again as string
|
|||
|
nil))
|
|||
|
;; Detect literate comment lines starting with syntax class '<'
|
|||
|
((save-excursion
|
|||
|
(goto-char (nth 8 state))
|
|||
|
(equal (string-to-syntax "<") (syntax-after (point))))
|
|||
|
'haskell-literate-comment-face)
|
|||
|
;; Detect pragmas. A pragma is enclosed in special comment
|
|||
|
;; delimiters {-# .. #-}.
|
|||
|
((save-excursion
|
|||
|
(goto-char (nth 8 state))
|
|||
|
(and (looking-at-p "{-#")
|
|||
|
(forward-comment 1)
|
|||
|
(goto-char (- (point) 3))
|
|||
|
(looking-at-p "#-}")))
|
|||
|
'haskell-pragma-face)
|
|||
|
;; Detect Liquid Haskell annotations enclosed in special comment
|
|||
|
;; delimiters {-@ .. @-}.
|
|||
|
((save-excursion
|
|||
|
(goto-char (nth 8 state))
|
|||
|
(and (looking-at-p "{-@")
|
|||
|
(forward-comment 1)
|
|||
|
(goto-char (- (point) 3))
|
|||
|
(looking-at-p "@-}")))
|
|||
|
'haskell-liquid-haskell-annotation-face)
|
|||
|
;; Haddock comment start with either "-- [|^*$]" or "{- ?[|^*$]"
|
|||
|
;; (note space optional for nested comments and mandatory for
|
|||
|
;; double dash comments).
|
|||
|
;;
|
|||
|
;; Haddock comment will also continue on next line, provided:
|
|||
|
;; - current line is a double dash haddock comment
|
|||
|
;; - next line is also double dash comment
|
|||
|
;; - there is only whitespace between
|
|||
|
;;
|
|||
|
;; We recognize double dash haddock comments by property
|
|||
|
;; 'font-lock-doc-face attached to newline. In case of {- -}
|
|||
|
;; comments newline is outside of comment.
|
|||
|
((save-excursion
|
|||
|
(goto-char (nth 8 state))
|
|||
|
(or (looking-at-p "\\(?:{- ?\\|-- \\)[|^*$]")
|
|||
|
(and (looking-at-p "--") ; are we at double dash comment
|
|||
|
(forward-line -1) ; this is nil on first line
|
|||
|
(eq (get-text-property (line-end-position) 'face)
|
|||
|
'font-lock-doc-face) ; is a doc face
|
|||
|
(forward-line)
|
|||
|
(skip-syntax-forward "-") ; see if there is only whitespace
|
|||
|
(eq (point) (nth 8 state))))) ; we are back in position
|
|||
|
;; Here we look inside the comment to see if there are substrings
|
|||
|
;; worth marking inside we try to emulate as much of haddock as
|
|||
|
;; possible. First we add comment face all over the comment, then
|
|||
|
;; we add special features.
|
|||
|
(let ((beg (nth 8 state))
|
|||
|
(end (save-excursion
|
|||
|
(parse-partial-sexp (point) (point-max) nil nil state
|
|||
|
'syntax-table)
|
|||
|
(point)))
|
|||
|
(emphasis-open-point nil)
|
|||
|
(strong-open-point nil))
|
|||
|
(put-text-property beg end 'face 'font-lock-doc-face)
|
|||
|
|
|||
|
(when (fboundp 'add-face-text-property)
|
|||
|
;; `add-face-text-property' is not defined in Emacs 23
|
|||
|
|
|||
|
;; iterate over chars, take escaped chars unconditionally
|
|||
|
;; mark when a construct is opened, close and face it when
|
|||
|
;; it is closed
|
|||
|
|
|||
|
(save-excursion
|
|||
|
(while (< (point) end)
|
|||
|
(if (looking-at "__\\|\\\\.\\|\\\n\\|[/]")
|
|||
|
(progn
|
|||
|
(cond
|
|||
|
((equal (match-string 0) "/")
|
|||
|
(if emphasis-open-point
|
|||
|
(progn
|
|||
|
(add-face-text-property emphasis-open-point (match-end 0)
|
|||
|
'(:slant italic))
|
|||
|
(setq emphasis-open-point nil))
|
|||
|
(setq emphasis-open-point (point))))
|
|||
|
((equal (match-string 0) "__")
|
|||
|
(if strong-open-point
|
|||
|
(progn
|
|||
|
(add-face-text-property strong-open-point (match-end 0)
|
|||
|
'(:weight bold))
|
|||
|
(setq strong-open-point nil))
|
|||
|
(setq strong-open-point (point))))
|
|||
|
(t
|
|||
|
;; this is a backslash escape sequence, skip over it
|
|||
|
))
|
|||
|
(goto-char (match-end 0)))
|
|||
|
;; skip chars that are not interesting
|
|||
|
(goto-char (1+ (point)))
|
|||
|
(skip-chars-forward "^_\\\\/" end))))))
|
|||
|
nil)
|
|||
|
(t 'font-lock-comment-face)))
|
|||
|
|
|||
|
(defun haskell-font-lock-defaults-create ()
|
|||
|
"Locally set `font-lock-defaults' for Haskell."
|
|||
|
(setq-local font-lock-defaults
|
|||
|
'((haskell-font-lock-keywords)
|
|||
|
nil nil nil nil
|
|||
|
(font-lock-syntactic-face-function
|
|||
|
. haskell-syntactic-face-function)
|
|||
|
;; Get help from font-lock-syntactic-keywords.
|
|||
|
(parse-sexp-lookup-properties . t)
|
|||
|
(font-lock-extra-managed-props . (composition)))))
|
|||
|
|
|||
|
(defun haskell-fontify-as-mode (text mode)
|
|||
|
"Fontify TEXT as MODE, returning the fontified text."
|
|||
|
(with-temp-buffer
|
|||
|
(funcall mode)
|
|||
|
(insert text)
|
|||
|
(if (fboundp 'font-lock-ensure)
|
|||
|
(font-lock-ensure)
|
|||
|
(with-no-warnings (font-lock-fontify-buffer)))
|
|||
|
(buffer-substring (point-min) (point-max))))
|
|||
|
|
|||
|
;; Provide ourselves:
|
|||
|
|
|||
|
(provide 'haskell-font-lock)
|
|||
|
|
|||
|
;; Local Variables:
|
|||
|
;; coding: utf-8-unix
|
|||
|
;; tab-width: 8
|
|||
|
;; End:
|
|||
|
|
|||
|
;;; haskell-font-lock.el ends here
|