A context dispatched newline. One insert mode key (default <S-CR>) does the
right thing for where the cursor is:
\\ in a matrix, \\ then &= in align, \item in a list)
Rules are plain data. Detection is Treesitter based for LaTeX (no VimTeX
dependency) and Lua pattern based for line shapes. Extend any filetype through
setup() opts.
vim.keycode, and vim.treesitter for the latex preset)latex Treesitter parser, for the latex preset<S-CR> distinctly from <CR> (Kitty, WezTerm, iTerm2
with the CSI u profile, most GUIs). Otherwise set a different key, or set
key = false and drive dispatch() from your own mapping.Run :checkhealth smart_enter to confirm the key, the parser, and the
configured filetypes.
Call require("smart_enter").setup{} once:
require("smart_enter").setup({
key = "<S-CR>", -- insert mode map; false to skip and drive dispatch() yourself
fallback = "newline", -- "newline" (no comment leader) | "cr" | false | function(ctx)
filetypes = {
markdown = { preset = "markdown" },
tex = { preset = "latex" },
latex = { preset = "latex" },
},
})
With lazy.nvim, the opts table is
lazy's way of passing that config, and config calls setup:
{ "Chiarandini/smart-enter.nvim", event = "InsertEnter",
opts = { filetypes = { markdown = { preset = "markdown" }, tex = { preset = "latex" } } },
config = function(_, opts) require("smart_enter").setup(opts) end }
filetypes[ft] is { preset = <name|list>, rules = {...} }. Your rules are
tried before the preset's, so they win:
-- adds a rule without touching the latex preset
require("smart_enter").setup({ filetypes = { tex = { rules = {
{ env = "exercise", item = "\\Question " },
} } } })
A ["*"] filetype applies its rules to every filetype, after that filetype's
own rules. Use it for behaviour you want everywhere, for example continuing a
comment leader read from commentstring.
Insert fixed text around the break with append and prefix. The new line is
indented to the current line, then prefix; append is glued to the current
line at the cursor (the row separator):
{ env = "align", append = "\\\\", prefix = "&= " } -- match one environment
{ pattern = "^%s*> ", prefix = "> " } -- match on the line text
Continue a list with item. The new entry lines up with the current entry,
not with the deeper indent of a soft wrapped continuation line, and pressing on
an empty entry clears the marker and exits the list. item is a string, or a
table with exit_empty and counter:
{ envs = { "itemize", "enumerate" }, item = "\\item " }
{ env = "exercise", item = "\\Question " }
{ env = "notes", item = { text = "\\item ", exit_empty = false } }
{ env = "steps", item = { text = "\\item[{})] ", counter = "alpha" } } -- a), b), c)
counter is "arabic", "alpha"/"Alpha", or "roman"/"Roman"; the {}
in text becomes the previous entry's counter plus one.
Run your own logic with handle for anything the fields above do not cover.
It returns false to fall through to the next rule; anything else (including
nil) counts as handled:
{ pattern = "^(%s*)(%d+)%.%s(.*)", handle = function(ctx, caps)
ctx.split("", caps[1] .. tostring(tonumber(caps[2]) + 1) .. ". ")
end }
The ctx object exposes ctx.split(append, prefix), ctx.replace_line(text),
and the fields buf, win, row, col, line, ws, filetype.
The env, pattern, and item fields cover the common rules. For anything
else, two modules give building blocks for a rule's match and handle:
require("smart_enter.matchers"): pattern(lua_pat), ts_env(names[, lang]),
and env_chain(ctx[, lang]) (enclosing environment names, innermost first).require("smart_enter.actions"): item(template), the handler behind the
item field.Both are required, so call them where setup runs, for example inside a
config function, the same as any Lua module loaded at startup.
<CR>If you want the smart behaviour on <CR> and already map <CR> for autopairs
or completion, keep key = false and compose in your own map. dispatch({ fallback = false })
runs the rules, skips the built in fallback, and reports whether a rule matched:
vim.keymap.set("i", "<CR>", function()
if require("smart_enter").dispatch({ fallback = false }) then
return
end
-- no rule matched: run your existing <CR> behaviour here
end)
smart-enter stays small on purpose. These belong to dedicated plugins:
<Tab> and <S-Tab> indent and dedent of list items.o and O continuation. You can bind dispatch() to any key.require("smart_enter").setup(opts)require("smart_enter").dispatch(opts?): run the smart action; returns true
when a rule handled it. opts.fallback = false skips the built in fallback so
you can compose with your own <CR> behaviour. Bind it from any key.require("smart_enter").inspect([lang]): report the filetype, the Treesitter
environment chain, and which configured rules match at the cursor. Run it
where the key behaves unexpectedly to see what the rules see.