greghendershott / racket-mode

Emacs major and minor modes for Racket: edit, REPL, check-syntax, debug, profile, and more.

Home Page:https://www.racket-mode.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Quotes in comments affect highlighting of non-commented code in racket-hash-lang-mode

ashton314 opened this issue · comments

Characters like ' in comments seem to affect the highlighting of the rest of the buffer. Clearly, what happens in comments should stay in comments. :)

((alist-get 'racket-mode package-alist))
((emacs-version "29.1.50")
 (system-type darwin)
 (x-gtk-use-system-tooltips UNDEFINED)
 (major-mode help-mode)
 (racket--el-source-dir "/var/folders/k3/4nd7wv0s6z9bm2h5p95ftrbw0000gn/T/elpaca.SRjyFh/elpaca/builds/racket-mode/")
 (racket--rkt-source-dir "/var/folders/k3/4nd7wv0s6z9bm2h5p95ftrbw0000gn/T/elpaca.SRjyFh/elpaca/builds/racket-mode/racket/")
 (racket-program "racket")
 (racket-command-timeout 10)
 (racket-path-from-emacs-to-racket-function UNDEFINED)
 (racket-path-from-racket-to-emacs-function UNDEFINED)
 (racket-browse-url-function racket-browse-url-using-temporary-file)
 (racket-documentation-search-location "https://docs.racket-lang.org/search/index.html?q=%s")
 (racket-xp-after-change-refresh-delay 1)
 (racket-xp-mode-lighter
  (:eval
   (racket--xp-mode-lighter)))
 (racket-xp-highlight-unused-regexp "^[^_]")
 (racket-repl-buffer-name-function nil)
 (racket-submodules-to-run
  ((test)
   (main)))
 (racket-memory-limit 2048)
 (racket-error-context medium)
 (racket-repl-history-directory "/var/folders/k3/4nd7wv0s6z9bm2h5p95ftrbw0000gn/T/elpaca.SRjyFh/racket-mode/")
 (racket-history-filter-regexp "\\`\\s *\\'")
 (racket-images-inline t)
 (racket-imagemagick-props nil)
 (racket-images-keep-last 100)
 (racket-images-system-viewer "open")
 (racket-pretty-print t)
 (racket-use-repl-submit-predicate nil)
 (racket-pretty-print t)
 (racket-indent-curly-as-sequence t)
 (racket-indent-sequence-depth 0)
 (racket-pretty-lambda nil)
 (racket-smart-open-bracket-enable nil)
 (racket-module-forms "\\s(\\(?:module[*+]?\\|library\\)")
 (racket-logger-config
  ((cm-accomplice . warning)
   (GC . info)
   (module-prefetch . warning)
   (optimizer . info)
   (racket/contract . error)
   (racket-mode-debugger . info)
   (sequence-specialization . info)
   (* . fatal)))
 (racket-show-functions
  (racket-show-pseudo-tooltip)))
(enabled-minor-modes
 (auto-composition-mode)
 (auto-compression-mode)
 (auto-encryption-mode)
 (auto-fill-mode)
 (auto-save-mode)
 (blink-cursor-mode)
 (buffer-read-only)
 (electric-indent-mode)
 (file-name-shadow-mode)
 (font-lock-mode)
 (global-eldoc-mode)
 (global-font-lock-mode)
 (indent-tabs-mode)
 (isearch-fold-quotes-mode)
 (line-number-mode)
 (menu-bar-mode)
 (mouse-wheel-mode)
 (semantic-minor-modes-format)
 (shell-dirtrack-mode)
 (show-paren-mode)
 (tool-bar-mode)
 (tooltip-mode)
 (transient-mark-mode))
(disabled-minor-modes
 (abbrev-mode)
 (auto-fill-function)
 (auto-save-visited-mode)
 (buffer-face-mode)
 (button-mode)
 (cl-old-struct-compat-mode)
 (column-number-mode)
 (comint-fontify-input-mode)
 (compilation-minor-mode)
 (compilation-shell-minor-mode)
 (completion-in-region-mode)
 (context-menu-mode)
 (cursor-face-highlight-mode)
 (defining-kbd-macro)
 (display-line-numbers-mode)
 (eldoc-mode)
 (electric-layout-mode)
 (electric-quote-mode)
 (elpaca-no-symlink-mode)
 (elpaca-ui-live-update-mode)
 (global-display-line-numbers-mode)
 (global-hl-line-mode)
 (global-prettify-symbols-mode)
 (global-semantic-highlight-edits-mode)
 (global-semantic-highlight-func-mode)
 (global-semantic-show-parser-state-mode)
 (global-semantic-show-unmatched-syntax-mode)
 (global-semantic-stickyfunc-mode)
 (global-visual-line-mode)
 (header-line-indent-mode)
 (hl-line-mode)
 (horizontal-scroll-bar-mode)
 (hs-minor-mode)
 (isearch-mode)
 (jit-lock-debug-mode)
 (lock-file-mode)
 (lost-selection-mode)
 (next-error-follow-minor-mode)
 (overwrite-mode)
 (paragraph-indent-minor-mode)
 (prettify-symbols-mode)
 (racket-hash-lang-repl-mode)
 (racket-smart-open-bracket-mode)
 (racket-xp-mode)
 (read-extended-command-mode)
 (semantic-highlight-edits-mode)
 (semantic-highlight-func-mode)
 (semantic-mode)
 (semantic-show-parser-state-mode)
 (semantic-show-unmatched-syntax-mode)
 (semantic-stickyfunc-mode)
 (sh-electric-here-document-mode)
 (shell-highlight-undef-mode)
 (size-indication-mode)
 (tab-bar-history-mode)
 (tab-bar-mode)
 (temp-buffer-resize-mode)
 (text-scale-mode)
 (treesit-explore-mode)
 (treesit-inspect-mode)
 (undelete-frame-mode)
 (url-handler-mode)
 (use-hard-newlines)
 (view-mode)
 (visible-mode)
 (visual-line-mode)
 (window-divider-mode)
 (xref-etags-mode))

Here's a sample Rhombus file that exhibits the behavior:

#lang rhombus

fun factorial(n :: Int) :: Int:
  if n == 0
  | 1
  | n * factorial(n - 1)

// The quote shouldn't affect the next block
// But everything after this gets font-lock-string-face
fun factorial_fast(n :: Int, acc :: Int) :: Int:
  if n == 0
  | acc
  | factorial_fast(n - 1, n * acc)

factorial_fast(5, 1)

The ' in the word shouldn't in the comment makes the rest of the file (except for the numbers) get the font-lock-string-face. If you delete the single quote, everything goes back to normal.

If you use elpaca, here's an elpaca-test macro to reproduce the behavior:

;; -*- lexical-binding: t; -*-

(elpaca-test
  :interactive t
  :early-init
  (setq byte-compile-warnings '(not obsolete))
  (setq inhibit-startup-screen t)
  :init
  (elpaca (racket-mode :host github :repo "greghendershott/racket-mode"))
  (elpaca-wait)
  (find-file "hash_lang_highlight_bug.rhm")
  (insert "#lang rhombus

fun factorial(n :: Int) :: Int:
  if n == 0
  | 1
  | n * factorial(n - 1)

// The quote shouldn't affect the next block
// But everything after this gets font-lock-string-face
fun factorial_fast(n :: Int, acc :: Int) :: Int:
  if n == 0
  | acc
  | factorial_fast(n - 1, n * acc)

factorial_fast(5, 1)")
  (save-buffer)
  (racket-hash-lang-mode)
  (racket-xp-mode))

Dump that into a buffer, and then run eval-buffer, and a new Emacs session should pop up in a clean environment, install racket-mode, and load the buffer. (You might have to hit q once Elpaca is done installing racket-mode to see the buffer.) As soon as you close the new Emacs session, Elpaca will clean everything up.

Thanks for the report!

If you M-x describe-char in those places the racket-token property is correct (e.g. "symbol" or "constant" etc. not "string"). So this is related to something else jumping in and doing font-lock.

I think this broke when fixing #669 to accommodate things like highlight-indent-guides by allowing some normal font-lock to happen, via font-lock-keywords matchers, and calling the default font-lock-fontify-region as well as doing our own thing.

I believe what I overlooked was that syntax-propertize-function must be set not to nil -- which means "do the default thing", which means "use the char syntax table for stuff like quotes to do font-lock" -- but instead to something like #'ignore (Emacs lisp that's ~= void in Racket), meaning do absolutely nothing.

At least that fixes the problem for me in a few quick tests. I might sleep on this to see if I can think of any other angles, before committing.

Thanks again for pointing this out!

Good to know—thank you for looking at this, Greg!

I'm at an awkward level of Emacs proficiency: I can kinda stumble around with Elisp a bit and read basic code, but my knowledge of Emacs internals (e.g. how font-lock works) is pretty lacking—so my help will be limited. 😅 I'm still very happy to help test stuff!

Oh no worries. I was thinking out loud in my comment. Your bug report was excellent.

Besides which, font-lock is probably the most complicated aspect of Emacs I've encountered so far. There are so many variables and configurable functions (~= parameters in Racket). So many layers.

Indeed after thinking about this more, I believe a better fix is to set font-lock-keywords-only true in our major mode initialization. And for belt+suspenders (just in case some minor-mode @#$s with that value), also avoid calling the general font-lock-default-fontify-region and instead call the more limited font-lock-fontify-keywords-region.

That also fixes your reported problem. And that's both clearer and more resilient... I think. :)

font-lock is probably the most complicated aspect of Emacs I've encountered so far

OK, well, if the maintainer racket-mode says an Emacs thing is complicated, it's complicated. :) I'm glad my bug report was helpful! I just wish I could actually monkey around in the code to maybe come up with a solution on occasion.


I noticed the same problem happens with #' for Rhombus/Shplait code:

#'thingy

Running describe-character on the ' (thanks for the tip!) shows the face as font-lock-string-face but the racket-token is [operator]. I imagine it's the same root cause ultimately, no?

(Happy Thanksgiving tomorrow! I hope you have a good, relaxing, and filling holiday!)

(Happy Thanksgiving tomorrow! I hope you have a good, relaxing, and filling holiday!)

Thanks!

I merged a commit to fix this -- as well as to more thoroughly review the situation.

Originally I believed that racket-hash-lang-mode would handle everything via tokens from the back end, and no "normal" font-lock would also be involved.

Then when I addressed #669 I didn't fully understand or appreciate what kinds font-lock it could co-exist with (and what would be required to do so properly) vs. what kinds of font-lock were just flat-out incompatible.

I think I sorted through all that, now. The size of the commit is mostly trying to handle and comment/document that in general.

I noticed the same problem happens with #' for Rhombus/Shplait code:

#'thingy

Running describe-character on the ' (thanks for the tip!) shows the face as font-lock-string-face but the racket-token is [operator]. I imagine it's the same root cause ultimately, no?

Yes. I just installed shplait and your example looks correct to me with the new commit now.

The bad behavior is definitely gone now. Thanks!

Alas, all the pretty colors are gone now too. Before fun, if, etc. would get highlighted with racket-xp-binding-lang-use-face, but that doesn't seem to be happening any more.

Is there something I can do to get the colors back? E.g. can I ask the racket backend to annotate things like fun or if or match as keywords to get some of the nice colors back? Right now the racket-token property for almost everything is [symbol]. Is this a limitation of how each language is implemented?

It's off by default because now it's just one of a couple possible approaches to adding extra colors.

You can enable via customization variable racket-xp-add-binding-faces.

See racket-hash-lang-module-language-hook doc string, toward the end, for discussion.

In other words you can keep using racket-xp-mode to add binding faces in racket-hash-lang-mode. If you always want this, you could (setq racket-xp-add-binding-faces) from a racket-hash-lang-mode-hook. Or, if you only want that for some hash-langs, you could enable this (or not) in racket-hash-lang-module-language-hook.

Another choice is that, you can use font-lock-add-keywords to add patterns. You can borrow some of the ones from classic racket-mode. You could make a new set of patterns for rhombus or for X.


Something like the "dynamic" approach of binding faces feels more general and better.

On the other hand, a fixed list of keywords to highlight for a given lang, could work better in some cases; it's definitely the classic Emacs approach.

p.s. A drawback of the binding faces is that, if check-syntax finds an error in your program, you get no colors until it's corrected. e.g. https://racket.discourse.group/t/racket-mode-users-want-to-try-racket-hash-lang-mode/2372/7?u=greghendershott

I think a third, dynamic-ish way might be possible: Making old-fashioned font-lock-keywords regexp patterns from the list of completion candidates. That's close to the same set of things as binding sites that get colored.

That way, even if racket-xp-mode found an error in your program, you could still get more colors (just like you still get completion candidates) based on info from previous non-error checks.

It wouldn't be perfect but maybe that would be OK if this approach were used as a "best effort fallback" only during the check-syntax error state, and then was replaced by the binding arrows colors when not in error. (Just thinking out loud here.)

However completions for rhombus don't work well quite yet (https://racket.discourse.group/t/imports-as-completion-candidates-for-hash-langs/2511/6?u=greghendershott), so that's moot for you at least in the near future.