From fa8aa35f6e093933451e022d32eb1357a560669b Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 5 Feb 2024 14:12:21 +0800 Subject: [PATCH] update --- lua/internal/event.lua | 7 + lua/internal/pairs.lua | 380 +++++++++++++++++++++++++++++++++ lua/keymap/remap.lua | 2 +- lua/modules/editor/package.lua | 5 - 4 files changed, 388 insertions(+), 6 deletions(-) create mode 100644 lua/internal/pairs.lua diff --git a/lua/internal/event.lua b/lua/internal/event.lua index 15f7e8d0..903d45e4 100644 --- a/lua/internal/event.lua +++ b/lua/internal/event.lua @@ -51,3 +51,10 @@ au('TermOpen', { vim.cmd.startinsert() end, }) + +au('InsertEnter', { + group = group, + callback = function() + require('internal.pairs').setup({}) + end, +}) diff --git a/lua/internal/pairs.lua b/lua/internal/pairs.lua new file mode 100644 index 00000000..c0b3c7cb --- /dev/null +++ b/lua/internal/pairs.lua @@ -0,0 +1,380 @@ +local M = {} +local H = {} + +M.setup = function(config) + _G.PairMate = M + + config = H.setup_config(config) + + H.apply_config(config) + + H.create_autocommands() +end + +M.config = { + modes = { insert = true, command = false, terminal = false }, + + mappings = { + ['('] = { action = 'open', pair = '()', neigh_pattern = '[^\\].' }, + ['['] = { action = 'open', pair = '[]', neigh_pattern = '[^\\].' }, + ['{'] = { action = 'open', pair = '{}', neigh_pattern = '[^\\].' }, + + [')'] = { action = 'close', pair = '()', neigh_pattern = '[^\\].' }, + [']'] = { action = 'close', pair = '[]', neigh_pattern = '[^\\].' }, + ['}'] = { action = 'close', pair = '{}', neigh_pattern = '[^\\].' }, + + ['"'] = { + action = 'closeopen', + pair = '""', + neigh_pattern = '[^\\].', + register = { cr = false }, + }, + ["'"] = { + action = 'closeopen', + pair = "''", + neigh_pattern = '[^%a\\].', + register = { cr = false }, + }, + ['`'] = { + action = 'closeopen', + pair = '``', + neigh_pattern = '[^\\].', + register = { cr = false }, + }, + }, +} + +M.map = function(mode, lhs, pair_info, opts) + pair_info = H.validate_pair_info(pair_info) + opts = vim.tbl_deep_extend('force', opts or {}, { expr = true, noremap = true }) + opts.desc = H.infer_mapping_description(pair_info) + + vim.api.nvim_set_keymap(mode, lhs, H.pair_info_to_map_rhs(pair_info), opts) + H.register_pair(pair_info, mode, 'all') + + H.ensure_cr_bs(mode) +end + +M.map_buf = function(buffer, mode, lhs, pair_info, opts) + pair_info = H.validate_pair_info(pair_info) + opts = vim.tbl_deep_extend('force', opts or {}, { expr = true, noremap = true }) + opts.desc = H.infer_mapping_description(pair_info) + + vim.api.nvim_buf_set_keymap(buffer, mode, lhs, H.pair_info_to_map_rhs(pair_info), opts) + H.register_pair(pair_info, mode, buffer == 0 and vim.api.nvim_get_current_buf() or buffer) + + H.ensure_cr_bs(mode) +end + +M.unmap = function(mode, lhs, pair) + vim.validate({ pair = { pair, 'string' } }) + + pcall(vim.api.nvim_del_keymap, mode, lhs) + if pair == '' then + return + end + H.unregister_pair(pair, mode, 'all') +end + +M.unmap_buf = function(buffer, mode, lhs, pair) + vim.validate({ pair = { pair, 'string' } }) + + pcall(vim.api.nvim_buf_del_keymap, buffer, mode, lhs) + if pair == '' then + return + end + H.unregister_pair(pair, mode, buffer == 0 and vim.api.nvim_get_current_buf() or buffer) +end + +M.open = function(pair, neigh_pattern) + if H.is_disabled() or not H.neigh_match(neigh_pattern) then + return pair:sub(1, 1) + end + + return ('%s%s'):format(pair, H.get_arrow_key('left')) +end + +M.close = function(pair, neigh_pattern) + if H.is_disabled() or not H.neigh_match(neigh_pattern) then + return pair:sub(2, 2) + end + + local close = pair:sub(2, 2) + if H.get_cursor_neigh(1, 1) == close then + return H.get_arrow_key('right') + else + return close + end +end + +M.closeopen = function(pair, neigh_pattern) + if H.is_disabled() or H.get_cursor_neigh(1, 1) ~= pair:sub(2, 2) then + return M.open(pair, neigh_pattern) + else + return H.get_arrow_key('right') + end +end + +M.bs = function(key) + local res = key or H.keys.bs + + local neigh = H.get_cursor_neigh(0, 1) + if not H.is_disabled() and H.is_pair_registered(neigh, vim.fn.mode(), 0, 'bs') then + res = ('%s%s'):format(res, H.keys.del) + end + + return res +end + +M.cr = function(key) + local res = key or H.keys.cr + + local neigh = H.get_cursor_neigh(0, 1) + if not H.is_disabled() and H.is_pair_registered(neigh, vim.fn.mode(), 0, 'cr') then + res = ('%s%s'):format(res, H.keys.above) + end + + return res +end + +H.default_config = vim.deepcopy(M.config) + +H.default_pair_info = { neigh_pattern = '..', register = { bs = true, cr = true } } + +H.registered_pairs = { + i = { all = { bs = {}, cr = {} } }, + c = { all = { bs = {}, cr = {} } }, + t = { all = { bs = {}, cr = {} } }, +} + +local function escape(s) + return vim.api.nvim_replace_termcodes(s, true, true, true) +end +H.keys = { + above = escape('O'), + bs = escape(''), + cr = escape(''), + del = escape(''), + keep_undo = escape('U'), + left = escape(''), + right = escape(''), +} + +H.setup_config = function(config) + vim.validate({ config = { config, 'table', true } }) + config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {}) + + vim.validate({ + modes = { config.modes, 'table' }, + mappings = { config.mappings, 'table' }, + }) + + vim.validate({ + ['modes.insert'] = { config.modes.insert, 'boolean' }, + ['modes.command'] = { config.modes.command, 'boolean' }, + ['modes.terminal'] = { config.modes.terminal, 'boolean' }, + }) + + local validate_mapping = function(pair_info, prefix) + if pair_info == false then + return + end + H.validate_pair_info(pair_info, prefix) + end + + validate_mapping(config.mappings['('], "mappings['(']") + validate_mapping(config.mappings['['], "mappings['[']") + validate_mapping(config.mappings['{'], "mappings['{']") + validate_mapping(config.mappings[')'], "mappings[')']") + validate_mapping(config.mappings[']'], "mappings[']']") + validate_mapping(config.mappings['}'], "mappings['}']") + validate_mapping(config.mappings['"'], "mappings['\"']") + validate_mapping(config.mappings["'"], 'mappings["\'"]') + validate_mapping(config.mappings['`'], "mappings['`']") + + return config +end + +H.apply_config = function(config) + M.config = config + + local mode_ids = { insert = 'i', command = 'c', terminal = 't' } + local mode_array = {} + for name, to_set in pairs(config.modes) do + if to_set then + table.insert(mode_array, mode_ids[name]) + end + end + + local map_conditionally = function(mode, key, pair_info) + if pair_info == false then + return + end + + M.map(mode, key, pair_info) + end + + for _, mode in pairs(mode_array) do + for key, pair_info in pairs(config.mappings) do + map_conditionally(mode, key, pair_info) + end + end +end + +H.create_autocommands = function() + local augroup = vim.api.nvim_create_augroup('PairMate', {}) + + local au = function(event, pattern, callback, desc) + vim.api.nvim_create_autocmd( + event, + { group = augroup, pattern = pattern, callback = callback, desc = desc } + ) + end + + au('FileType', { 'TelescopePrompt', 'fzf' }, function() + vim.b.minipairs_disable = true + end, 'Disable locally') +end + +H.is_disabled = function() + return vim.g.minipairs_disable == true or vim.b.minipairs_disable == true +end + +H.register_pair = function(pair_info, mode, buffer) + H.registered_pairs[mode] = H.registered_pairs[mode] or { all = { bs = {}, cr = {} } } + local mode_pairs = H.registered_pairs[mode] + + mode_pairs[buffer] = mode_pairs[buffer] or { bs = {}, cr = {} } + + local register, pair = pair_info.register, pair_info.pair + if register.bs and not vim.tbl_contains(mode_pairs[buffer].bs, pair) then + table.insert(mode_pairs[buffer].bs, pair) + end + if register.cr and not vim.tbl_contains(mode_pairs[buffer].cr, pair) then + table.insert(mode_pairs[buffer].cr, pair) + end +end + +H.unregister_pair = function(pair, mode, buffer) + local mode_pairs = H.registered_pairs[mode] + if not (mode_pairs and mode_pairs[buffer]) then + return + end + + local buf_pairs = mode_pairs[buffer] + for _, key in ipairs({ 'bs', 'cr' }) do + for i, p in ipairs(buf_pairs[key]) do + if p == pair then + table.remove(buf_pairs[key], i) + end + end + end +end + +H.is_pair_registered = function(pair, mode, buffer, key) + local mode_pairs = H.registered_pairs[mode] + if not mode_pairs then + return false + end + + if vim.tbl_contains(mode_pairs['all'][key], pair) then + return true + end + + buffer = buffer == 0 and vim.api.nvim_get_current_buf() or buffer + local buf_pairs = mode_pairs[buffer] + if not buf_pairs then + return false + end + + return vim.tbl_contains(buf_pairs[key], pair) +end + +H.ensure_cr_bs = function(mode) + local has_any_cr_pair, has_any_bs_pair = false, false + for _, pair_tbl in pairs(H.registered_pairs[mode]) do + has_any_cr_pair = has_any_cr_pair or not vim.tbl_isempty(pair_tbl.cr) + has_any_bs_pair = has_any_bs_pair or not vim.tbl_isempty(pair_tbl.bs) + end + + if has_any_bs_pair then + local opts = + { silent = mode ~= 'c', expr = true, replace_keycodes = false, desc = 'PairMate ' } + H.map(mode, '', 'v:lua.PairMate.bs()', opts) + end + + if mode == 'i' and has_any_cr_pair then + local opts = { expr = true, replace_keycodes = false, desc = 'PairMate ' } + H.map(mode, '', 'v:lua.PairMate.cr()', opts) + end +end + +H.validate_pair_info = function(pair_info, prefix) + prefix = prefix or 'pair_info' + vim.validate({ [prefix] = { pair_info, 'table' } }) + pair_info = vim.tbl_deep_extend('force', H.default_pair_info, pair_info) + + vim.validate({ + [prefix .. '.action'] = { pair_info.action, 'string' }, + [prefix .. '.pair'] = { pair_info.pair, 'string' }, + [prefix .. '.neigh_pattern'] = { pair_info.neigh_pattern, 'string' }, + [prefix .. '.register'] = { pair_info.register, 'table' }, + }) + + vim.validate({ + [prefix .. '.register.bs'] = { pair_info.register.bs, 'boolean' }, + [prefix .. '.register.cr'] = { pair_info.register.cr, 'boolean' }, + }) + + return pair_info +end + +H.pair_info_to_map_rhs = function(pair_info) + return ('v:lua.PairMate.%s(%s, %s)'):format( + pair_info.action, + vim.inspect(pair_info.pair), + vim.inspect(pair_info.neigh_pattern) + ) +end + +H.infer_mapping_description = function(pair_info) + local action_name = pair_info.action:sub(1, 1):upper() .. pair_info.action:sub(2) + return ('%s action for %s pair'):format(action_name, vim.inspect(pair_info.pair)) +end + +H.get_cursor_neigh = function(start, finish) + local line, col + if vim.fn.mode() == 'c' then + line = vim.fn.getcmdline() + col = vim.fn.getcmdpos() + start = start - 1 + finish = finish - 1 + else + line = vim.api.nvim_get_current_line() + col = vim.api.nvim_win_get_cursor(0)[2] + end + + return string.sub(('%s%s%s'):format('\r', line, '\n'), col + 1 + start, col + 1 + finish) +end + +H.neigh_match = function(pattern) + return (pattern == nil) or (H.get_cursor_neigh(0, 1):find(pattern) ~= nil) +end + +H.get_arrow_key = function(key) + if vim.fn.mode() == 'i' then + return H.keys.keep_undo .. H.keys[key] + else + return H.keys[key] + end +end + +H.map = function(mode, lhs, rhs, opts) + if lhs == '' then + return + end + opts = vim.tbl_deep_extend('force', { silent = true }, opts or {}) + vim.keymap.set(mode, lhs, rhs, opts) +end + +return M diff --git a/lua/keymap/remap.lua b/lua/keymap/remap.lua index c390bbba..7b5194f0 100644 --- a/lua/keymap/remap.lua +++ b/lua/keymap/remap.lua @@ -81,7 +81,7 @@ map.i('', function() end, { expr = true }) map.i('', function() - return vim.fn.pumvisible() == 1 and '' or vim.fn.lexima['expand']('CR>', 'i') + return vim.fn.pumvisible() == 1 and '' or _G.PairMate.cr() end, { expr = true }) map.i('', function() diff --git a/lua/modules/editor/package.lua b/lua/modules/editor/package.lua index 5872c885..a85359e0 100644 --- a/lua/modules/editor/package.lua +++ b/lua/modules/editor/package.lua @@ -1,10 +1,5 @@ local conf = require('modules.editor.config') -packadd({ - 'cohama/lexima.vim', - event = 'InsertEnter', -}) - packadd({ 'nvim-telescope/telescope.nvim', cmd = 'Telescope',