From a47be3adf9283e7c2c21ee3e6f5f5643ebd5ff1c Mon Sep 17 00:00:00 2001
From: prescientmoon <git@moonythm.dev>
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 = "<leader>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"
   }
 }