Mise + Neovim Cookbook
Here are some tips for an improved mise workflow with Neovim.
Syntax highlighting
Run commands
Use Treesitter to enable syntax highlighting for the code in the run commands of your mise files. See the example here on the left side of the image:
In your neovim config, create a after/queries/toml/injections.scm file with these queries:
; extends
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content @injection.language
(#is-mise?)
(#match? @injection.language "^['\"]{3}\n*#!(/\\w+)+/env\\s+\\w+") ; multiline shebang using env
(#gsub! @injection.language "^.*#!/.*/env%s+([^%s]+).*" "%1") ; extract lang
(#offset! @injection.content 0 3 0 -3) ; rm quotes
)
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content @injection.language
(#is-mise?)
(#match? @injection.language "^['\"]{3}\n*#!(/\\w+)+\s*\n") ; multiline shebang
(#gsub! @injection.language "^.*#!/.*/([^/%s]+).*" "%1") ; extract lang
(#offset! @injection.content 0 3 0 -3) ; rm quotes
)
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content
(#is-mise?)
(#match? @injection.content "^['\"]{3}\n*.*") ; multiline
(#not-match? @injection.content "^['\"]{3}\n*#!") ; no shebang
(#offset! @injection.content 0 3 0 -3) ; rm quotes
(#set! injection.language "bash") ; default to bash
)
(pair
(bare_key) @key (#eq? @key "run")
(string) @injection.content
(#is-mise?)
(#not-match? @injection.content "^['\"]{3}") ; not multiline
(#offset! @injection.content 0 1 0 -1) ; rm quotes
(#set! injection.language "bash") ; default to bash
)To only apply the highlighting on mise files instead of all toml files, the is-mise? predicate is used. If you don't care for this distinction, the lines containing (#is-mise?) can be removed. Otherwise, make sure to also create the predicate somewhere in your neovim config.
For example, using lazy.nvim:
{
"nvim-treesitter/nvim-treesitter",
init = function()
require("vim.treesitter.query").add_predicate("is-mise?", function(_, _, bufnr, _)
local filepath = vim.api.nvim_buf_get_name(tonumber(bufnr) or 0)
local filename = vim.fn.fnamemodify(filepath, ":t")
return string.match(filename, ".*mise.*%.toml$") ~= nil
end, { force = true, all = false })
end,
},This will consider any toml file containing mise in its name as a mise file.
MISE and USAGE comments in file tasks
You can also use Treesitter to enable syntax highlighting for "#MISE and #USAGE comments in file based tasks. See the example here on the left side of the image:
In your neovim config, create a after/queries/bash/injections.scm file with these queries:
; extends
; ============================================================================
; #MISE comments - TOML injection
; ============================================================================
; This injection captures comment lines starting with "#MISE " or "#[MISE]" or
; "# [MISE]" and treats them as TOML code blocks for syntax highlighting.
;
; #MISE format
; The (#offset!) directive skips the "#MISE " prefix (6 characters) from the source
((comment) @injection.content
(#lua-match? @injection.content "^#MISE ")
(#offset! @injection.content 0 6 0 1)
(#set! injection.language "toml"))
; #[MISE] format
((comment) @injection.content
(#lua-match? @injection.content "^#%[MISE%] ")
(#offset! @injection.content 0 8 0 1)
(#set! injection.language "toml"))
; # [MISE] format
((comment) @injection.content
(#lua-match? @injection.content "^# %[MISE%] ")
(#offset! @injection.content 0 9 0 1)
(#set! injection.language "toml"))
; ============================================================================
; #USAGE comments - KDL injection
; ============================================================================
; This injection captures consecutive comment lines starting with "#USAGE " or
; "#[USAGE]" or "# [USAGE]" and treats them as a single KDL code block for
; syntax highlighting.
;
; #USAGE format
((comment) @injection.content
(#lua-match? @injection.content "^#USAGE ")
; Extend the range one byte to the right, to include the trailing newline.
; see https://github.com/neovim/neovim/discussions/36669#discussioncomment-15054154
(#offset! @injection.content 0 7 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
; #[USAGE] format
((comment) @injection.content
(#lua-match? @injection.content "^#%[USAGE%] ")
(#offset! @injection.content 0 9 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
; # [USAGE] format
((comment) @injection.content
(#lua-match? @injection.content "^# %[USAGE%] ")
(#offset! @injection.content 0 10 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
; NOTE: on neovim >= 0.12, you can use the multi node pattern instead of
; combining injections:
;
; ((comment)+ @injection.content
; (#lua-match? @injection.content "^#USAGE ")
; (#offset! @injection.content 0 7 0 1)
; (#set! injection.language "kdl"))
;
; this is the preferred way as combined injections have multiple
; limitations:
; https://github.com/neovim/neovim/issues/32635The same queries work as is for all languages that use # as a comment delimiter. Due to TS injections being per language, you need to put the same queries to the language specific query files. For example, put them to after/queries/python/injections.scm to enable them for Python in addition to bash.
For languages that use // as a comment delimiter, you need to modify the queries a bit:
((comment) @injection.content
(#lua-match? @injection.content "^//MISE ")
(#offset! @injection.content 0 7 0 1)
(#set! injection.language "toml"))
((comment) @injection.content
(#lua-match? @injection.content "^//%[MISE%] ")
(#offset! @injection.content 0 9 0 1)
(#set! injection.language "toml"))
((comment) @injection.content
(#lua-match? @injection.content "^// %[MISE%] ")
(#offset! @injection.content 0 10 0 1)
(#set! injection.language "toml"))
((comment) @injection.content
(#lua-match? @injection.content "^//USAGE ")
(#offset! @injection.content 0 8 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
((comment) @injection.content
(#lua-match? @injection.content "^//%[USAGE%] ")
(#offset! @injection.content 0 10 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))
((comment) @injection.content
(#lua-match? @injection.content "^// %[USAGE%] ")
(#offset! @injection.content 0 11 0 1)
(#set! injection.combined)
(#set! injection.language "kdl"))Enable LSP for embedded lang in run commands
Use otter.nvim to enable LSP features and code completion for code embedded in your mise files.
Again using lazy.nvim:
{
"jmbuhr/otter.nvim",
dependencies = {
"nvim-treesitter/nvim-treesitter",
},
config = function()
vim.api.nvim_create_autocmd({ "FileType" }, {
pattern = { "toml" },
group = vim.api.nvim_create_augroup("EmbedToml", {}),
callback = function()
require("otter").activate()
end,
})
end,
},This will only work if the TS injection queries are also set up.