L3MON4D3 / LuaSnip

Snippet Engine for Neovim written in Lua.
Apache License 2.0
3.42k stars 242 forks source link

[ Help ] Include locals from another file into luasnips snippets files #781

Closed JxJxxJxJ closed 1 year ago

JxJxxJxJ commented 1 year ago

I use the same locals for many snippetfiles so I would like to import them in the beginning of each file using dofile (<path to locals.lua>). My file structure is like this:

/home/Jx/.config/lvim
├── config.lua
├── lua
│   └── user
│       ├── luasnips.lua
│       ├── my-things
│       │   └── luasnips
│       │       └── locals.lua
├── luasnippets
│   └── tex
│       ├── environments.lua
│       ├── in_math.lua
│       ├── in_text.lua
│       ├── locals.lua
│       ├── preamble.lua
│       └── symbols.lua

I'm returning as variables the content of the locals.lua file into dofile like this:

--- ~/.config/lvim/lua/user/my-things/luasnips/locals.lua

---@diagnostic disable: undefined-global

local in_mathzone = function()
  return vim.fn["vimtex#syntax#in_mathzone"]() == 1
end

local in_comment = function()
  return vim.fn["vimtex#syntax#in_comment"]() == 1
end

local in_text = function()
  return not in_mathzone() and not in_comment()
end

local in_align = function()
  return vim.fn["vimtex#env#is_inside"]("align")[1] ~= 0
end

local in_enumerate = function()
  return vim.fn["vimtex#env#is_inside"]("enumerate")[1] ~= 0
end

local in_itemize = function()
  return vim.fn["vimtex#env#is_inside"]("itemize")[1] ~= 0
end

local begins_line = function()
  local cur_line = vim.api.nvim_get_current_line()
  -- Checks if the current line consists of whitespace and then the snippet
  -- TODO: Fix limitation that the snippet cannot contain whitespace itself
  return #cur_line == #string.match(cur_line, "%s*[^%s]+")
end

local get_env = function(name)
  return {
    t({ "\\begin{" .. name .. "}", "\t" }),
    i(0),
    t({ "", "\\end{" .. name .. "}" }),
  }
end

return {
  in_mathzone = in_mathzone,
  in_comment = in_comment,
  in_text = in_text,
  in_align = in_align,
  in_enumerate = in_enumerate,
  in_itemize = in_itemize,
  begins_line = begins_line,
  -- get_env = get_env,
  -- add more functions here as needed
}

And using them in for example my in_math.lua snippets file

--- ~/.conifg/lvim/luasnippets/tex/in_math.lua

---@diagnostic disable: undefined-global

-- load the variables file

dofile('../../lua/user/my-things/luasnips/locals.lua')

-- local in_mathzone = function()
--   return vim.fn["vimtex#syntax#in_mathzone"]() == 1
-- end

-- local in_comment = function()
--   return vim.fn["vimtex#syntax#in_comment"]() == 1
-- end

-- local in_text = function()
--   return not in_mathzone() and not in_comment()
-- end

-- local in_align = function()
--   return vim.fn["vimtex#env#is_inside"]("align")[1] ~= 0
-- end

-- local in_enumerate = function()
--   return vim.fn["vimtex#env#is_inside"]("enumerate")[1] ~= 0
-- end

-- local in_itemize = function()
--   return vim.fn["vimtex#env#is_inside"]("itemize")[1] ~= 0
-- end

-- local begins_line = function()
--   local cur_line = vim.api.nvim_get_current_line()
--   -- Checks if the current line consists of whitespace and then the snippet
--   -- TODO: Fix limitation that the snippet cannot contain whitespace itself
--   return #cur_line == #string.match(cur_line, "%s*[^%s]+")
-- end

-- local get_env = function(name)
--   return {
--     t({ "\\begin{" .. name .. "}", "\t" }),
--     i(0),
--     t({ "", "\\end{" .. name .. "}" }),
--   }
-- end

local GREEK_LETTERS = {}
GREEK_LETTERS["a"] = "alpha"
GREEK_LETTERS["b"] = "beta"
GREEK_LETTERS["d"] = "delta"
GREEK_LETTERS["e"] = "eps"
GREEK_LETTERS["g"] = "gamma"
GREEK_LETTERS["l"] = "lam"
GREEK_LETTERS["o"] = "omega"
GREEK_LETTERS["s"] = "sigma"
GREEK_LETTERS["t"] = "tau"

return {

  -- easy differentials in mathzone
  s({ trig = "df", snippetType = "autosnippet" },
    { t("\\diff") },
    { condition = locals.in_enumerate }
  ),

  -- langles rangles
  s({ trig = 'lrangle', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\langle{ <> }\rangle <> ]],
      {
        i(1),
        i(0)
      }
    ),
    { condition = locals.in_mathzone }
  ),

  -- ff -> \frac{i(1)}{i(2)}
  s({ trig = '([^%a])ff', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[<>\frac{<>}{<>} ]],
      {
        f(function(_, snip) return snip.captures[1] end),
        i(1),
        i(2)
      }
    ),
    { condition = locals.in_mathzone }
  ),

  -- LaTeX: Matrices
  s({ trig = "([pbv])mat_(%d)(%d)", regTrig = true, snippetType = "autosnippet" }, {
    d(1, function(_, snip)
      local type = snip.captures[1] .. "matrix"
      local rows, cols = snip.captures[2], snip.captures[3]
      local nodes = {}
      local ts = 1
      table.insert(nodes, t("\\begin{" .. type .. "}[rrrrrrrrrr]"))
      for _ = 1, rows, 1 do
        table.insert(nodes, t({ "", "\t" }))
        for _ = 1, cols, 1 do
          table.insert(nodes, i(ts))
          table.insert(nodes, t(" & "))
          ts = ts + 1
        end
        table.remove(nodes, #nodes)
        table.insert(nodes, t(" \\\\"))
      end
      table.remove(nodes, #nodes)
      table.insert(nodes, t({ "", "\\end{" .. type .. "}" }))
      return sn(1, nodes)
    end),
  }, { condition = locals.in_mathzone }),

  -- LaTeX: Lims and stuff
  s({ trig = 'Lim', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\lim_{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = locals.in_mathzone }),
  s({ trig = 'Sum', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\sum_{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = locals.in_mathzone }),

  s({ trig = 'Prod', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\prod_{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = locals.in_mathzone }),

  s({ trig = 'Int', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\int{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = locals.in_mathzone }),

  s({ trig = 'ssum', regTrig = false, wordTrig = true, snippetType = "autosnippet" }, { t("\\sum ") },
    { condition = locals.in_mathzone }),

  s({ trig = 'sprod', regTrig = false, wordTrig = true, snippetType = "autosnippet" }, { t("\\prod ") },
    { condition = locals.in_mathzone }),

  -- LaTeX: easy () {}
  s({ trig = '()', regTrig = false, wordTrig = true, snippetType = "snippet" },
    fmta(
      [[\paren{ <> } <>]],
      {
        i(1),
        i(0),
      }
    )
  ),

  s({ trig = '{}', regTrig = false, wordTrig = true, snippetType = "snippet" },
    fmta(
      [[\cbrack{ <> } <>]],
      {
        i(1),
        i(0),
      }
    )
  ),

  -- LaTeX: Single-digit subscripts
  s(
    { trig = "([^%a])([abcvwxyz])([ijkn%d])", regTrig = true, wordTrig = false,
      snippetType = "autosnippet" },
    f(function(_, snip)
      return snip.captures[1] .. " " .. snip.captures[2] .. "_" .. snip.captures[3] .. " "
    end),
    { condition = locals.in_mathzone }
  ),

  -- LaTeX: Math subscripts and superscripts
  s({ trig = "([abcvwxyz])([%_%^])", regTrig = true, snippetType = "autosnippet", wordTrig = false }, {
    f(function(_, snip)
      return snip.captures[1] .. snip.captures[2] .. "{"
    end),
    i(1),
    t("} "),
  }, { condition = locals.in_mathzone }),

  -- LaTeX: Math exponents
  s({ trig = "^", wordTrig = false, snippetType = "autosnippet" }, {
    t("^{-"),
    i(1),
    t("}"),
  }, { condition = locals.in_mathzone }),

  -- LaTeX: Math boldface
  s("bf", fmt([[\mathbf{{{}}}]], i(1)), { condition = locals.in_mathzone }),

  -- LaTeX: Romanized math
  s("rm", fmt([[\mathrm{{{}}}]], i(1)), { condition = locals.in_mathzone }),

  -- LaTeX: Math calligraphy
  s("mcal", fmt([[\mathcal{{{}}}]], i(1)), { condition = locals.in_mathzone }),

  -- LaTeX: Math script
  s("mscr", fmt([[\mathscr{{{}}}]], i(1)), { condition = locals.in_mathzone }),

  -- LaTeX: Math text
  s({ trig = "tt", wordTrig = true, snippetType = "autosnippet" }, fmt([[\text{{ {} }}]], i(1)),
    { condition = locals.in_mathzone }),

  -- LaTeX: Square root
  s({ trig = "sqrt", snippetType = "autosnippet", wordTrig = false }, {
    t("\\sqrt[2]{"),
    i(1),
    t("}"),
  }, { condition = locals.in_mathzone }),

  -- LaTeX: Vector
  s({ trig = "vec", snippetType = "autosnippet", wordTrig = false }, {
    t("\\vec{"),
    i(1),
    t("}"),
  }, { condition = locals.in_mathzone }),
  -- LaTeX: bar
  s(
    {
      trig = "(%a)bar",
      wordTrig = false,
      regTrig = true,
      name = "bar",
      priority = 100,
      snippetType = "autosnippet",
    },
    f(function(_, snip)
      return string.format("\\overline{%s}", snip.captures[1])
    end, {}),
    { condition = locals.in_mathzone }),

  -- LaTeX: hat
  s(
    {
      trig = "(%a)hat",
      wordTrig = false,
      regTrig = true,
      name = "hat",
      priority = 100,
      snippetType = "autosnippet",
    },
    f(function(_, snip)
      return string.format("\\hat{%s}", snip.captures[1])
    end, {})
  ),
}

What am I doing wrong? Luasnips gives a bunch of errors and this does not work. I suspect it has to do with syntax but I'm not sure...

L3MON4D3 commented 1 year ago

Ah! dofile with a relative path might not work because we're reading the files and then run them as a function, which probably means that the current working directory is just the one where neovim is opened. An absolute path should work, or! and IMO this is the nicer option, use snip_env, which is exactly for this purpose. You might want to do something like ls.setup({snip_env = require("the_file_you_dofiled")}).

JxJxxJxJ commented 1 year ago

Thanks! But, if I put ls.setup({ snip_env = require("~/.config/lvim/lua/user/my-things/luasnips/locals.lua") }) or ls.setup({snip_env = require("../../lua/user/my-things/luasnips/locals.lua")}) in my in_math.lua file luasnips still gives errors. Is that how you mean I should use it?

In the conditions I tried using locals.in_mathzone and in_mathzone and didn't help either... But I could be missing something

L3MON4D3 commented 1 year ago

Ah, no, you'd have to call setup in your luasnip-config, outside those files.

L3MON4D3 commented 1 year ago

Oh, and maybe not require, dofile should work

JxJxxJxJ commented 1 year ago

doing ls.setup({ snip_env = require("~/.config/lvim/lua/user/my-things/luasnips/locals.lua") }) in my config.lua gives no errors, but at the same time stops luasnips from working. I can start a file but no snippet work, is it overriding 'the rest stuff'?

The same happens with dofile('~/.config/lvim/lua/user/my-things/luasnips/locals.lua') in config.lua.

L3MON4D3 commented 1 year ago

Right, that depends: if you're on the latest release, the default snip_env is not extended with what you're setting there, on master it is.

Im a bit surprised that you're not getting an error... Could you post the relevant parts of your config?

JxJxxJxJ commented 1 year ago

Will leave the ones I think matter, let me know if there's another one left

~/.config/lvim
├── config.lua
├── lua
│   └── user
│       ├── luasnips.lua
│       ├── my-things
│       │   └── luasnips
│       │       └── locals.lua
├── luasnippets
│   └── tex
│       ├── environments.lua
│       ├── in_math.lua
│       ├── in_text.lua
│       ├── locals.lua
│       ├── preamble.lua
│       └── symbols.lua

In ~/.config/lvim/config.lua I just load the ~/.config/lvim/lua/user/luasnips.lua file

-- ~/.config/lvim/config.lua
reload("user.luasnips") -- luasnippets related configs

Then this is what there's in that file

-- ~/.config/lvim/lua/user/luasnips.lua
---@diagnostic disable: undefined-global

-- -- load the variables file
dofile('~/.config/lvim/lua/user/my-things/luasnips/locals.lua')

-- Locals for my snippets
-- ls.setup({ snip_env = require("~/.config/lvim/lua/user/my-things/luasnips/locals.lua") })

-- Somewhere in your Neovim startup, e.g. init.lua
require("luasnip").config.set_config({ -- Setting LuaSnip config

  -- Enable autotriggered snippets
  enable_autosnippets = true,

  -- Enable jump back into older snippets
  history = true,

  -- Use Tab (or some other key if you prefer) to trigger visual selection
  store_selection_keys = "<Tab>",

  -- Update text in repeated nodes lively, not after confirming its content
  update_events = 'TextChanged,TextChangedI',
})

-- Keybindings for choice nodes
-- set keybinds for both INSERT and VISUAL.
vim.api.nvim_set_keymap("i", "<Tab>", "<Plug>luasnip-next-choice", {})
vim.api.nvim_set_keymap("s", "<Tab>", "<Plug>luasnip-next-choice", {})
-- vim.api.nvim_set_keymap("i", "<C-k>", "<Plug>luasnip-prev-choice", {})
-- vim.api.nvim_set_keymap("s", "<C-k>", "<Plug>luasnip-prev-choice", {})

local current_nsid = vim.api.nvim_create_namespace("LuaSnipChoiceListSelections")
local current_win = nil

local function window_for_choiceNode(choiceNode)
  local buf = vim.api.nvim_create_buf(false, true)
  local buf_text = {}
  local row_selection = 0
  local row_offset = 0
  local text
  for _, node in ipairs(choiceNode.choices) do
    text = node:get_docstring()
    -- find one that is currently showing
    if node == choiceNode.active_choice then
      -- current line is starter from buffer list which is length usually
      row_selection = #buf_text
      -- finding how many lines total within a choice selection
      row_offset = #text
    end
    vim.list_extend(buf_text, text)
  end

  vim.api.nvim_buf_set_text(buf, 0, 0, 0, 0, buf_text)
  local w, h = vim.lsp.util._make_floating_popup_size(buf_text)

  -- adding highlight so we can see which one is been selected.
  local extmark = vim.api.nvim_buf_set_extmark(buf, current_nsid, row_selection, 0,
    { hl_group = 'incsearch', end_line = row_selection + row_offset })

  -- shows window at a beginning of choiceNode.
  local win = vim.api.nvim_open_win(buf, false, {
    relative = "win", width = w, height = h, bufpos = choiceNode.mark:pos_begin_end(), style = "minimal",
    border = 'rounded'
  })

  -- return with 3 main important so we can use them again
  return { win_id = win, extmark = extmark, buf = buf }
end

function choice_popup(choiceNode)
  -- build stack for nested choiceNodes.
  if current_win then
    vim.api.nvim_win_close(current_win.win_id, true)
    vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)
  end
  local create_win = window_for_choiceNode(choiceNode)
  current_win = {
    win_id = create_win.win_id,
    prev = current_win,
    node = choiceNode,
    extmark = create_win.extmark,
    buf = create_win.buf
  }
end

function update_choice_popup(choiceNode)
  vim.api.nvim_win_close(current_win.win_id, true)
  vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)
  local create_win = window_for_choiceNode(choiceNode)
  current_win.win_id = create_win.win_id
  current_win.extmark = create_win.extmark
  current_win.buf = create_win.buf
end

function choice_popup_close()
  vim.api.nvim_win_close(current_win.win_id, true)
  vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)
  -- now we are checking if we still have previous choice we were in after exit nested choice
  current_win = current_win.prev
  if current_win then
    -- reopen window further down in the stack.
    local create_win = window_for_choiceNode(current_win.node)
    current_win.win_id = create_win.win_id
    current_win.extmark = create_win.extmark
    current_win.buf = create_win.buf
  end
end

vim.cmd([[
augroup choice_popup
au!
au User LuasnipChoiceNodeEnter lua choice_popup(require("luasnip").session.event_node)
au User LuasnipChoiceNodeLeave lua choice_popup_close()
au User LuasnipChangeChoice lua update_choice_popup(require("luasnip").session.event_node)
augroup END
]])

Then like this if I open a .tex file there are no errors but also snippets don't work (they also don't appear in cmp window).

This is the content of ~/.config/lvim/lua/user/my-things/luasnips/locals.lua

-- ~/.config/lvim/lua/user/my-things/luasnips/locals.lua
---@diagnostic disable: undefined-global

local in_mathzone = function()
  return vim.fn["vimtex#syntax#in_mathzone"]() == 1
end

local in_comment = function()
  return vim.fn["vimtex#syntax#in_comment"]() == 1
end

local in_text = function()
  return not in_mathzone() and not in_comment()
end

local in_align = function()
  return vim.fn["vimtex#env#is_inside"]("align")[1] ~= 0
end

local in_enumerate = function()
  return vim.fn["vimtex#env#is_inside"]("enumerate")[1] ~= 0
end

local in_itemize = function()
  return vim.fn["vimtex#env#is_inside"]("itemize")[1] ~= 0
end

local begins_line = function()
  local cur_line = vim.api.nvim_get_current_line()
  -- Checks if the current line consists of whitespace and then the snippet
  -- TODO: Fix limitation that the snippet cannot contain whitespace itself
  return #cur_line == #string.match(cur_line, "%s*[^%s]+")
end

local get_env = function(name)
  return {
    t({ "\\begin{" .. name .. "}", "\t" }),
    i(0),
    t({ "", "\\end{" .. name .. "}" }),
  }
end

return {
  in_mathzone = in_mathzone,
  in_comment = in_comment,
  in_text = in_text,
  in_align = in_align,
  in_enumerate = in_enumerate,
  in_itemize = in_itemize,
  begins_line = begins_line,
  -- get_env = get_env,
  -- add more functions here as needed
}

And I'm testing the snippets defined in ~/.config/lvim/luasnippets/tex/in_math written like this

---@diagnostic disable: undefined-global

-- local in_mathzone = function()
--   return vim.fn["vimtex#syntax#in_mathzone"]() == 1
-- end

-- local in_comment = function()
--   return vim.fn["vimtex#syntax#in_comment"]() == 1
-- end

-- local in_text = function()
--   return not in_mathzone() and not in_comment()
-- end

-- local in_align = function()
--   return vim.fn["vimtex#env#is_inside"]("align")[1] ~= 0
-- end

-- local in_enumerate = function()
--   return vim.fn["vimtex#env#is_inside"]("enumerate")[1] ~= 0
-- end

-- local in_itemize = function()
--   return vim.fn["vimtex#env#is_inside"]("itemize")[1] ~= 0
-- end

-- local begins_line = function()
--   local cur_line = vim.api.nvim_get_current_line()
--   -- Checks if the current line consists of whitespace and then the snippet
--   -- TODO: Fix limitation that the snippet cannot contain whitespace itself
--   return #cur_line == #string.match(cur_line, "%s*[^%s]+")
-- end

-- local get_env = function(name)
--   return {
--     t({ "\\begin{" .. name .. "}", "\t" }),
--     i(0),
--     t({ "", "\\end{" .. name .. "}" }),
--   }
-- end

local GREEK_LETTERS = {}
GREEK_LETTERS["a"] = "alpha"
GREEK_LETTERS["b"] = "beta"
GREEK_LETTERS["d"] = "delta"
GREEK_LETTERS["e"] = "eps"
GREEK_LETTERS["g"] = "gamma"
GREEK_LETTERS["l"] = "lam"
GREEK_LETTERS["o"] = "omega"
GREEK_LETTERS["s"] = "sigma"
GREEK_LETTERS["t"] = "tau"

return {

  -- easy differentials in mathzone
  s({ trig = "df", snippetType = "autosnippet" },
    { t("\\diff") },
    { condition = in_mathzone }
  ),

  -- langles rangles
  s({ trig = 'lrangle', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\langle{ <> }\rangle <> ]],
      {
        i(1),
        i(0)
      }
    ),
    { condition = in_mathzone }
  ),

  -- ff -> \frac{i(1)}{i(2)}
  s({ trig = '([^%a])ff', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[<>\frac{<>}{<>} ]],
      {
        f(function(_, snip) return snip.captures[1] end),
        i(1),
        i(2)
      }
    ),
    { condition = in_mathzone }
  ),

  -- LaTeX: Matrices
  s({ trig = "([pbv])mat_(%d)(%d)", regTrig = true, snippetType = "autosnippet" }, {
    d(1, function(_, snip)
      local type = snip.captures[1] .. "matrix"
      local rows, cols = snip.captures[2], snip.captures[3]
      local nodes = {}
      local ts = 1
      table.insert(nodes, t("\\begin{" .. type .. "}[rrrrrrrrrr]"))
      for _ = 1, rows, 1 do
        table.insert(nodes, t({ "", "\t" }))
        for _ = 1, cols, 1 do
          table.insert(nodes, i(ts))
          table.insert(nodes, t(" & "))
          ts = ts + 1
        end
        table.remove(nodes, #nodes)
        table.insert(nodes, t(" \\\\"))
      end
      table.remove(nodes, #nodes)
      table.insert(nodes, t({ "", "\\end{" .. type .. "}" }))
      return sn(1, nodes)
    end),
  }, { condition = in_mathzone }),

  -- LaTeX: Lims and stuff
  s({ trig = 'Lim', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\lim_{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = in_mathzone }),
  s({ trig = 'Sum', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\sum_{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = in_mathzone }),

  s({ trig = 'Prod', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\prod_{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = in_mathzone }),

  s({ trig = 'Int', regTrig = true, wordTrig = false, snippetType = "autosnippet" },
    fmta(
      [[\int{ <> }^{ <> }{ <> } <>]],
      {
        i(1),
        i(2),
        i(3),
        i(0),
      }
    ),
    { condition = in_mathzone }),

  s({ trig = 'ssum', regTrig = false, wordTrig = true, snippetType = "autosnippet" }, { t("\\sum ") },
    { condition = in_mathzone }),

  s({ trig = 'sprod', regTrig = false, wordTrig = true, snippetType = "autosnippet" }, { t("\\prod ") },
    { condition = in_mathzone }),

  -- LaTeX: easy () {}
  s({ trig = '()', regTrig = false, wordTrig = true, snippetType = "snippet" },
    fmta(
      [[\paren{ <> } <>]],
      {
        i(1),
        i(0),
      }
    )
  ),

  s({ trig = '{}', regTrig = false, wordTrig = true, snippetType = "snippet" },
    fmta(
      [[\cbrack{ <> } <>]],
      {
        i(1),
        i(0),
      }
    )
  ),

  -- LaTeX: Single-digit subscripts
  s(
    { trig = "([^%a])([abcvwxyz])([ijkn%d])", regTrig = true, wordTrig = false,
      snippetType = "autosnippet" },
    f(function(_, snip)
      return snip.captures[1] .. " " .. snip.captures[2] .. "_" .. snip.captures[3] .. " "
    end),
    { condition = in_mathzone }
  ),

  -- LaTeX: Math subscripts and superscripts
  s({ trig = "([abcvwxyz])([%_%^])", regTrig = true, snippetType = "autosnippet", wordTrig = false }, {
    f(function(_, snip)
      return snip.captures[1] .. snip.captures[2] .. "{"
    end),
    i(1),
    t("} "),
  }, { condition = in_mathzone }),

  -- LaTeX: Math exponents
  s({ trig = "^", wordTrig = false, snippetType = "autosnippet" }, {
    t("^{-"),
    i(1),
    t("}"),
  }, { condition = in_mathzone }),

  -- LaTeX: Math boldface
  s("bf", fmt([[\mathbf{{{}}}]], i(1)), { condition = in_mathzone }),

  -- LaTeX: Romanized math
  s("rm", fmt([[\mathrm{{{}}}]], i(1)), { condition = in_mathzone }),

  -- LaTeX: Math calligraphy
  s("mcal", fmt([[\mathcal{{{}}}]], i(1)), { condition = in_mathzone }),

  -- LaTeX: Math script
  s("mscr", fmt([[\mathscr{{{}}}]], i(1)), { condition = in_mathzone }),

  -- LaTeX: Math text
  s({ trig = "tt", wordTrig = true, snippetType = "autosnippet" }, fmt([[\text{{ {} }}]], i(1)),
    { condition = in_mathzone }),

  -- LaTeX: Square root
  s({ trig = "sqrt", snippetType = "autosnippet", wordTrig = false }, {
    t("\\sqrt[2]{"),
    i(1),
    t("}"),
  }, { condition = in_mathzone }),

  -- LaTeX: Vector
  s({ trig = "vec", snippetType = "autosnippet", wordTrig = false }, {
    t("\\vec{"),
    i(1),
    t("}"),
  }, { condition = in_mathzone }),
  -- LaTeX: bar
  s(
    {
      trig = "(%a)bar",
      wordTrig = false,
      regTrig = true,
      name = "bar",
      priority = 100,
      snippetType = "autosnippet",
    },
    f(function(_, snip)
      return string.format("\\overline{%s}", snip.captures[1])
    end, {}),
    { condition = in_mathzone }),

  -- LaTeX: hat
  s(
    {
      trig = "(%a)hat",
      wordTrig = false,
      regTrig = true,
      name = "hat",
      priority = 100,
      snippetType = "autosnippet",
    },
    f(function(_, snip)
      return string.format("\\hat{%s}", snip.captures[1])
    end, {})
  ),
}

But as I said, there are no snippets in general nor cmp box, so it's hard to tell if these are typed correctly besides the no-errors when first opening a file.

Note: The rest of files next to in_math.lua are all commented out

L3MON4D3 commented 1 year ago

Oh, okay xD The dofile has to go inside the ls.set_config-call, like ls.set_config({snip_env = dofile(...), ...}), then that should work. I'm not sure what lvim does, but if this is all of the luasnip-related config, it's missing a call to load the snippets, like require("luasnip.loaders.from_lua").lazy_load().

JxJxxJxJ commented 1 year ago

I think that is probably done in the backstage since the snippets work normally when not-doing all these locals in another file things. When defining them in each file it worked as expected.

Now this is my ~/.config/lvim/lua/user/luasnips.lua

---@diagnostic disable: undefined-global

-- Locals for my snippets
-- ls.setup({ snip_env = require("~/.config/lvim/lua/user/my-things/luasnips/locals.lua") })

-- Somewhere in your Neovim startup, e.g. init.lua
require("luasnip").config.set_config({ -- Setting LuaSnip config
  -- load the variables file
  snip_env = dofile('~/.config/lvim/lua/user/my-things/luasnips/locals.lua'),

  -- Enable autotriggered snippets
  enable_autosnippets = true,

  -- Enable jump back into older snippets
  history = true,

  -- Use Tab (or some other key if you prefer) to trigger visual selection
  store_selection_keys = "<Tab>",

  -- Update text in repeated nodes lively, not after confirming its content
  update_events = 'TextChanged,TextChangedI',
})

-- Keybindings for choice nodes
-- set keybinds for both INSERT and VISUAL.
vim.api.nvim_set_keymap("i", "<Tab>", "<Plug>luasnip-next-choice", {})
vim.api.nvim_set_keymap("s", "<Tab>", "<Plug>luasnip-next-choice", {})
-- vim.api.nvim_set_keymap("i", "<C-k>", "<Plug>luasnip-prev-choice", {})
-- vim.api.nvim_set_keymap("s", "<C-k>", "<Plug>luasnip-prev-choice", {})

local current_nsid = vim.api.nvim_create_namespace("LuaSnipChoiceListSelections")
local current_win = nil

local function window_for_choiceNode(choiceNode)
  local buf = vim.api.nvim_create_buf(false, true)
  local buf_text = {}
  local row_selection = 0
  local row_offset = 0
  local text
  for _, node in ipairs(choiceNode.choices) do
    text = node:get_docstring()
    -- find one that is currently showing
    if node == choiceNode.active_choice then
      -- current line is starter from buffer list which is length usually
      row_selection = #buf_text
      -- finding how many lines total within a choice selection
      row_offset = #text
    end
    vim.list_extend(buf_text, text)
  end

  vim.api.nvim_buf_set_text(buf, 0, 0, 0, 0, buf_text)
  local w, h = vim.lsp.util._make_floating_popup_size(buf_text)

  -- adding highlight so we can see which one is been selected.
  local extmark = vim.api.nvim_buf_set_extmark(buf, current_nsid, row_selection, 0,
    { hl_group = 'incsearch', end_line = row_selection + row_offset })

  -- shows window at a beginning of choiceNode.
  local win = vim.api.nvim_open_win(buf, false, {
    relative = "win", width = w, height = h, bufpos = choiceNode.mark:pos_begin_end(), style = "minimal",
    border = 'rounded'
  })

  -- return with 3 main important so we can use them again
  return { win_id = win, extmark = extmark, buf = buf }
end

function choice_popup(choiceNode)
  -- build stack for nested choiceNodes.
  if current_win then
    vim.api.nvim_win_close(current_win.win_id, true)
    vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)
  end
  local create_win = window_for_choiceNode(choiceNode)
  current_win = {
    win_id = create_win.win_id,
    prev = current_win,
    node = choiceNode,
    extmark = create_win.extmark,
    buf = create_win.buf
  }
end

function update_choice_popup(choiceNode)
  vim.api.nvim_win_close(current_win.win_id, true)
  vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)
  local create_win = window_for_choiceNode(choiceNode)
  current_win.win_id = create_win.win_id
  current_win.extmark = create_win.extmark
  current_win.buf = create_win.buf
end

function choice_popup_close()
  vim.api.nvim_win_close(current_win.win_id, true)
  vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)
  -- now we are checking if we still have previous choice we were in after exit nested choice
  current_win = current_win.prev
  if current_win then
    -- reopen window further down in the stack.
    local create_win = window_for_choiceNode(current_win.node)
    current_win.win_id = create_win.win_id
    current_win.extmark = create_win.extmark
    current_win.buf = create_win.buf
  end
end

vim.cmd([[
augroup choice_popup
au!
au User LuasnipChoiceNodeEnter lua choice_popup(require("luasnip").session.event_node)
au User LuasnipChoiceNodeLeave lua choice_popup_close()
au User LuasnipChangeChoice lua update_choice_popup(require("luasnip").session.event_node)
augroup END
]])

And no errors are shown when opening a file, but the snippet still don't seem to work. I'm testing mainly the snippets defined in the in_math.lua file. But also I have snippets in environments.lua that do not require any condition, I tried them and they do not work either.

JxJxxJxJ commented 1 year ago

Also, returning or not the locals in a table in ~/.config/lvim/lua/user/my-things/luasnips/locals.lua does not change anything. This gives the same behavior

-- ~/.config/lvim/lua/user/my-things/luasnips/locals.lua

---@diagnostic disable: undefined-global

local in_mathzone = function()
  return vim.fn["vimtex#syntax#in_mathzone"]() == 1
end

local in_comment = function()
  return vim.fn["vimtex#syntax#in_comment"]() == 1
end

local in_text = function()
  return not in_mathzone() and not in_comment()
end

local in_align = function()
  return vim.fn["vimtex#env#is_inside"]("align")[1] ~= 0
end

local in_enumerate = function()
  return vim.fn["vimtex#env#is_inside"]("enumerate")[1] ~= 0
end

local in_itemize = function()
  return vim.fn["vimtex#env#is_inside"]("itemize")[1] ~= 0
end

local begins_line = function()
  local cur_line = vim.api.nvim_get_current_line()
  -- Checks if the current line consists of whitespace and then the snippet
  -- TODO: Fix limitation that the snippet cannot contain whitespace itself
  return #cur_line == #string.match(cur_line, "%s*[^%s]+")
end

local get_env = function(name)
  return {
    t({ "\\begin{" .. name .. "}", "\t" }),
    i(0),
    t({ "", "\\end{" .. name .. "}" }),
  }
end

-- return {
--   in_mathzone = in_mathzone,
--   in_comment = in_comment,
--   in_text = in_text,
--   in_align = in_align,
--   in_enumerate = in_enumerate,
--   in_itemize = in_itemize,
--   begins_line = begins_line,
--   -- get_env = get_env,
--   -- add more functions here as needed
-- }
L3MON4D3 commented 1 year ago

Checking out the lunarvim defaults, it looks like it will immediately load snippets. I guess the custom-config is executed after this, which of course causes this initial load to miss the extended snip_env.

I don't have a good solution for this, but dofileing locals.lua with an absolute path in each snippet-file should work regardless.

JxJxxJxJ commented 1 year ago

I tried this in luasnips.lua file loading it from config.lua

lvim.builtin.luasnip = vim.tbl_extend("force", { -- Setting LuaSnip config
  -- load the variables file
  snip_env = require("user.my-things.luasnip.locals"),

  -- Enable autotriggered snippets
  enable_autosnippets = true,

  -- Enable jump back into older snippets
  history = true,

  -- Use Tab (or some other key if you prefer) to trigger visual selection
  store_selection_keys = "<Tab>",

  -- Update text in repeated nodes lively, not after confirming its content
  update_events = 'TextChanged,TextChangedI',
})

this is a more "correct way" of doing things, but did not solve the issue. This require is equivalent to doing dofile.

I don't have a good solution for this, but dofileing locals.lua with an absolute path in each snippet-file should work regardless.

How do you mean in each snippet file? Should I do dofile in for example in_math.lua? (or maybe this seemingly require equivalent?)

L3MON4D3 commented 1 year ago

Yes, I meant that calling dofile with an absolute path, or require, in the snippet files, in_math.lua for example, is less error-prone.

JxJxxJxJ commented 1 year ago

This ended up working.

I use lvim and I'm using a ~/.config/lvim/lua/user/luasnips.lua file being loaded from my ~/.config/lvim/config.lua like this:

-- ~/.config/lvim/config.lua

require("user.luasnips") -- luasnippets related configs

Then in ~/.config/lvim/lua/user/luasnips.lua I have my usual configs for luasnips and with them snip_env requiring a ~/.config/lvim/lua/user/my-things/locals.lua file

-- ~/.config/lvim/lua/user/luasnips.lua 

require("luasnip").config.set_config({ -- Setting LuaSnip config

  -- load the variables file
  snip_env = require("user.my-things.luasnips.locals"),

  -- Enable autotriggered snippets
  enable_autosnippets = true,

  -- Enable jump back into older snippets
  history = true,

  -- Use Tab (or some other key if you prefer) to trigger visual selection
  store_selection_keys = "<Tab>",

  -- Update text in repeated nodes lively, not after confirming its content
  update_events = 'TextChanged,TextChangedI',
})

Here's what's inside that ~/.config/lvim/lua/user/my-things/locals.lua file

--  ~/.config/lvim/lua/user/my-things/locals.lua

local in_mathzone = function()
  return vim.fn["vimtex#syntax#in_mathzone"]() == 1
end

local in_comment = function()
  return vim.fn["vimtex#syntax#in_comment"]() == 1
end

local in_text = function()
  return not in_mathzone() and not in_comment()
end

local in_align = function()
  return vim.fn["vimtex#env#is_inside"]("align")[1] ~= 0
end

local in_enumerate = function()
  return vim.fn["vimtex#env#is_inside"]("enumerate")[1] ~= 0
end

local in_itemize = function()
  return vim.fn["vimtex#env#is_inside"]("itemize")[1] ~= 0
end

local begins_line = function()
  local cur_line = vim.api.nvim_get_current_line()
  -- Checks if the current line consists of whitespace and then the snippet
  -- TODO: Fix limitation that the snippet cannot contain whitespace itself
  return #cur_line == #string.match(cur_line, "%s*[^%s]+")
end

local get_env = function(name)
  return {
    t({ "\\begin{" .. name .. "}", "\t" }),
    i(0),
    t({ "", "\\end{" .. name .. "}" }),
  }
end

return {
  -- LuaSnips defaults
  s = require("luasnip.nodes.snippet").S,
  sn = require("luasnip.nodes.snippet").SN,
  isn = require("luasnip.nodes.snippet").ISN,
  t = require("luasnip.nodes.textNode").T,
  i = require("luasnip.nodes.insertNode").I,
  f = require("luasnip.nodes.functionNode").F,
  c = require("luasnip.nodes.choiceNode").C,
  d = require("luasnip.nodes.dynamicNode").D,
  r = require("luasnip.nodes.restoreNode").R,
  events = require("luasnip.util.events"),
  ai = require("luasnip.nodes.absolute_indexer"),
  extras = require("luasnip.extras"),
  l = require("luasnip.extras").lambda,
  rep = require("luasnip.extras").rep,
  p = require("luasnip.extras").partial,
  m = require("luasnip.extras").match,
  n = require("luasnip.extras").nonempty,
  dl = require("luasnip.extras").dynamic_lambda,
  fmt = require("luasnip.extras.fmt").fmt,
  fmta = require("luasnip.extras.fmt").fmta,
  conds = require("luasnip.extras.expand_conditions"),
  postfix = require("luasnip.extras.postfix").postfix,
  types = require("luasnip.util.types"),
  parse = require("luasnip.util.parser").parse_snippet,

  -- my locals
  in_mathzone = in_mathzone,
  in_comment = in_comment,
  in_text = in_text,
  in_align = in_align,
  in_enumerate = in_enumerate,
  in_itemize = in_itemize,
  begins_line = begins_line,
  -- get_env = get_env,
  -- add more functions here as needed
}