From a47be3adf9283e7c2c21ee3e6f5f5643ebd5ff1c Mon Sep 17 00:00:00 2001 From: prescientmoon Date: Thu, 14 Mar 2024 07:48:19 +0100 Subject: [PATCH] Start working on a more modern latex snippet system --- home/features/neovim/config/ftplugin/tex.lua | 4 +- home/features/neovim/config/lua/my/init.lua | 9 + .../neovim/config/lua/my/snippets/tex.lua | 263 ++++++++++++++++++ home/features/neovim/default.nix | 12 +- .../neovim/snippets/snippets/latex/core.json | 185 ------------ 5 files changed, 283 insertions(+), 190 deletions(-) create mode 100644 home/features/neovim/config/lua/my/snippets/tex.lua diff --git a/home/features/neovim/config/ftplugin/tex.lua b/home/features/neovim/config/ftplugin/tex.lua index 9cd7196..940199b 100644 --- a/home/features/neovim/config/ftplugin/tex.lua +++ b/home/features/neovim/config/ftplugin/tex.lua @@ -7,7 +7,7 @@ vim.opt.conceallevel = 0 local abbreviations = { -- Other fancy symvols - { "tmat", "^T" }, -- Tranpose of a matrix + { "tmat", "^T" }, -- Transpose of a matrix { "cmat", "^*" }, -- Conjugate of a matrix { "sneg", "^C" }, -- Set complement { "ortco", "^\\bot" }, -- Orthogonal complement @@ -79,7 +79,7 @@ local abbreviations = { local abolishAbbreviations = { -- {{{ Special chars - -- System for writing special characters which need to also be easly + -- System for writing special characters which need to also be easily -- accessible as {sub/super}scripts. -- -- The reason epsilon and lambda are separated out from everything else in diff --git a/home/features/neovim/config/lua/my/init.lua b/home/features/neovim/config/lua/my/init.lua index 4e3b81b..bfa30cc 100644 --- a/home/features/neovim/config/lua/my/init.lua +++ b/home/features/neovim/config/lua/my/init.lua @@ -9,6 +9,15 @@ function M.setup() require("my.keymaps").setup() require("my.lazy").setup() tempest.configureMany(nix.post) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "tex", + group = vim.api.nvim_create_augroup("luasnip-latex-snippets", {}), + once = true, + callback = function() + require("my.snippets.tex").setup() + end, + }) end return M diff --git a/home/features/neovim/config/lua/my/snippets/tex.lua b/home/features/neovim/config/lua/my/snippets/tex.lua new file mode 100644 index 0000000..baaa37f --- /dev/null +++ b/home/features/neovim/config/lua/my/snippets/tex.lua @@ -0,0 +1,263 @@ +local M = {} + +local ls = require("luasnip") +local extras = require("luasnip.extras") +local conds = require("luasnip.extras.expand_conditions") +local t = ls.text_node +local i = ls.insert_node +local d = ls.dynamic_node +local f = ls.function_node +local sn = ls.snippet_node +local fmt = require("luasnip.extras.fmt").fmt +local n = extras.nonempty + +local autosnippets = {} + +-- {{{ Helpers +local function unlines(...) + return table.concat({ ... }, "\n") +end + +local function flatten(arr) + local result = {} + for _, subarray in ipairs(arr) do + if type(subarray) == "table" then + for _, value in ipairs(subarray) do + table.insert(result, value) + end + else + table.insert(result, subarray) + end + end + return result +end + +local function trig(name) + return { name = name, trig = name } +end +-- }}} +-- {{{ Math mode detection +-- Taken from: https://github.com/iurimateus/luasnip-latex-snippets.nvim/blob/main/lua/luasnip-latex-snippets/util/ts_utils.lua +local MATH_NODES = { + displayed_equation = true, + inline_formula = true, + math_environment = true, +} + +local TEXT_NODES = { + text_mode = true, + label_definition = true, + label_reference = true, +} + +local function get_node_at_cursor() + local pos = vim.api.nvim_win_get_cursor(0) + -- Subtract one to account for 1-based row indexing in nvim_win_get_cursor + local row, col = pos[1] - 1, pos[2] + + local parser = vim.treesitter.get_parser(0, "latex") + if not parser then + return + end + + local root_tree = parser:parse({ row, col, row, col })[1] + local root = root_tree and root_tree:root() + if not root then + return + end + + return root:named_descendant_for_range(row, col, row, col) +end + +local function in_text(check_parent) + local node = get_node_at_cursor() + while node do + if node:type() == "text_mode" then + if check_parent then + -- For \text{} + local parent = node:parent() + if parent and MATH_NODES[parent:type()] then + return false + end + end + + return true + elseif MATH_NODES[node:type()] then + return false + end + node = node:parent() + end + return true +end + +local function in_mathzone() + local node = get_node_at_cursor() + while node do + if TEXT_NODES[node:type()] then + return false + elseif MATH_NODES[node:type()] then + return true + end + node = node:parent() + end + return false +end + +local function not_math() + return in_text(true) +end +-- }}} +-- {{{ Start of line & non-math +local beginTextCondition = function(...) + return conds.line_begin(...) and not_math() +end + +local parseBeginText = ls.extend_decorator.apply(ls.parser.parse_snippet, { + condition = beginTextCondition, +}) --[[@as function]] + +local snipBeginText = ls.extend_decorator.apply(ls.snippet, { + condition = beginTextCondition, +}) --[[@as function]] + +local function env(name) + return "\\begin{" .. name .. "}\n\t$0\n\\end{" .. name .. "}" +end + +local function optional_square_bracket_arg(index, default) + return sn(index, { + n(1, "[", ""), + i(1, default), + n(1, "]", ""), + }) +end + +local function theorem_env(name, prefix) + return snipBeginText(trig(name), { + t("\\begin{" .. name .. "}"), + optional_square_bracket_arg(1), + n(2, "\\label{" .. prefix .. ":", ""), + i(2), + n(2, "}", ""), + t({ "", "\t" }), + i(0), + t({ "", "\\end{" .. name .. "}" }), + }) +end + +vim.list_extend(autosnippets, { + parseBeginText( + { trig = "begin", name = "Begin / end environment" }, + env("$1") + ), + -- {{{ Chapters / sections + parseBeginText(trig("chapter"), "\\chapter{$1}\n$0"), + parseBeginText(trig("section"), "\\section{$1}\n$0"), + parseBeginText(trig("subsection"), "\\subsection{$1}\n$0"), + parseBeginText(trig("subsubsection"), "\\subsubsection{$1}\n$0"), + -- }}} + -- {{{ Lists + parseBeginText({ trig = "item", name = "List item" }, "\\item"), + parseBeginText({ trig = "olist", name = "Ordered list" }, env("enumerate")), + parseBeginText({ trig = "ulist", name = "Unordered list" }, env("itemize")), + -- }}} + -- {{{ Theorem envs + theorem_env("theorem", "thm"), + theorem_env("lemma", "lem"), + theorem_env("exercise", "exe"), + theorem_env("definition", "def"), + theorem_env("corollary", "cor"), + theorem_env("example", "exa"), + snipBeginText(trig("proof"), { + t("\\begin{proof}"), + optional_square_bracket_arg(1), + t({ "", "\t" }), + i(0), + t({ "", "\\end{proof}" }), + }), + -- }}} + -- {{{ Special structures + parseBeginText( + { trig = "ciff", name = "If and only if cases" }, + unlines( + "\\begin{enumerate}", + "\t\\item[$\\implies$]$1", + "\t\\item[$\\impliedby$]$2", + "\\end{enumerate}", + "$0" + ) + ), + -- }}} +}) +-- }}} +-- {{{ Non-math +local parseText = ls.extend_decorator.apply(ls.parser.parse_snippet, { + condition = not_math, +}) --[[@as function]] + +local snipText = ls.extend_decorator.apply(ls.snippet, { + condition = not_math, +}) --[[@as function]] + +local function ref(name, prefix) + return { + parseText( + { trig = "r" .. name, name = name .. " reference" }, + "\\ref{" .. prefix .. ":$1}$0" + ), + parseText( + { trig = "pr" .. name, name = name .. " reference" }, + "(\\ref{" .. prefix .. ":$1})$0" + ), + } +end + +vim.list_extend( + autosnippets, + flatten({ + -- {{{ References + ref("theorem", "thm"), + ref("lemma", "lem"), + ref("exercise", "exe"), + ref("definition", "def"), + ref("corollary", "cor"), + ref("example", "exa"), + { + parseText({ trig = "ref", name = "reference" }, "\\ref{$1}$0"), + parseText({ trig = "pref", name = "reference" }, "(\\ref{$1})$0"), + }, + -- }}} + { + -- {{{ Misc + parseText(trig("quote"), "``$1''$0"), + parseText(trig("forcecr"), "{\\ \\\\\\\\}"), + -- }}} + -- {{{ Let ... + snipText( + { trig = "([Ll]et)", trigEngine = "pattern", name = "definition" }, + { + f(function(_, snip) + return snip.captures[1] + end), + t(" "), + sn(1, fmt("${} ≔ {}$", { i(1), i(2) })), + } + ), + -- }}} + -- {{{ Display / inline math + parseText({ trig = "dm", name = "display math" }, env("align*")), + parseText({ trig = "im", name = "inline math" }, "\\$$1\\$$0"), + -- }}} + }, + }) +) +-- }}} + +function M.setup() + ls.add_snippets("tex", autosnippets, { + type = "autosnippets", + default_priority = 0, + }) +end + +return M diff --git a/home/features/neovim/default.nix b/home/features/neovim/default.nix index 9a24788..d6df236 100644 --- a/home/features/neovim/default.nix +++ b/home/features/neovim/default.nix @@ -846,20 +846,26 @@ let # {{{ luasnip # snippeting engine luasnip = - let reload = /* lua */ ''require("luasnip.loaders.from_vscode").lazy_load()''; + let reloadVscode = /* lua */ ''require("luasnip.loaders.from_vscode").lazy_load()''; in { package = "L3MON4D3/LuaSnip"; version = "v2"; cond = blacklist "vscode"; - config = thunk reload; + config = thunk '' + require("luasnip").config.setup(${encode { + enable_autosnippets = true; + update_events = ["TextChanged" "TextChangedI"]; + }}) + ${reloadVscode} + ''; # {{{ Keybinds keys = [ { mapping = "rs"; - action = thunk reload; + action = thunk reloadVscode; desc = "[R]eload [s]nippets"; } { diff --git a/home/features/neovim/snippets/snippets/latex/core.json b/home/features/neovim/snippets/snippets/latex/core.json index f17733e..0149180 100644 --- a/home/features/neovim/snippets/snippets/latex/core.json +++ b/home/features/neovim/snippets/snippets/latex/core.json @@ -1,13 +1,4 @@ { - "Begin": { - "prefix": "begin", - "description": "Begin anything", - "body": [ - "\\begin{$1}", - "\t$0", - "\\end{$1}" - ] - }, "Set": { "prefix": "set", "description": "Set I guess", @@ -38,69 +29,6 @@ "description": "Inner product of a vector with itself", "body": "\\dprod{$1}$0" }, - "Lemma": { - "prefix": "lemma", - "description": "Create a lemma", - "body": [ - "\\begin{lemma}[$1]\\label{lem:$1}", - "\t$0", - "\\end{lemma}" - ] - }, - "Example*": { - "prefix": "example*", - "description": "Create an example*", - "body": [ - "\\begin{example*}", - "\t$0", - "\\end{example*}" - ] - }, - "Example": { - "prefix": "example", - "description": "Create an example", - "body": [ - "\\begin{example}[$1]\\label{exp:$1}", - "\t$0", - "\\end{example}" - ] - }, - "Theorem": { - "prefix": "theorem", - "description": "Create a theorem", - "body": [ - "\\begin{theorem}[$1]\\label{thm:$1}", - "\t$0", - "\\end{theorem}" - ] - }, - "Exercise": { - "prefix": "exercise", - "description": "Create a exercise", - "body": [ - "\\begin{exercise}[$1]\\label{exe:$1}", - "\t$0", - "\\end{exercise}" - ] - }, - "Definition": { - "prefix": "definition", - "description": "Create a definition", - "body": [ - "\\begin{definition}[$1]\\label{def:$1}", - "\t$0", - "\\end{definition}" - ] - }, - "Display math": { - "prefix": "dm", - "description": "Display math section", - "body": [ - "\\[", - "$0", - "\\]" - ] - }, "Subscript": { "prefix": "ss", "description": "Subscript", @@ -126,80 +54,6 @@ "description": "The set of Z/nZ", "body": "\\mathbb{Z}/$1\\mathbb{Z}$0" }, - "Section": { - "prefix": "section", - "description": "Add section", - "body": [ - "\\section{$1}", - "$0" - ] - }, - "Subsection": { - "prefix": "subsection", - "description": "Add subsection", - "body": [ - "\\subsection{$1}", - "$0" - ] - }, - "Subsubsection": { - "prefix": "subsubsection", - "description": "Add subsubsection", - "body": [ - "\\subsubsection{$1}", - "$0" - ] - }, - "Chapter": { - "prefix": "chapter", - "description": "Add chapter", - "body": [ - "\\chapter{$1}", - "$0" - ] - }, - "Proof": { - "prefix": "proof", - "description": "Create proof", - "body": [ - "\\begin{proof}", - "\t$0", - "\\end{proof}" - ] - }, - "Itemize": { - "prefix": "item", - "body": [ - "\\\\begin{itemize}", - "\t\\item $0", - "\\\\end{itemize}" - ], - "description": "Itemize env" - }, - "Enumerate": { - "prefix": "enum", - "body": [ - "\\\\begin{enumerate}", - "\t\\item $0", - "\\\\end{enumerate}" - ], - "description": "Enumerate env" - }, - "Reference definition": { - "prefix": "rdef", - "description": "Reference a definition", - "body": "\\ref{def:$1}$0" - }, - "Reference lemma": { - "prefix": "rlemma", - "description": "Reference a lemma", - "body": "\\ref{lem:$1}$0" - }, - "Reference theorem": { - "prefix": "rtheorem", - "description": "Reference a theorem", - "body": "\\ref{thm:$1}$0" - }, "Sigma sum": { "prefix": "bsum", "description": "Create a sum using sigma notation", @@ -260,34 +114,11 @@ "description": "Create a ln call", "body": "\\ln($1)$0" }, - "Aligned": { - "prefix": "aligned", - "description": "Create an aligned environment", - "body": [ - "\\begin{aligned}", - "\t$0", - "\\end{aligned}" - ] - }, "Let": { "prefix": "let", "description": "Let something equal something else", "body": "Let $$1 = $2$. $0" }, - "Force newline": { - "prefix": "cr", - "description": "Force newline in math mode", - "body": "{\\ \\\\\\\\}" - }, - "Aligned display math": { - "prefix": "maligned", - "description": "Create an aligned display math environment", - "body": [ - "\\begin{align*}", - "\t$0", - "\\end{align*}" - ] - }, "System of equations": { "prefix": "eqsystem", "description": "Create a system of equations", @@ -358,21 +189,5 @@ "prefix": "integral", "description": "Integral", "body": "\\int $1 d${2:x}$0" - }, - "Iff cases": { - "prefix": "ciff", - "description": "Prove an equivalence in both directions", - "body": [ - "\\begin{enumerate}", - "\t\\item[$\\implies$]$1", - "\t\\item[$\\impliedby$]$2", - "\\end{enumerate}", - "$0" - ] - }, - "quote": { - "prefix": "quote", - "description": "Quote a bunch of text", - "body": "``$1''$0" } }