Nix Syntax Highlighting and Highlighting Inside Strings

Hello, everyone! I am looking for a way to highlight code snippets inside Nix strings. I use VS Code and Neovim for editing text.

Using proprietary Nix syntax or sourcing files is not an option for me. The proprietary Nix syntax will compromise the formatting, making the configuration files less readable in a non-Nix-based environment, while sourcing files means missing out on the Nix constants and other functionality. Both of these points are deal breakers for me.

Here is a snippet from my configuration for Waybar:

style = lib.concatStrings [
  ''
    /* some styles here */

    window#waybar > box {
      background-color: rgba(${base00RGB}, 0.6);
      border-radius: 0px;
      color: #${base00};
      box-shadow: 0px 2px 5px 0px rgba(${base00RGB}, 0.6);
    }

    /* some other styles here */
  ''
];

I want the code inside the string to be properly highlighted. What are some ways I can do that in VS Code and Neovim?

Thanks in advance!

Best way to do this is just to have those files in a separate file and read them to strings with builtins.readFile. That gives you syntax highlighting, formatting, linting, etc.

There are some fancy plugins for older editors (e.g. polymode for emacs), but generally they end up interfering with various bits of functionality.

In theory language servers can support this kind of thing, but none of the nix language servers currently implement this - it would be kind of difficult to, since we embed so many different languages, and there’s no good way to identify them. They’re also often snippets that lack surrounding boilerplate.

3 Likes

Thanks for the reply!

As far as I understand, this means I’ll have to miss out on the Nix functionality within that file? For example, declaring a constant using the let ... in syntax to use it inside the configuration? Or am I missing something?

For Neovim you can achieve it with treesitter injections, like this plugin: GitHub - calops/hmts.nvim: Custom treesitter queries for Home Manager nix files, in Neovim

3 Likes

Thanks for the reply! I’ll check it out.

Partially. You won’t be able to use nix’ string interpolation, but you can always use environment variables or the substitute helpers to push those values into the files anyway.

My personal preference is to do that for larger bits of configuration where syntax highlighting really helps, but I’m content with treating smaller snippets as text.

Good multi-language support in language servers (and git forges) would be awesome long-term, though.

1 Like

This is pretty cool, thanks!

Now that I am at a computer, I can do better. I have this treesitter injection for nix files: nvim-config/after/queries/nix/injections.scm at main - ndreas/nvim-config - Codeberg.org With it, I can add a comment just before a string with the language name, and the string will highlight using that language.

An example:

The /* tmux */ comment enables tmux highlighting within the following string.

3 Likes

For the reference: Some time ago, I compared some Emacs major/minor modes doing the same thing:

  • mmm-mode: Established solution. Seems to be a bit aging.
  • polymode: Allows definition of a host and several inner modes. Seems to
    be a bit hard to set up, and the default modes do not cover my use cases.
  • string-edit: Offers string-edit-at-point which opens a new buffer in a
    desired major mode and unescaped contents. Finishing the edit, escapes the
    buffer contents and replaces the string in the original buffer.
  • edit-indirect: Edit regions in separate buffers. Similar to string-edit;
    used by markdown-mode, and so I have it installed. I use edit-indirect
    regularly, but automatic mode detection does not work well.
6 Likes

@dschrempf thank you for this list. Regarding polymode, I found this code in a github issue (Interaction with nix-mode: font-lock broken · Issue #324 · polymode/polymode · GitHub), and it seems to work ok:

(define-hostmode poly-nix-hostmode
  :mode 'nix-mode)
(define-auto-innermode poly-nix-dynamic-innermode
  :head-matcher "/[*] +[[:alpha:]]+ +[*]/ +''$"
  :tail-matcher "^[ \t]+''"
  :mode-matcher (cons "/[*] +\\([[:alpha:]]+\\) +[*]/" 1)
  :head-mode 'host
  :tail-mode 'host)
(define-polymode poly-nix-mode
  :hostmode 'poly-nix-hostmode
  :innermodes '(poly-nix-dynamic-innermode))

(add-to-list 'auto-mode-alist '("\\.nix" . poly-nix-mode))
1 Like