Path: blob/master/elisp/slime/contrib/slime-fontifying-fu.el
1496 views
1(define-slime-contrib slime-fontifying-fu2"Additional fontification tweaks:3Fontify WITH-FOO, DO-FOO, DEFINE-FOO like standard macros.4Fontify CHECK-FOO like CHECK-TYPE."5(:authors "Tobias C. Rittweiler <[email protected]>")6(:license "GPL")7(:on-load8(font-lock-add-keywords9'lisp-mode slime-additional-font-lock-keywords)10(when slime-highlight-suppressed-forms11(slime-activate-font-lock-magic)))12(:on-unload13;; FIXME: remove `slime-search-suppressed-forms', and remove the14;; extend-region hook.15(font-lock-remove-keywords16'lisp-mode slime-additional-font-lock-keywords)))1718;;; Fontify WITH-FOO, DO-FOO, and DEFINE-FOO like standard macros.19;;; Fontify CHECK-FOO like CHECK-TYPE.20(defvar slime-additional-font-lock-keywords21'(("(\\(\\(\\s_\\|\\w\\)*:\\(define-\\|do-\\|with-\\|without-\\)\\(\\s_\\|\\w\\)*\\)" 1 font-lock-keyword-face)22("(\\(\\(define-\\|do-\\|with-\\)\\(\\s_\\|\\w\\)*\\)" 1 font-lock-keyword-face)23("(\\(check-\\(\\s_\\|\\w\\)*\\)" 1 font-lock-warning-face)24("(\\(assert-\\(\\s_\\|\\w\\)*\\)" 1 font-lock-warning-face)))252627;;;; Specially fontify forms suppressed by a reader conditional.2829(defcustom slime-highlight-suppressed-forms t30"Display forms disabled by reader conditionals as comments."31:type '(choice (const :tag "Enable" t) (const :tag "Disable" nil))32:group 'slime-mode)3334(defface slime-reader-conditional-face35(if (slime-face-inheritance-possible-p)36'((t (:inherit font-lock-comment-face)))37'((((background light)) (:foreground "DimGray" :bold t))38(((background dark)) (:foreground "LightGray" :bold t))))39"Face for compiler notes while selected."40:group 'slime-mode-faces)4142(defvar slime-search-suppressed-forms-match-data (list nil nil))4344(defun slime-search-suppressed-forms-internal (limit)45(when (search-forward-regexp slime-reader-conditionals-regexp limit t)46(let ((start (match-beginning 0)) ; save match data47(state (slime-current-parser-state)))48(if (or (nth 3 state) (nth 4 state)) ; inside string or comment?49(slime-search-suppressed-forms-internal limit)50(let* ((char (char-before))51(expr (read (current-buffer)))52(val (slime-eval-feature-expression expr)))53(when (<= (point) limit)54(if (or (and (eq char ?+) (not val))55(and (eq char ?-) val))56;; If `slime-extend-region-for-font-lock' did not57;; fully extend the region, the assertion below may58;; fail. This should only happen on XEmacs and older59;; versions of GNU Emacs.60(ignore-errors61(forward-sexp) (backward-sexp)62;; Try to suppress as far as possible.63(slime-forward-sexp)64(assert (<= (point) limit))65(let ((md (match-data nil slime-search-suppressed-forms-match-data)))66(setf (first md) start)67(setf (second md) (point))68(set-match-data md)69t))70(slime-search-suppressed-forms-internal limit))))))))7172(defun slime-search-suppressed-forms (limit)73"Find reader conditionalized forms where the test is false."74(when (and slime-highlight-suppressed-forms75(slime-connected-p))76(let ((result 'retry))77(while (and (eq result 'retry) (<= (point) limit))78(condition-case condition79(setq result (slime-search-suppressed-forms-internal limit))80(end-of-file ; e.g. #+(81(setq result nil))82;; We found a reader conditional we couldn't process for83;; some reason; however, there may still be other reader84;; conditionals before `limit'.85(invalid-read-syntax ; e.g. #+#.foo86(setq result 'retry))87(scan-error ; e.g. #+nil (foo ...88(setq result 'retry))89(slime-incorrect-feature-expression ; e.g. #+(not foo bar)90(setq result 'retry))91(slime-unknown-feature-expression ; e.g. #+(foo)92(setq result 'retry))93(error94(setq result nil)95(slime-display-warning96(concat "Caught error during fontification while searching for forms\n"97"that are suppressed by reader-conditionals. The error was: %S.")98condition))))99result)))100101102(defun slime-search-directly-preceding-reader-conditional ()103"Search for a directly preceding reader conditional. Return its104position, or nil."105;;; We search for a preceding reader conditional. Then we check that106;;; between the reader conditional and the point where we started is107;;; no other intervening sexp, and we check that the reader108;;; conditional is at the same nesting level.109(condition-case nil110(let* ((orig-pt (point)))111(when-let (reader-conditional-pt112(search-backward-regexp slime-reader-conditionals-regexp113;; We restrict the search to the114;; beginning of the /previous/ defun.115(save-excursion (beginning-of-defun) (point))116t))117(let* ((parser-state118(parse-partial-sexp (progn (goto-char (+ reader-conditional-pt 2))119(forward-sexp) ; skip feature expr.120(point))121orig-pt))122(paren-depth (car parser-state))123(last-sexp-pt (caddr parser-state)))124(if (and paren-depth (not (plusp paren-depth)) ; no opening parenthesis in between?125(not last-sexp-pt)) ; no complete sexp in between?126reader-conditional-pt127nil))))128(scan-error nil))) ; improper feature expression129130131;;; We'll push this onto `font-lock-extend-region-functions'. In past,132;;; we didn't do so which made our reader-conditional font-lock magic133;;; pretty unreliable (it wouldn't highlight all suppressed forms, and134;;; worked quite non-deterministic in general.)135;;;136;;; Cf. _Elisp Manual_, 23.6.10 Multiline Font Lock Constructs.137;;;138;;; We make sure that `font-lock-beg' and `font-lock-end' always point139;;; to the beginning or end of a toplevel form. So we never miss a140;;; reader-conditional, or point in mid of one.141(defun slime-extend-region-for-font-lock ()142(when slime-highlight-suppressed-forms143(condition-case c144(let (changedp)145(multiple-value-setq (changedp font-lock-beg font-lock-end)146(slime-compute-region-for-font-lock font-lock-beg font-lock-end))147changedp)148(error149(slime-display-warning150(concat "Caught error when trying to extend the region for fontification.\n"151"The error was: %S\n"152"Further: font-lock-beg=%d, font-lock-end=%d.")153c font-lock-beg font-lock-end)))))154155(when (fboundp 'syntax-ppss-toplevel-pos)156(defun slime-beginning-of-tlf ()157(when-let (pos (syntax-ppss-toplevel-pos (slime-current-parser-state)))158(goto-char pos))))159160(unless (fboundp 'syntax-ppss-toplevel-pos)161(defun slime-beginning-of-tlf ()162(let* ((state (slime-current-parser-state))163(comment-start (nth 8 state)))164(when comment-start ; or string165(goto-char comment-start)166(setq state (slime-current-parser-state)))167(let ((depth (nth 0 state)))168(when (plusp depth)169(ignore-errors (up-list (- depth)))) ; ignore unbalanced parentheses170(when-let (upper-pt (nth 1 state))171(goto-char upper-pt)172(while (when-let (upper-pt (nth 1 (slime-current-parser-state)))173(goto-char upper-pt))))))))174175(defun slime-compute-region-for-font-lock (orig-beg orig-end)176(let ((beg orig-beg)177(end orig-end))178(goto-char beg)179(inline (slime-beginning-of-tlf))180(assert (not (plusp (nth 0 (slime-current-parser-state)))))181(setq beg (let ((pt (point)))182(cond ((> (- beg pt) 20000) beg)183((slime-search-directly-preceding-reader-conditional))184(t pt))))185(goto-char end)186(while (search-backward-regexp slime-reader-conditionals-regexp beg t)187(setq end (max end (save-excursion188(ignore-errors (slime-forward-reader-conditional))189(point)))))190(values (or (/= beg orig-beg) (/= end orig-end)) beg end)))191192193(defun slime-activate-font-lock-magic ()194(if (featurep 'xemacs)195(let ((pattern `((slime-search-suppressed-forms196(0 slime-reader-conditional-face t)))))197(dolist (sym '(lisp-font-lock-keywords198lisp-font-lock-keywords-1199lisp-font-lock-keywords-2))200(set sym (append (symbol-value sym) pattern))))201(font-lock-add-keywords202'lisp-mode203`((slime-search-suppressed-forms 0 ,''slime-reader-conditional-face t)))204205(add-hook 'lisp-mode-hook206#'(lambda ()207(add-hook 'font-lock-extend-region-functions208'slime-extend-region-for-font-lock t t)))))209210(let ((byte-compile-warnings '()))211(mapc #'byte-compile212'(slime-extend-region-for-font-lock213slime-compute-region-for-font-lock214slime-search-directly-preceding-reader-conditional215slime-search-suppressed-forms216slime-beginning-of-tlf)))217218;;; Tests219(def-slime-test font-lock-magic (buffer-content)220"Some testing for the font-lock-magic. *YES* should be221highlighted as a suppressed form, *NO* should not."222223'(("(defun *NO* (x y) (+ x y))")224("(defun *NO*")225("*NO*) #-(and) (*YES*) (*NO* *NO*")226("\(227\(defun *NO*")228("\)229\(defun *NO*230\(231\)")232("#+#.foo233\(defun *NO* (x y) (+ x y))")234("#+#.foo235\(defun *NO* (x ")236("#+(237\(defun *NO* (x ")238("#+(test)239\(defun *NO* (x ")240241("(eval-when (...)242\(defun *NO* (x ")243244("(eval-when (...)245#+(and)246\(defun *NO* (x ")247248("#-(and) (defun *YES* (x y) (+ x y))")249("250#-(and) (defun *YES* (x y) (+ x y))251#+(and) (defun *NO* (x y) (+ x y))")252253("#+(and) (defun *NO* (x y) #-(and) (+ *YES* y))")254("#| #+(or) |# *NO*")255("#| #+(or) x |# *NO*")256("*NO* \"#| *NO* #+(or) x |# *NO*\" *NO*")257("#+#.foo (defun foo (bar))258#-(and) *YES* *NO* bar259")260("#+(foo) (defun foo (bar))261#-(and) *YES* *NO* bar")262("#| #+(or) |# *NO* foo263#-(and) *YES* *NO*")264("#- (and)265\(*YES*)266\(*NO*)267#-(and)268\(*YES*)269\(*NO*)")270("#+nil (foo)271272#-(and)273#+nil (274asdf *YES* a275fsdfad)276277\( asdf *YES*278279)280\(*NO*)281282")283("*NO*284285#-(and) \(progn286#-(and)287(defun *YES* ...)288289#+(and)290(defun *YES* ...)291292(defun *YES* ...)293294*YES*295296*YES*297298*YES*299300*YES*301\)302303*NO*")304("#-(not) *YES* *NO*305306*NO*307308#+(not) *NO* *NO*309310*NO*311312#+(not a b c) *NO* *NO*313314*NO*"))315(slime-check-top-level)316(with-temp-buffer317(insert buffer-content)318(slime-initialize-lisp-buffer-for-test-suite319:autodoc t :font-lock-magic t)320;; Can't use `font-lock-fontify-buffer' because for the case when321;; `jit-lock-mode' is enabled. Jit-lock-mode fontifies only on322;; actual display.323(font-lock-default-fontify-buffer)324(when (search-backward "*NO*" nil t)325(slime-test-expect "Not suppressed by reader conditional?"326'slime-reader-conditional-face327(get-text-property (point) 'face)328#'(lambda (x y) (not (eq x y)))))329(goto-char (point-max))330(when (search-backward "*YES*" nil t)331(slime-test-expect "Suppressed by reader conditional?"332'slime-reader-conditional-face333(get-text-property (point) 'face)))))334335(defun* slime-initialize-lisp-buffer-for-test-suite336(&key (font-lock-magic t) (autodoc t))337(let ((hook lisp-mode-hook))338(unwind-protect339(progn340(set (make-local-variable 'slime-highlight-suppressed-forms)341font-lock-magic)342(setq lisp-mode-hook nil)343(lisp-mode)344(slime-mode 1)345(when (boundp 'slime-autodoc-mode)346(if autodoc347(slime-autodoc-mode 1)348(slime-autodoc-mode -1))))349(setq lisp-mode-hook hook))))350351(provide 'slime-fontifying-fu)352353354