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 }