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 }