nvim-config

Log | Files | Refs | README

tex.lua (9000B)


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