nvim-config

Log | Files | Refs | Submodules | README

tex.lua (8917B)


      1 vim.opt_local.formatoptions:append { t = true, c = true, n = true }
      2 vim.opt_local.formatlistpat = "^\\s*\\\\item\\s*"
      3 vim.opt_local.textwidth = 100
      4 vim.opt_local.conceallevel = 0
      5 vim.opt_local.foldmethod = "marker"
      6 vim.opt_local.foldmarker = "%(,%)"
      7 vim.opt_local.wrap = false
      8 vim.opt_local.spell = true
      9 
     10 vim.cmd [[normal zx]]
     11 
     12 if #vim.fs.find({ "Makefile", "makefile" }, { type = "file" }) == 0 then
     13   vim.cmd [[compiler latexmk]]
     14 end
     15 
     16 MiniPairs.map_buf(0, 'i', '$', { action = 'closeopen', pair = '$$', register = { cr = false } })
     17 -- XXX: when calling map_buf, it resets the mapping for `<CR>` and `<BS>`, so we need to reset them
     18 require "mappings".set_mappings()
     19 
     20 local edit = require "architext.edit"
     21 local utls = require "mytsutils"
     22 
     23 --- Wraps the provided function to call it with the current position as arguments
     24 ---@param func fun(buf: buffer, win: window, cursors: integer[])
     25 ---@return function wrapped
     26 local function with_current_position(func)
     27   return function()
     28     local curbuf = vim.api.nvim_get_current_buf()
     29     local curwin = vim.api.nvim_get_current_win()
     30     local cursor = vim.api.nvim_win_get_cursor(curwin)
     31 
     32     return func(curbuf, curwin, cursor)
     33   end
     34 end
     35 
     36 local function lazy_query(lang, content)
     37   return setmetatable({}, {
     38     __index = function(tbl, index)
     39       local query = rawget(tbl, "_query")
     40       if not query then
     41         query = vim.treesitter.query.parse(lang, content)
     42         rawset(tbl, "_query", query)
     43       end
     44       local qmeta = getmetatable(query)
     45       local res = rawget(qmeta, "__index")[index]
     46       if type(res) == "function" then
     47         return function(_, ...)
     48           return res(query, ...)
     49         end
     50       elseif not res then
     51         return query[index]
     52       else
     53         return res
     54       end
     55     end
     56   })
     57 end
     58 
     59 do
     60   -- Replace the surrounding environment
     61   local query = lazy_query("latex", [[
     62     (generic_environment
     63       (begin (curly_group_text text: (_) @envbegin))
     64       (end (curly_group_text text: (_) @envend))) @_root
     65 
     66     (math_environment
     67       (begin (curly_group_text text: (_) @envbegin))
     68       (end (curly_group_text text: (_) @envend))) @_root
     69   ]])
     70 
     71   vim.keymap.set('n', '<LocalLeader>re', with_current_position(function(curbuf, _, cursor)
     72     local match, node = utls.find_smallest_match(cursor[1] - 1, cursor[2], query, curbuf)
     73 
     74     if not match or not node then
     75       print("Not in an environment")
     76       return
     77     end
     78 
     79     local start_row, _, end_row, _ = node:range()
     80 
     81     local default = vim.treesitter.get_node_text(utls.index_by_name(query, match, "envbegin"), curbuf)
     82 
     83     vim.ui.input({ prompt = "Replacement: ", default = default }, function(replacement)
     84       if replacement then
     85         edit.edit_match(curbuf, match, query, { envbegin = replacement, envend = replacement }, start_row, end_row)
     86       end
     87     end)
     88   end), { buffer = true })
     89 
     90   vim.keymap.set('n', '<LocalLeader>se', with_current_position(function(curbuf, _, cursor)
     91     local match, node = utls.find_smallest_match(cursor[1] - 1, cursor[2], query, curbuf)
     92 
     93     if not match then
     94       print("Not in an environment")
     95       return
     96     end
     97 
     98     local start_row, _, end_row, _ = node:range()
     99 
    100     local current = vim.treesitter.get_node_text(utls.index_by_name(query, match, "envbegin"), curbuf)
    101 
    102     if vim.endswith(current, "*") then
    103       current = current:sub(0, #current - 1)
    104     else
    105       current = current .. "*"
    106     end
    107 
    108     edit.edit_match(curbuf, match, query, { envbegin = current, envend = current }, start_row, end_row)
    109   end), { buffer = true })
    110 end
    111 
    112 local function well_defined(root, capture_to_id, matches)
    113   return (matches[capture_to_id[root .. ".inner"]]
    114     or (matches[capture_to_id[root .. ".inner.from"]]
    115         and matches[capture_to_id[root .. ".inner.to"]]
    116     )) and (matches[capture_to_id[root .. ".outer"]]
    117     or (matches[capture_to_id[root .. ".outer.from"]]
    118         and matches[capture_to_id[root .. ".outer.to"]]
    119     ))
    120 end
    121 
    122 local function resolve_capture(node, idx)
    123   if type(node) == 'table' then
    124     return node[idx or #node]
    125   else
    126     return node
    127   end
    128 end
    129 
    130 local function extract_range(root, match, capture_to_id)
    131   local ret = {}
    132 
    133   local fromnode
    134   local tonode
    135 
    136   local tmp = match[capture_to_id[root]]
    137   if tmp then
    138     fromnode = resolve_capture(tmp, 1)
    139     tonode  = resolve_capture(tmp)
    140   else
    141     fromnode = resolve_capture(match[capture_to_id[root .. ".from"]], 1)
    142     tonode = resolve_capture(match[capture_to_id[root .. ".to"]]) or fromnode
    143   end
    144 
    145   local start_row, start_col = fromnode:start()
    146   ret.from = { line = start_row, col = start_col }
    147 
    148   local end_row, end_col = tonode:end_()
    149   ret.to = { line = end_row, col = end_col }
    150 
    151   return ret
    152 end
    153 
    154 local qcache = setmetatable({}, {
    155   __mode = "v",
    156   __index = function(tbl, key)
    157     local val = rawget(tbl, key)
    158     if val then
    159       return val
    160     else
    161       local q = vim.treesitter.query.get(key, 'textobjects')
    162       rawset(tbl, key, q)
    163       return q
    164     end
    165   end
    166 })
    167 
    168 function captures_inout(root)
    169   local lang = require 'nvim-treesitter.parsers'.get_buf_lang()
    170   local query = qcache[lang]
    171   if query == nil then error("Could not get query") end
    172 
    173   local capture_to_id = {}
    174   for idx, val in ipairs(query.captures) do
    175     capture_to_id[val] = idx
    176   end
    177 
    178   root = root:sub(2)
    179 
    180 
    181   local ok, parser = pcall(vim.treesitter.get_parser, 0, lang)
    182   if not ok then return end
    183 
    184   -- Compute matched captures
    185   local res = {}
    186   for _, tree in ipairs(parser:trees()) do
    187     for _, match, _ in query:iter_matches(tree:root(), 0, 0, -1, {all = true}) do
    188       if well_defined(root, capture_to_id, match) then
    189         local innerrange = extract_range(root .. ".inner", match, capture_to_id)
    190         res[#res + 1] = {
    191           outer = extract_range(root .. ".outer", match, capture_to_id),
    192           inner = innerrange
    193         }
    194       end
    195     end
    196   end
    197   return res
    198 end
    199 
    200 -- Somewhat copied from mini.ai
    201 local function ai_treesitter(captures)
    202   return function(ai_type)
    203     local tgt_captures = ai_type == "a" and "outer" or "inner"
    204 
    205     local res = {}
    206     for _, match in ipairs(captures_inout(captures)) do
    207       local tgt = match[tgt_captures]
    208       res[#res + 1] = {
    209         from = {
    210           line = tgt.from.line + 1,
    211           col = tgt.from.col + 1
    212         },
    213         to = {
    214           line = tgt.to.line + 1,
    215           col = tgt.to.col
    216         }
    217       }
    218     end
    219 
    220     return res
    221   end
    222 end
    223 
    224 -- local spec_treesitter = require('mini.ai').gen_spec.treesitter
    225 vim.b.miniai_config = {
    226   custom_textobjects = {
    227     m = ai_treesitter "@math",
    228     e = ai_treesitter "@env",
    229     s = ai_treesitter "@section",
    230     i = ai_treesitter "@item",
    231   }
    232 }
    233 
    234 local surround = require 'mini.surround'
    235 
    236 ---@param prompt string
    237 ---@param leftfmt string
    238 ---@param rightfmt string
    239 ---@return function()
    240 local function output_ask(prompt, leftfmt, rightfmt)
    241   return function()
    242     local name = surround.user_input(prompt)
    243     if not name then return end
    244     local ret = { left = string.format(leftfmt, name), right = string.format(rightfmt, name) }
    245     return ret
    246   end
    247 end
    248 
    249 
    250 local pos_to_left = function(pos)
    251   if pos.line == 1 and pos.col == 1 then return { line = pos.line, col = pos.col } end
    252   if pos.col == 1 then return { line = pos.line - 1, col = H.get_line_cols(pos.line - 1) } end
    253   return { line = pos.line, col = pos.col - 1 }
    254 end
    255 
    256 local pos_to_right = function(pos)
    257   local n_cols = vim.api.nvim_buf_get_lines(0, pos.line - 1, pos.line, true)[1]:len()
    258   -- Using `>` and not `>=` helps with removing '\n' and in the last line
    259   if pos.line == vim.api.nvim_buf_line_count(0) and pos.col > n_cols then return { line = pos.line, col = n_cols } end
    260   if pos.col > n_cols then return { line = pos.line + 1, col = 1 } end
    261   return { line = pos.line, col = pos.col + 1 }
    262 end
    263 
    264 -- Somewhat copied from mini.surround
    265 local function surround_treesitter(captures)
    266   return function()
    267     local matches = captures_inout(captures)
    268     local res = {}
    269     for _, match in ipairs(matches) do
    270       local left_from = { line = match.outer.from.line + 1, col = match.outer.from.col + 1 }
    271       local right_to = { line = match.outer.to.line + 1, col = match.outer.to.col }
    272 
    273       local left_to = pos_to_left { line = match.inner.from.line + 1, col = match.inner.from.col + 1 }
    274       local right_from = pos_to_right { line = match.inner.to.line + 1, col = match.inner.to.col }
    275 
    276       res[#res + 1] = {
    277         left = { from = left_from, to = left_to },
    278         right = { from = right_from, to = right_to }
    279       }
    280     end
    281 
    282     return res
    283   end
    284 end
    285 
    286 vim.b.minisurround_config = {
    287   custom_surroundings = {
    288     f = {
    289       input = surround_treesitter "@call",
    290       output = output_ask("Function name", "\\%s{", "}")
    291     },
    292     e = {
    293       input = surround_treesitter "@env",
    294       output = output_ask("Env name", "\\begin{%s}\n", "\n\\end{%s}")
    295     },
    296     c = {
    297       input = surround_treesitter "@color",
    298       output = output_ask("Color name", "\\textcolor{%s}{", "}")
    299     }
    300   }
    301 }