Highlighting Parts of Lua as Bash

Table of Contents

Tags:

Since i am currently working on a little tool for synching my old macbook pro, my workstation at home and my work thinkpad - I want to highlight a part of the lua configuration for said tool as bash, not as a lua string.

I use this tool (mehr2) to keep my installed packages on all systems in sync. I plan to use the following configuration file:

LUA
 1MEHR2 = {
 2    packages = {
 3        default = {
 4            "git",
 5            "picom",
 6            "fish",
 7            "imagemagick",
 8            "firefox",
 9            "flameshot",
10            "pipewire",
11            "dunst",
12            "rofi",
13            "i3",
14            "acpi",
15            "zathura",
16            "curl",
17        },
18        apt = { "build-essentials" },
19        pacman = { "base-devel", "pamixer", "hugo", "go", "ghostty" },
20        scratch = {
21            {
22                identifier = "rustup",
23                needs = { "curl" },
24                update = "rustup update", 
25                script = [[
26                    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
27                    rustup component add rust-docs
28                    rustup component add cargo
29                    rustup component add clippy
30                    rustup component add rustfmt
31                ]]
32                }
33            },
34            {
35                -- see: https://github.com/neovim/neovim/blob/master/BUILD.md
36                identifier = "nvim",
37                git = "github.com/neovim/neovim",
38                needs = { "make", "cmake", "gcc" },
39                branch = "nightly",
40                script = [[
41                    make CMAKE_BUILD_TYPE=Release
42                    make install
43                ]]
44                }
45            },
46            {
47                -- see: https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager
48                identifier = "zig",
49                execute_for = { "apt" },
50                update = "snap refresh zig",
51                needs = { "snap" },
52                script = "snap install zig --classic --beta"
53            },
54            {
55                -- see: https://ghostty.org/docs/install/build
56                identifier = "ghostty",
57                execute_for = { "apt" },
58                git = "github.com/ghostty-org/ghostty",
59                needs = { "zig", "gtk4" },
60                script = "zig build -p /usr -Doptimize=ReleaseFast"
61            },
62            {
63                identifier = "go",
64                execute_for = { "apt" },
65                script = [[
66                    VERSION=$(curl -s "https://go.dev/VERSION?m=text" | head -n1)
67                    wget https://go.dev/dl/$VERSION.linux-amd64.tar.gz
68                    rm -rf /usr/local/go
69                    tar -C /usr/local -xzf $VERSION.linux-amd64.tar.gz
70                ]]
71            },
72            {
73                -- see: https://gohugo.io/installation/linux/#build-from-source
74                identifier = "hugo",
75                execute_for = { "apt" },
76                needs = { "go" },
77                script = "go install github.com/gohugoio/hugo@latest"
78            },
79        },
80        cargo = { "exa", "bat", "ripgrep", "yazi" }
81    }
82}

The default array defines packages the current package manager would install. The packages in the apt, pacman and cargo arrays are only installed if the corresponding package manager is executable on the target machine (some differ in names, depending on the package manager, so I install them specifically). However each entry in the scratch array is executed if any of the entries in the execute_for field are found on the system. The execution is done by passing the value of the script field to bash. If the execute_for field is missing, the array entry is executed every time.

Since the script field contains a bash script, i want it to be highlighted as such. Neovim and treesitter enable this exact usecase.

Neovim setup

I use packer to install treesitter:

LUA
 1-- .config/nvim/lua/teo/packer.lua
 2
 3-- packer installation
 4local fn = vim.fn
 5local install_path = fn.stdpath('data') .. '/site/pack/packer/start/packer.nvim'
 6if fn.empty(fn.glob(install_path)) > 0 then
 7    packer_bootstrap = fn.system({ 'git', 'clone', '--depth', '1', 'https://github.com/wbthomason/packer.nvim',
 8        install_path })
 9    download_result = fn.system({ 'ls', '-l', install_path })
10    print("download_result: " .. download_result)
11end
12
13vim.cmd [[packadd packer.nvim]]
14
15return require('packer').startup(function(use)
16    -- syntax highlighting and parser
17    use { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' }
18end)

:PackerSync will synchronise your neovim instance to your configuration and install treesitter.

Treesitter setup

I configure treesitter in a minimalistic way:

LUA
 1-- .config/nvim/after/plugin/treesitter.lua
 2
 3require'nvim-treesitter.configs'.setup {
 4  ensure_installed = { "c", "lua", "rust", "javascript", "css", "html", "markdown", "javascript"},
 5  sync_install = false,
 6  auto_install = true,
 7  highlight = {
 8    enable = true,
 9    additional_vim_regex_highlighting = false,
10  },
11}

Injections

Queries are S-expressions and documented here

Treesitter injections are placed at .config/nvim/queries/<filetype>/injections.scm1 and contain configurations treesitter injects into files with the corresponding filetype.

For my specific usecase I search for the script table property/field:

LISP
1; Inject into script = [[...]] as bash
2((field
3    name: (identifier) @_name
4    value: (string 
5             content: (string_content) @injection.content
6             (#eq? @_name "script")
7             (#set! injection.language "bash")
8 )))

The injection works by quering the treesitter tree (inspectable via :InspectTree) for a field where its value is a string and its name is equal to "script". If so the string content is set to be highlighted as bash:

bat_vs_nvim_with_injection