commit 3b19a9c8a84bb7ff7cf1cf97c1d4502284557c46 Author: captain Date: Sat Apr 11 22:22:01 2026 +0800 first commit diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..f59d9cc --- /dev/null +++ b/.github/README.md @@ -0,0 +1,18 @@ +> [!IMPORTANT] +> 本人是colemak键位用户 + +某些插件需要安装特定的软件才能正常使用,如果是arch用户可以使用`paru`或者`yay`安装,示例如下 + +```bash +paru -S neovim python-neovim +# 如果使用kitty,则不需要安装下面的ueberzugpp等软件(用于图像预览) +paru -S lua51 imagemagick luarocks ueberzugpp +sudo luarocks --lua-version=5.1 install magick +# deno用于markdown预览,支持火狐和chrome,webkit2gtk-4.1可选 +paru -S deno webkit2gtk-4.1 +# translate-shell用于翻译,我配置的翻译快捷键是tr和ts +paru -S translate-shell +# 也在终端使用`trans -e bing :zh "hello world"`指定bing引擎翻译文本 +``` + +![效果](效果.jpg) diff --git a/.github/效果.jpg b/.github/效果.jpg new file mode 100644 index 0000000..0c99f41 Binary files /dev/null and b/.github/效果.jpg differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9f7c8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..55fa8e2 --- /dev/null +++ b/init.lua @@ -0,0 +1,25 @@ +-- 基本配置 +require("core.init") + +-- 基本键盘映射 +require("core.keymap") + +-- vim综合症 +require("core.cursor") + +-- markdown snippets +require("core.md-snippets") + +-- markdown table fromat +require("core.markdown_table_format") + +-- 安装的插件 +require("pack.plugins") + +-- 本地备份配置方式如下 +-- # required +-- mv ~/.config/nvim{,.bak} +-- # optional but recommended +-- mv ~/.local/share/nvim{,.bak} +-- mv ~/.local/state/nvim{,.bak} +-- mv ~/.cache/nvim{,.bak} diff --git a/lua/core/cursor.lua b/lua/core/cursor.lua new file mode 100644 index 0000000..6a1ea5e --- /dev/null +++ b/lua/core/cursor.lua @@ -0,0 +1,435 @@ +-- === +-- === 光标移动neovide +-- === +-- 判断 CPU 架构 +local arch = jit and jit.arch or "" +local is_arm = arch:match("arm") or arch:match("aarch64") +if is_arm then + vim.o.guifont = "ComicShannsMono Nerd Font:h24" +end +vim.keymap.set('n', '', '"+y', { noremap = true }) +vim.keymap.set('v', '', '"+y', { noremap = true }) +if vim.g.neovide then + if not is_arm then + vim.env.HTTP_PROXY = "http://127.0.0.1:7897" + vim.env.HTTPS_PROXY = "http://127.0.0.1:7897" + end + -- https://github.com/neovide/neovide/issues/1282 + vim.api.nvim_set_keymap('n', '', '"+p', { noremap = true }) + vim.api.nvim_set_keymap('v', '', '"+P', { noremap = true }) + vim.api.nvim_set_keymap('c', '', '+', { noremap = true }) + vim.api.nvim_set_keymap('i', '', '+', { noremap = true }) + vim.api.nvim_set_keymap('t', '', '"+Pi', { noremap = true }) + vim.g.neovide_opacity = 0.75 + -- vim.o.guifont = "ComicShannsMono Nerd Font:h16" -- text below applies for VimScript + vim.o.guifont = "Ioskeley Mono:h15" -- text below applies for VimScript + -- 全屏,可以在i3和sway的配置中设置 + -- vim.g.neovide_fullscreen = true +end +-- === +-- === map function +-- === +local function mapkey(mode, lhs, rhs) + vim.api.nvim_set_keymap(mode, lhs, rhs, { noremap = true }) +end +-- interestind stess +mapkey('', '[a', '1k') +mapkey('', '[r', '2k') +mapkey('', '[s', '3k') +mapkey('', '[t', '4k') +mapkey('', '[d', '5k') +mapkey('', '[h', '6k') +mapkey('', '[n', '7k') +mapkey('', '[e', '8k') +mapkey('', '[i', '9k') +mapkey('', '[ao', '10k') +mapkey('', '[aa', '11k') +mapkey('', '[ar', '12k') +mapkey('', '[as', '13k') +mapkey('', '[at', '14k') +mapkey('', '[ad', '15k') +mapkey('', '[ah', '16k') +mapkey('', '[an', '17k') +mapkey('', '[ae', '18k') +mapkey('', '[ai', '19k') +mapkey('', '[ro', '20k') +mapkey('', '[ra', '21k') +mapkey('', '[rr', '22k') +mapkey('', '[rs', '23k') +mapkey('', '[rt', '24k') +mapkey('', '[rd', '25k') +mapkey('', '[rh', '26k') +mapkey('', '[rn', '27k') +mapkey('', '[re', '28k') +mapkey('', '[ri', '29k') +mapkey('', '[so', '30k') +mapkey('', '[sa', '31k') +mapkey('', '[sr', '32k') +mapkey('', '[ss', '33k') +mapkey('', '[st', '34k') +mapkey('', '[sd', '35k') +mapkey('', '[sh', '36k') +mapkey('', '[sn', '37k') +mapkey('', '[se', '38k') +mapkey('', '[si', '39k') +mapkey('', '[to', '40k') +mapkey('', '[ta', '41k') +mapkey('', '[tr', '42k') +mapkey('', '[ts', '43k') +mapkey('', '[tt', '44k') +mapkey('', '[td', '45k') +mapkey('', '[th', '46k') +mapkey('', '[tn', '47k') +mapkey('', '[te', '48k') +mapkey('', '[ti', '49k') +mapkey('', '[do', '50k') +mapkey('', '[da', '51k') +mapkey('', '[dr', '52k') +mapkey('', '[ds', '53k') +mapkey('', '[dt', '54k') +mapkey('', '[dd', '55k') +mapkey('', '[dh', '56k') +mapkey('', '[dn', '57k') +mapkey('', '[de', '58k') +mapkey('', '[di', '59k') +mapkey('', '[ho', '60k') +mapkey('', '[ha', '61k') +mapkey('', '[hr', '62k') +mapkey('', '[hs', '63k') +mapkey('', '[ht', '64k') +mapkey('', '[hd', '65k') +mapkey('', '[hh', '66k') +mapkey('', '[hn', '67k') +mapkey('', '[he', '68k') +mapkey('', '[hi', '69k') +mapkey('', '[no', '70k') +mapkey('', '[na', '71k') +mapkey('', '[nr', '72k') +mapkey('', '[ns', '73k') +mapkey('', '[nt', '74k') +mapkey('', '[nd', '75k') +mapkey('', '[nh', '76k') +mapkey('', '[nn', '77k') +mapkey('', '[ne', '78k') +mapkey('', '[ni', '79k') +mapkey('', '[eo', '80k') +mapkey('', '[ea', '81k') +mapkey('', '[er', '82k') +mapkey('', '[es', '83k') +mapkey('', '[et', '84k') +mapkey('', '[ed', '85k') +mapkey('', '[eh', '86k') +mapkey('', '[en', '87k') +mapkey('', '[ee', '88k') +mapkey('', '[ei', '89k') +mapkey('', '[io', '90k') +mapkey('', '[ia', '91k') +mapkey('', '[ir', '92k') +mapkey('', '[is', '93k') +mapkey('', '[it', '94k') +mapkey('', '[id', '95k') +mapkey('', '[ih', '96k') +mapkey('', '[in', '97k') +mapkey('', '[ie', '98k') +mapkey('', '[ii', '99k') +mapkey('', '[aoo', '100k') +mapkey('', '[aoa', '101k') +mapkey('', '[aor', '102k') +mapkey('', '[aos', '103k') +mapkey('', '[aot', '104k') +mapkey('', '[aod', '105k') +mapkey('', '[aoh', '106k') +mapkey('', '[aon', '107k') +mapkey('', '[aoe', '108k') +mapkey('', '[aoi', '109k') +mapkey('', '[aao', '110k') +mapkey('', '[aaa', '111k') +mapkey('', '[aar', '112k') +mapkey('', '[aas', '113k') +mapkey('', '[aat', '114k') +mapkey('', '[aad', '115k') +mapkey('', '[aah', '116k') +mapkey('', '[aan', '117k') +mapkey('', '[aae', '118k') +mapkey('', '[aai', '119k') +mapkey('', '[aro', '120k') +mapkey('', '[ara', '121k') +mapkey('', '[arr', '122k') +mapkey('', '[ars', '123k') +mapkey('', '[art', '124k') +mapkey('', '[ard', '125k') +mapkey('', '[arh', '126k') +mapkey('', '[arn', '127k') +mapkey('', '[are', '128k') +mapkey('', '[ari', '129k') +mapkey('', '[aso', '130k') +mapkey('', '[asa', '131k') +mapkey('', '[asr', '132k') +mapkey('', '[ass', '133k') +mapkey('', '[ast', '134k') +mapkey('', '[asd', '135k') +mapkey('', '[ash', '136k') +mapkey('', '[asn', '137k') +mapkey('', '[ase', '138k') +mapkey('', '[asi', '139k') +mapkey('', '[ato', '140k') +mapkey('', '[ata', '141k') +mapkey('', '[atr', '142k') +mapkey('', '[ats', '143k') +mapkey('', '[att', '144k') +mapkey('', '[atd', '145k') +mapkey('', '[ath', '146k') +mapkey('', '[atn', '147k') +mapkey('', '[ate', '148k') +mapkey('', '[ati', '149k') +mapkey('', '[ado', '150k') +mapkey('', '[ada', '151k') +mapkey('', '[adr', '152k') +mapkey('', '[ads', '153k') +mapkey('', '[adt', '154k') +mapkey('', '[add', '155k') +mapkey('', '[adh', '156k') +mapkey('', '[adn', '157k') +mapkey('', '[ade', '158k') +mapkey('', '[adi', '159k') +mapkey('', '[aho', '160k') +mapkey('', '[aha', '161k') +mapkey('', '[ahr', '162k') +mapkey('', '[ahs', '163k') +mapkey('', '[aht', '164k') +mapkey('', '[ahd', '165k') +mapkey('', '[ahh', '166k') +mapkey('', '[ahn', '167k') +mapkey('', '[ahe', '168k') +mapkey('', '[ahi', '169k') +mapkey('', '[ano', '170k') +mapkey('', '[ana', '171k') +mapkey('', '[anr', '172k') +mapkey('', '[ans', '173k') +mapkey('', '[ant', '174k') +mapkey('', '[and', '175k') +mapkey('', '[anh', '176k') +mapkey('', '[ann', '177k') +mapkey('', '[ane', '178k') +mapkey('', '[ani', '179k') +mapkey('', '[aeo', '180k') +mapkey('', '[aea', '181k') +mapkey('', '[aer', '182k') +mapkey('', '[aes', '183k') +mapkey('', '[aet', '184k') +mapkey('', '[aed', '185k') +mapkey('', '[aeh', '186k') +mapkey('', '[aen', '187k') +mapkey('', '[aee', '188k') +mapkey('', '[aei', '189k') +mapkey('', '[aio', '190k') +mapkey('', '[aia', '191k') +mapkey('', '[air', '192k') +mapkey('', '[ais', '193k') +mapkey('', '[ait', '194k') +mapkey('', '[aid', '195k') +mapkey('', '[aih', '196k') +mapkey('', '[ain', '197k') +mapkey('', '[aie', '198k') +mapkey('', '[aii', '199k') + + +mapkey("", "'a", "1j") +mapkey("", "'r", "2j") +mapkey("", "'s", "3j") +mapkey("", "'t", "4j") +mapkey("", "'d", "5j") +mapkey("", "'h", "6j") +mapkey("", "'n", "7j") +mapkey("", "'e", "8j") +mapkey("", "'i", "9j") +mapkey("", "'ao", "10j") +mapkey("", "'aa", "11j") +mapkey("", "'ar", "12j") +mapkey("", "'as", "13j") +mapkey("", "'at", "14j") +mapkey("", "'ad", "15j") +mapkey("", "'ah", "16j") +mapkey("", "'an", "17j") +mapkey("", "'ae", "18j") +mapkey("", "'ai", "19j") +mapkey("", "'ro", "20j") +mapkey("", "'ra", "21j") +mapkey("", "'rr", "22j") +mapkey("", "'rs", "23j") +mapkey("", "'rt", "24j") +mapkey("", "'rd", "25j") +mapkey("", "'rh", "26j") +mapkey("", "'rn", "27j") +mapkey("", "'re", "28j") +mapkey("", "'ri", "29j") +mapkey("", "'so", "30j") +mapkey("", "'sa", "31j") +mapkey("", "'sr", "32j") +mapkey("", "'ss", "33j") +mapkey("", "'st", "34j") +mapkey("", "'sd", "35j") +mapkey("", "'sh", "36j") +mapkey("", "'sn", "37j") +mapkey("", "'se", "38j") +mapkey("", "'si", "39j") +mapkey("", "'to", "40j") +mapkey("", "'ta", "41j") +mapkey("", "'tr", "42j") +mapkey("", "'ts", "43j") +mapkey("", "'tt", "44j") +mapkey("", "'td", "45j") +mapkey("", "'th", "46j") +mapkey("", "'tn", "47j") +mapkey("", "'te", "48j") +mapkey("", "'ti", "49j") +mapkey("", "'do", "50j") +mapkey("", "'da", "51j") +mapkey("", "'dr", "52j") +mapkey("", "'ds", "53j") +mapkey("", "'dt", "54j") +mapkey("", "'dd", "55j") +mapkey("", "'dh", "56j") +mapkey("", "'dn", "57j") +mapkey("", "'de", "58j") +mapkey("", "'di", "59j") +mapkey("", "'ho", "60j") +mapkey("", "'ha", "61j") +mapkey("", "'hr", "62j") +mapkey("", "'hs", "63j") +mapkey("", "'ht", "64j") +mapkey("", "'hd", "65j") +mapkey("", "'hh", "66j") +mapkey("", "'hn", "67j") +mapkey("", "'he", "68j") +mapkey("", "'hi", "69j") +mapkey("", "'no", "70j") +mapkey("", "'na", "71j") +mapkey("", "'nr", "72j") +mapkey("", "'ns", "73j") +mapkey("", "'nt", "74j") +mapkey("", "'nd", "75j") +mapkey("", "'nh", "76j") +mapkey("", "'nn", "77j") +mapkey("", "'ne", "78j") +mapkey("", "'ni", "79j") +mapkey("", "'eo", "80j") +mapkey("", "'ea", "81j") +mapkey("", "'er", "82j") +mapkey("", "'es", "83j") +mapkey("", "'et", "84j") +mapkey("", "'ed", "85j") +mapkey("", "'eh", "86j") +mapkey("", "'en", "87j") +mapkey("", "'ee", "88j") +mapkey("", "'ei", "89j") +mapkey("", "'io", "90j") +mapkey("", "'ia", "91j") +mapkey("", "'ir", "92j") +mapkey("", "'is", "93j") +mapkey("", "'it", "94j") +mapkey("", "'id", "95j") +mapkey("", "'ih", "96j") +mapkey("", "'in", "97j") +mapkey("", "'ie", "98j") +mapkey("", "'ii", "99j") +mapkey("", "'aoo", "100j") +mapkey("", "'aoa", "101j") +mapkey("", "'aor", "102j") +mapkey("", "'aos", "103j") +mapkey("", "'aot", "104j") +mapkey("", "'aod", "105j") +mapkey("", "'aoh", "106j") +mapkey("", "'aon", "107j") +mapkey("", "'aoe", "108j") +mapkey("", "'aoi", "109j") +mapkey("", "'aao", "110j") +mapkey("", "'aaa", "111j") +mapkey("", "'aar", "112j") +mapkey("", "'aas", "113j") +mapkey("", "'aat", "114j") +mapkey("", "'aad", "115j") +mapkey("", "'aah", "116j") +mapkey("", "'aan", "117j") +mapkey("", "'aae", "118j") +mapkey("", "'aai", "119j") +mapkey("", "'aro", "120j") +mapkey("", "'ara", "121j") +mapkey("", "'arr", "122j") +mapkey("", "'ars", "123j") +mapkey("", "'art", "124j") +mapkey("", "'ard", "125j") +mapkey("", "'arh", "126j") +mapkey("", "'arn", "127j") +mapkey("", "'are", "128j") +mapkey("", "'ari", "129j") +mapkey("", "'aso", "130j") +mapkey("", "'asa", "131j") +mapkey("", "'asr", "132j") +mapkey("", "'ass", "133j") +mapkey("", "'ast", "134j") +mapkey("", "'asd", "135j") +mapkey("", "'ash", "136j") +mapkey("", "'asn", "137j") +mapkey("", "'ase", "138j") +mapkey("", "'asi", "139j") +mapkey("", "'ato", "140j") +mapkey("", "'ata", "141j") +mapkey("", "'atr", "142j") +mapkey("", "'ats", "143j") +mapkey("", "'att", "144j") +mapkey("", "'atd", "145j") +mapkey("", "'ath", "146j") +mapkey("", "'atn", "147j") +mapkey("", "'ate", "148j") +mapkey("", "'ati", "149j") +mapkey("", "'ado", "150j") +mapkey("", "'ada", "151j") +mapkey("", "'adr", "152j") +mapkey("", "'ads", "153j") +mapkey("", "'adt", "154j") +mapkey("", "'add", "155j") +mapkey("", "'adh", "156j") +mapkey("", "'adn", "157j") +mapkey("", "'ade", "158j") +mapkey("", "'adi", "159j") +mapkey("", "'aho", "160j") +mapkey("", "'aha", "161j") +mapkey("", "'ahr", "162j") +mapkey("", "'ahs", "163j") +mapkey("", "'aht", "164j") +mapkey("", "'ahd", "165j") +mapkey("", "'ahh", "166j") +mapkey("", "'ahn", "167j") +mapkey("", "'ahe", "168j") +mapkey("", "'ahi", "169j") +mapkey("", "'ano", "170j") +mapkey("", "'ana", "171j") +mapkey("", "'anr", "172j") +mapkey("", "'ans", "173j") +mapkey("", "'ant", "174j") +mapkey("", "'and", "175j") +mapkey("", "'anh", "176j") +mapkey("", "'ann", "177j") +mapkey("", "'ane", "178j") +mapkey("", "'ani", "179j") +mapkey("", "'aeo", "180j") +mapkey("", "'aea", "181j") +mapkey("", "'aer", "182j") +mapkey("", "'aes", "183j") +mapkey("", "'aet", "184j") +mapkey("", "'aed", "185j") +mapkey("", "'aeh", "186j") +mapkey("", "'aen", "187j") +mapkey("", "'aee", "188j") +mapkey("", "'aei", "189j") +mapkey("", "'aio", "190j") +mapkey("", "'aia", "191j") +mapkey("", "'air", "192j") +mapkey("", "'ais", "193j") +mapkey("", "'ait", "194j") +mapkey("", "'aid", "195j") +mapkey("", "'aih", "196j") +mapkey("", "'ain", "197j") +mapkey("", "'aie", "198j") +mapkey("", "'aii", "199j") diff --git a/lua/core/init.lua b/lua/core/init.lua new file mode 100644 index 0000000..8edbc86 --- /dev/null +++ b/lua/core/init.lua @@ -0,0 +1,87 @@ +-- === +-- === Editor behavior +-- === + +-- 开启左侧数字 +vim.o.number = true +-- 使用相对数 +vim.o.relativenumber = true +-- 高亮当前行 +vim.o.cursorline = false +-- 一行不能完全显示时自动换行 +vim.o.wrap = true +-- 在最后一行显示一些内容 +vim.o.showcmd = true +-- 命令模式显示补全菜单 +vim.o.wildmenu = true +-- /搜索时忽略大小写 +vim.o.ignorecase = true +-- /搜索时智能大小写 +vim.o.smartcase = true +-- 共享系统剪切 +vim.o.clipboard = 'unnamedplus' +-- 设置键 +vim.o.tabstop = 2 +vim.o.shiftwidth = 2 +vim.o.softtabstop = 2 +-- 随文件自动更改当前路径 +vim.o.autochdir = true +-- 在光标上方和下方保留的最小屏幕行数 +vim.o.scrolloff = 4 +-- 自动缩进 +vim.o.smartindent = true +-- 100毫秒没有输入文件将会自动保存交换文件 +vim.o.updatetime = 100 +-- 开启鼠标 +vim.o.mouse = 'a' +-- 开启颜色 +vim.o.termguicolors = true +-- 将updatetime设置为较低的值以提高性能 +vim.opt.updatetime = 200 +-- 指定keyword +vim.opt.iskeyword = "_,49-57,A-Z,a-z" +-- 让全局默认边框变成rounded或single +vim.o.winborder = 'rounded' +-- 始终隐藏字符(不依赖语法高亮),在 Markdown 文件中,粗体、斜体等标记字符可能会被隐藏 +-- vim.opt.conceallevel = 2 + +-- 设置编码格式 +vim.o.fileencodings = 'utf-8,gb2312,gb18030,gbk,ucs-bom,cp936,latin1' +vim.o.enc = 'utf8' + +-- 保存修改历史 +vim.o.swapfile = true +vim.o.undofile = true + +-- 保存折叠记录,在某些独立的窗口会报错 +-- vim.cmd 'au BufWinLeave * silent mkview' +-- vim.cmd 'au BufWinEnter * silent loadview' + +-- 打开文件时进入上次编辑的位置 +vim.cmd([[au BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif]]) + +-- fcitx5在normal模式时自动切换为英文输入法,摘自fcitx5的archwiki +vim.cmd([[ +autocmd InsertLeave * :silent !fcitx5-remote -c +autocmd BufCreate * :silent !fcitx5-remote -c +autocmd BufEnter * :silent !fcitx5-remote -c +autocmd BufLeave * :silent !fcitx5-remote -c +]]) +-- 意为: +-- 当 进入插入模式 时 触发shell命令 fcitx-remote -c 关闭输入法,改为英文输入 +-- 当 创建Buf 时 触发shell命令 fcitx-remote -c 关闭输入法,改为英文输入 +-- 当 进入Buf 时 触发shell命令 fcitx-remote -c 关闭输入法, 改为英文输入 +-- 当 离开Buf 时 触发shell命令 fcitx-remote -c 关闭输入法, 改为英文输入 + +-- 开启高亮复制 +vim.cmd([[au TextYankPost * silent! lua vim.highlight.on_yank()]]) + + + + + + + + + + diff --git a/lua/core/keymap.lua b/lua/core/keymap.lua new file mode 100644 index 0000000..4f9bbd9 --- /dev/null +++ b/lua/core/keymap.lua @@ -0,0 +1,305 @@ +-- === +-- === map function +-- === + +local function mapkey(mode, lhs, rhs) + vim.keymap.set(mode, lhs, rhs, { silent = true, nowait = true }) +end + +local function mapcmd(key, cmd) + vim.keymap.set("n", key, "" .. cmd .. "", { silent = true }) +end + +local function maplua(modes, key, action, desc) + vim.keymap.set(modes, key, action, { silent = true, noremap = true, desc = desc }) +end + +-- === +-- === Basic Mappings +-- === + +-- leader键设置为空格,; as : +vim.g.mapleader = " " +mapkey({ "n", "x", "o" }, ";", ":") + +-- 上/下一个搜索结果以及取消搜索结果高亮 +mapkey({ "n", "x", "o" }, "=", "nzz") +mapkey({ "n", "x", "o" }, "-", "Nzz") +mapcmd("", "nohlsearch") + +-- 保存和退出 +mapkey({ "n", "x", "o" }, "S", ":wall") +mapkey({ "n", "x", "o" }, "Q", ":qall") + +-- 撤销与反撤销 +mapkey({ "n", "x", "o" }, "l", "u") +mapkey({ "n", "x", "o" }, "L", "") + +-- 插入 +mapkey({ "n", "x", "o" }, "k", "i") +mapkey({ "n", "x", "o" }, "K", "I") + +mapkey('n', '', '') + +-- 折叠 +mapkey({ "n", "x", "o" }, "o", "za") +mapkey("x", "o", "zf") -- 可视模式创造折叠 +-- 读取保存的折叠 +-- mapkey({ "n", "x", "o" }, "a", ":loadview") + + +-- 以下两个映射默认了 +-- make Y to copy till the end of the line +-- mapkey('','Y','y$') + +-- make D to delete till the end of the line +-- mapkey('','D','d$') + +-- 打开lazygit,已用fm-nvim插件 +-- mapcmd('',':tabe:-tabmove:term lazygit') + +-- === +-- === treesitter +-- === + +-- 删除i键映射 +vim.api.nvim_create_autocmd("VimEnter", { -- 所有启动脚本、默认脚本都加载完毕后的最后一个事件 + callback = function() + -- 使用 pcall 忽略如果键位不存在时的报错 + pcall(vim.keymap.del, { "x", "o" }, "in") + end, +}) + +local function smart_select(ts_method, lsp_dir) + return function() + -- 如果当前不是普通文件(比如 Quickfix、帮助文档、终端等)那么我们直接发送一个原生的 按键并退出,恢复回车原本的功能 + if lsp_dir == 1 and vim.bo.buftype ~= "" then + local cr = vim.api.nvim_replace_termcodes("", true, false, true) + vim.api.nvim_feedkeys(cr, "n", false) + return + end + if vim.treesitter.get_parser(nil, nil, { error = false }) then + require("vim.treesitter._select")[ts_method](vim.v.count1) + else + vim.lsp.buf.selection_range(lsp_dir * vim.v.count1) + end + end +end +-- 扩大范围 (回车键):不断向上寻找父节点 (等价于官方的 an) +maplua({ "x", "o", "n" }, "", smart_select("select_parent", 1), "扩大 Treesitter/LSP 范围") +-- 缩小范围 (退格键):不断向下寻找子节点 +maplua({ "x", "o" }, "", smart_select("select_child", -1), "缩小 Treesitter/LSP 范围") + +-- === +-- === Cursor Movement +-- === +-- New cursor movement (the default arrow keys are used for resizing windows) + +-- ^ +-- u +-- < n i > +-- e +-- v +mapkey({ "n", "x", "o" }, "n", "h") +mapkey({ "n", "x", "o" }, "u", "k") +mapkey({ "n", "x", "o" }, "e", "j") +mapkey({ "n", "x", "o" }, "i", "l") + +-- 更快的导航 +mapkey({ "n", "x", "o" }, "U", "5k") +mapkey({ "n", "x", "o" }, "E", "5j") +mapkey({ "n", "x", "o" }, "N", "0") +mapkey({ "n", "x", "o" }, "I", "$") +-- 向下滚动半页,默认向上滚动半页 +mapkey({ "n", "x", "o" }, "", "") + +-- 更快的行导航 +mapkey({ "n", "x", "o" }, "W", "5W") +mapkey({ "n", "x", "o" }, "B", "5B") + +-- === +-- === Window management +-- === + +-- 使用 + 新方向键 在分屏之间移动 +mapkey({ "n", "x", "o" }, "w", "w") +mapkey({ "n", "x", "o" }, "u", "k") +mapkey({ "n", "x", "o" }, "e", "j") +mapkey({ "n", "x", "o" }, "n", "h") +mapkey({ "n", "x", "o" }, "i", "l") + +-- 使用s + 新方向键 进行分屏 +mapcmd("su", "set nosplitbelow:split:set splitbelow") +mapcmd("se", "set splitbelow:split") +mapcmd("sn", "set nosplitright:vsplit:set splitright") +mapcmd("si", "set splitright:vsplit") + +-- 使用方向键来调整窗口大小 +mapcmd("", "res +5") +mapcmd("", "res -5") +mapcmd("", "vertical resize-5") +mapcmd("", "vertical resize+5") + +-- 使分屏窗口上下分布 +mapkey({ "n", "x", "o" }, "sh", "tK") +-- 使分屏窗口左右分布 +mapkey({ "n", "x", "o" }, "sv", "tH") + +-- 按 + q 关闭当前窗口下方的窗口 +mapkey({ "n", "x", "o" }, "q", "j:q") + +-- === +-- === Tab management +-- === + +-- tu创建新标签,已用bufferline.nvim替代 +-- mapcmd("tu", "tabe") +-- 在标签之间移动 +-- mapcmd('tn','-tabnext') +-- mapcmd('ti','+tabnext') + +-- 移动标签的位置 +-- mapcmd('tmn','-tabmove') +-- mapcmd('tmi','+tabmove') + +-- 关闭当前标签,已用close-buffers替代 +-- mapcmd('tq','tabc') + +-- === +-- === 批量缩进方法 +-- === +-- 操作为,esc从编辑模式退到命令模式,将光标移到需要缩进的行的行首,然后按shift+v,可以看到该行已被选中,且左下角提示为“可视” +-- 按键盘上的上下方向键,如这里按向下的箭头,选中所有需要批量缩进的行 +-- 按shift+>,是向前缩进一个tab值,按shift+<,则是缩回一个tab值 + +mapkey("x", "<", "", ">gv") +mapkey("x", "", "", ">gv") + +-- === +-- === 批量替换 +-- === + +-- 设置快捷键,替换所有文件内容 +mapcmd("sa", "lua search_and_replace()") +-- 设置快捷键,替换当前文件内容 +mapcmd("sr", "lua search_and_replace_current_file()") + +-- 替换当前目录及子目录下所有文件内容 +function search_and_replace() + -- 获取用户输入的查找内容,使用 input() 函数动态输入替换内容 + local search_text = vim.fn.input("Search for: ") + + -- 获取用户输入的替换内容 + local replace_text = vim.fn.input("Replace with: ") + + -- 执行替换命令 + if search_text ~= "" and replace_text ~= "" then + local cmd = 'execute "!grep -rl \\"' + .. search_text + .. '\\" ./ | xargs sed -i \\"s/' + .. search_text + .. "/" + .. replace_text + .. '/g\\""' + vim.cmd(cmd) + print("Replaced all occurrences of '" .. search_text .. "' with '" .. replace_text .. "'") + else + print("Search or replace text cannot be empty.") + end +end + +-- 替换当前文件内容 +function search_and_replace_current_file() + -- 获取用户输入的查找内容 + local search_text = vim.fn.input("Search for in current file: ") + + -- 获取用户输入的替换内容 + local replace_text = vim.fn.input("Replace with: ") + + -- 执行替换命令 + if search_text ~= "" and replace_text ~= "" then + -- 使用 sed 替换当前文件中的匹配内容,并正确转义引号 + local cmd = string.format("!sed -i 's/%s/%s/g' %%", search_text, replace_text) + vim.cmd(cmd) + print("Replaced all occurrences of '" .. search_text .. "' with '" .. replace_text .. "' in current file.") + else + print("Search or replace text cannot be empty.") + end +end + +-- === +-- === 临时“存档”文件当前的版本,并与后续的修改进行 diff 对比 +-- === + +-- 创建 :DiffOrig 自定义命令,这个命令会打开一个垂直分屏,加载当前文件存盘时的版本,并启动 diff 模式 +vim.api.nvim_create_user_command( + 'DiffOrig', + function() + -- 在创建新窗口前,先保存当前文件的 filetype + local original_filetype = vim.bo.filetype + -- 打开一个垂直分屏,并准备好临时缓冲区 + vim.cmd('vert new | set buftype=nofile') + -- 在新的临时缓冲区里,设置我们刚才保存的 filetype,这是确保语法高亮的关键! + vim.bo.filetype = original_filetype + -- 读取原始文件的磁盘内容,并启动 diff + vim.cmd('read ++edit # | 0d_ | diffthis | wincmd p | diffthis') + end, + { force = true } +) +-- dd 将会执行 :DiffOrig 命令 +mapcmd('dd', 'DiffOrig') + +-- === +-- === Other useful stuff +-- === + +-- 打开一个终端窗口 +mapcmd("/", "set splitbelow:split:res +10:term") + +-- 按两下空格跳转到占位符<++>,并进入插入模式 +mapkey({ "n", "x", "o" }, "", "/<++>:nohlsearchc4l") + +-- 拼写检查 +mapcmd("sc", "set spell!") + +-- === +-- === 运行代码(该功能已经迁移到plugins/coderunner.lua) +-- === + +-- vim.cmd([[ +-- au filetype dart noremap r :wall:Telescope flutter commands +-- au filetype python noremap r :wall:set splitbelow:sp:term uv run % +-- au filetype go noremap r :wall:set splitbelow:sp:term go run % +-- au filetype markdown noremap r :PeekClose:PeekOpen +-- au filetype rust noremap r :wall:set splitbelow:sp:term cargo run +-- ]]) + + +-- === +-- === map function external environment +-- === + +-- 下面的函数给外部文件调用的 +-- 使用示例如下 +-- local map = require("core.keymap") +-- map:cmd('p','PasteImg') +local map = {} +function map:key(mode, lhs, rhs) + vim.keymap.set(mode, lhs, rhs, { silent = true }) +end + +function map:cmd(key, cmd) + vim.keymap.set("n", key, "" .. cmd .. "", { silent = true }) +end + +function map:lua(key, txt_or_func) + if type(txt_or_func) == "string" then + vim.keymap.set("n", key, "lua " .. txt_or_func .. "", { silent = true }) + else + vim.keymap.set("n", key, txt_or_func, { silent = true }) + end +end + +return map diff --git a/lua/core/markdown_table_format.lua b/lua/core/markdown_table_format.lua new file mode 100644 index 0000000..e40dbb2 --- /dev/null +++ b/lua/core/markdown_table_format.lua @@ -0,0 +1,197 @@ +local table_line_char = { + { ':', ':' }, + { ':', '-' }, + { '-', ':' }, +} + +local table_line_char_insert = { + { ':', ':' }, + { ':', ' ' }, + { ' ', ':' }, +} + +---Check if the line_number is a markdown table +---@param line_number integer +---@return integer +local function check_markdown_table(line_number) + local line = vim.api.nvim_buf_get_lines(0, line_number - 1, line_number, true)[1] + return string.match(line, '^|.*|$') +end + +---Find the starting or ending line of the markdown table +---@param range integer 1 or -1 +---@return integer +local function find_markdown_table(range) + local cursor_line, end_line = vim.fn.line('.'), range == -1 and 1 or vim.fn.line('$') + + for l = cursor_line, end_line, range do + if not check_markdown_table(l) then + return l - range + end + if (range == -1 and l == 1) or (range == 1 and l == vim.fn.line('$')) then --- if in the first line or last line + return l + end + end + + return -1 +end + +---Get markdown table cells width +---@param table_contents table +---@return table integers +local function get_markdown_table_cells_width(table_contents) + local width = {} + for _ = 1, #table_contents[1], 1 do + table.insert(width, 0) + end + for i = 1, #table_contents, 1 do + if i ~= 2 then + for _, cell in ipairs(table_contents[i]) do + width[_] = math.max(width[_], cell and vim.fn.strdisplaywidth(cell) or 0) + end + end + end + return width +end + +---Update markdown table's cell contents +---@param table_contents table +---@param width table +---@return table +local function update_cell_contents(table_contents, width) + local function add_space(cell, num) -- add space at markdown table cell contents + cell = ' ' .. cell .. ' ' + cell = cell .. string.rep(' ', num) + return cell + end + + local function get_chars(cell) -- add chars at second line's left and right + local char_left = string.sub(cell, 1, 1) + local char_right = string.sub(cell, #cell) + return { char_left, char_right } + end + + local function get_table_line_char_id(chars) -- get chars at second line's left and right + for i, v in ipairs(table_line_char) do -- leave insert + if chars[1] == v[1] and chars[2] == v[2] then + return i + end + end + for i, v in ipairs(table_line_char_insert) do -- type "|" + if chars[1] == v[1] and chars[2] == v[2] then + return i + end + end + return 0 + end + + local function add_chars(cell, chars) -- update cell contents at second line's left and right + cell = chars[1] .. cell .. chars[2] + return cell + end + + for i, cells in ipairs(table_contents) do -- traversal markdown table lines and update + if i == 2 then + for j, _ in ipairs(cells) do + local chars = get_chars(table_contents[i][j]) + local id = get_table_line_char_id(chars) + table_contents[i][j] = string.rep('-', width[j]) + table_contents[i][j] = + add_chars(table_contents[i][j], id ~= 0 and table_line_char[id] or { '-', '-' }) + end + else + for j, cell in ipairs(cells) do + local change_length = width[j] - vim.fn.strdisplaywidth(cell) + table_contents[i][j] = add_space(cell, change_length) + end + end + end + + return table_contents +end + +---change every lines's tables to string +---@param table_contents table +---@return table +local function cells_to_table(table_contents) + local corner_char = '|' + for i = 1, #table_contents, 1 do + local line = corner_char + for j = 1, #table_contents[i], 1 do + line = line .. table_contents[i][j] .. corner_char + end + table_contents[i] = line + end + return table_contents +end + +local function format_markdown_table() + if not check_markdown_table(vim.fn.line('.')) then -- check if the curso is in markdown table + return + end + + -- find the staring line and ending line of markdown table + local table_start_line, table_end_line = find_markdown_table(-1), find_markdown_table(1) + local table_contents = {} + + ---change table to cells + ---@param line string + ---@param lnum integer + local function table_to_cells(line, lnum) + local table_cells = {} + for cell in line:gmatch('([^|]+)%|') do + if lnum ~= 1 then + cell = cell:match('^%s*(.-)%s*$') + end + table.insert(table_cells, cell) + end + table.insert(table_contents, table_cells) + end + + -- traversal markdown table lines + for lnum = table_start_line, table_end_line, 1 do + local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1] + table_to_cells(line, lnum - table_start_line) + end + + local width = get_markdown_table_cells_width(table_contents) + + table_contents = update_cell_contents(table_contents, width) + table_contents = cells_to_table(table_contents) + + vim.api.nvim_buf_set_lines(0, table_start_line - 1, table_end_line, true, table_contents) +end + +local function format_markdown_table_lines() + local current_line = vim.api.nvim_get_current_line() + local cursor_pos = vim.api.nvim_win_get_cursor(0) + local char = current_line:sub(cursor_pos[2], cursor_pos[2]) + if char == '|' and cursor_pos[2] ~= 1 then + format_markdown_table() + local length = #vim.api.nvim_get_current_line() + vim.api.nvim_win_set_cursor(0, { cursor_pos[1], length }) + end +end + +-- return { +-- format_markdown_table = format_markdown_table, +-- format_markdown_table_lines = format_markdown_table_lines, +-- } + +-- markdown_table_format +local au = vim.api.nvim_create_autocmd +local group = vim.api.nvim_create_augroup('KicamonGroup', {}) +au('InsertLeave', { + group = group, + pattern = '*.md', + callback = function() + format_markdown_table() + end, +}) +au('TextChangedI', { + group = group, + pattern = '*.md', + callback = function() + format_markdown_table_lines() + end, +}) diff --git a/lua/core/md-snippets.lua b/lua/core/md-snippets.lua new file mode 100644 index 0000000..4f11ebb --- /dev/null +++ b/lua/core/md-snippets.lua @@ -0,0 +1,25 @@ +vim.cmd([[ +autocmd Filetype markdown inoremap ,f /<++>:nohlsearch"_c4l +autocmd Filetype markdown inoremap ,w /<++>:nohlsearch"_c5l +autocmd Filetype markdown inoremap ,n --- +autocmd Filetype markdown inoremap ,b **** <++>F*hi +autocmd Filetype markdown inoremap ,s ~~~~ <++> F~hi +autocmd Filetype markdown inoremap ,i ** <++>F*i +autocmd Filetype markdown inoremap ,d `` <++>F`i +autocmd Filetype markdown inoremap ,c ``````<++>3kA +autocmd Filetype markdown inoremap ,m - [ ] +autocmd Filetype markdown inoremap ,p ![](<++>) F[a +autocmd Filetype markdown inoremap ,a [](<++>) F[a +autocmd Filetype markdown inoremap ,1 # +autocmd Filetype markdown inoremap ,2 ## +autocmd Filetype markdown inoremap ,3 ### +autocmd Filetype markdown inoremap ,4 #### +autocmd Filetype markdown inoremap ,l -------- +]]) + +-- some modify +-- autocmd Filetype markdown inoremap ,c ```<++>```<++>4kA +-- autocmd Filetype markdown inoremap ,1 #<++>kA +-- autocmd Filetype markdown inoremap ,2 ##<++>kA +-- autocmd Filetype markdown inoremap ,3 ###<++>kA +-- autocmd Filetype markdown inoremap ,4 ####<++>kA diff --git a/lua/pack/configs/blinkcmp.lua b/lua/pack/configs/blinkcmp.lua new file mode 100644 index 0000000..4bf75cf --- /dev/null +++ b/lua/pack/configs/blinkcmp.lua @@ -0,0 +1,75 @@ +-- === 自动补全插件 (Blink.cmp) === +if vim.g.vscode then return end + +local P = { + name = "blink.cmp", + module = "blink.cmp", + deps = { "friendly-snippets" }, + -- build_cmd = "cargo build --release", +} + +-- PackUtils.setup_listener(P.name, P.build_cmd) + +vim.api.nvim_create_autocmd({ "InsertEnter", "CmdlineEnter", "LspAttach" }, { + once = true, + callback = function() + -- 调用引擎的 load 方法,把 setup 逻辑作为匿名函数传进去 + PackUtils.load(P, function(plugin) + plugin.setup({ + fuzzy = { + prebuilt_binaries = { + force_version = 'v*', + }, + }, + cmdline = { + -- keymap = { [""] = { "select_and_accept", "fallback" } }, + completion = { + list = { selection = { preselect = false, auto_insert = true } }, + menu = { auto_show = function() return vim.fn.getcmdtype() == ":" end }, + ghost_text = { enabled = false }, + }, + }, + keymap = { + preset = "none", + [""] = { "show", "show_documentation", "hide_documentation" }, + [""] = { "accept", "fallback" }, + [""] = { "select_prev", "snippet_backward", "fallback" }, + [""] = { "select_next", "snippet_forward", "fallback" }, + [""] = { "scroll_documentation_up", "fallback" }, + [""] = { "scroll_documentation_down", "fallback" }, + [""] = { "snippet_forward", "select_next", "fallback" }, + [""] = { "snippet_backward", "select_prev", "fallback" }, + }, + completion = { + keyword = { range = "full" }, + documentation = { auto_show = true, auto_show_delay_ms = 0 }, + list = { selection = { preselect = false, auto_insert = false } }, + }, + enabled = function() + return not vim.tbl_contains({}, vim.bo.filetype) + and vim.bo.buftype ~= "prompt" + and vim.b.completion ~= false + end, + appearance = { + use_nvim_cmp_as_default = true, + nerd_font_variant = "mono", + }, + sources = { + default = { "buffer", "lsp", "path", "snippets" }, + providers = { + buffer = { score_offset = 5 }, + path = { score_offset = 3 }, + lsp = { score_offset = 2 }, + snippets = { score_offset = 1 }, + -- cmdline = { -- 输入超过3个及以上字母才触发补全 + -- min_keyword_length = function(ctx) + -- if ctx.mode == "cmdline" and string.find(ctx.line, " ") == nil then return 3 end + -- return 0 + -- end, + -- }, + }, + }, + }) + end) + end +}) diff --git a/lua/pack/configs/bufferline.lua b/lua/pack/configs/bufferline.lua new file mode 100644 index 0000000..6a18a14 --- /dev/null +++ b/lua/pack/configs/bufferline.lua @@ -0,0 +1,49 @@ +local map = require("core.keymap") +-- 新建空缓冲区,neovim自带的 +map:cmd('tu', 'enew') +-- 关闭当前缓冲区,neovim自带的,完整命令bdelete +map:cmd('tq', 'bd') + +-- 在缓冲区之间移动 +map:cmd('tn', 'BufferLineCyclePrev') +map:cmd('ti', 'BufferLineCycleNext') + +-- 移动缓冲区的位置 +map:cmd('tmn', 'BufferLineMovePrev') +map:cmd('tmi', 'BufferLineMoveNext') + +-- 关闭缓冲区 +map:cmd('tN', 'BufferLineCloseLeft') +map:cmd('tI', 'BufferLineCloseRight') +map:cmd('tQ', 'BufferLineCloseOthers') + +-- === 顶部标签栏 (Bufferline) === +if vim.g.vscode then return end + +local P = { + name = "bufferline.nvim", + module = "bufferline", + deps = { "nvim-web-devicons" }, -- 确保图标库先加载 +} + +-- 懒加载触发器:打开或新建文件时触发 +vim.api.nvim_create_autocmd({ "BufReadPost", "BufNewFile" }, { + once = true, + callback = function() + PackUtils.load(P, function(plugin) + plugin.setup({ + options = { + modified_icon = "", + buffer_close_icon = "×", + -- show_buffer_close_icons = false, + max_name_length = 14, + max_prefix_length = 13, + tab_size = 10, + indicator = { + style = "none", + }, + }, + }) + end) + end +}) diff --git a/lua/pack/configs/coderunner.lua b/lua/pack/configs/coderunner.lua new file mode 100644 index 0000000..b01e6ba --- /dev/null +++ b/lua/pack/configs/coderunner.lua @@ -0,0 +1,53 @@ +if vim.g.vscode then return end + +local P = { + name = "code_runner.nvim", + module = "code_runner", + deps = {}, +} + +-- 定义映射配置表 +local mappings = { + { ft = { "rust", "python" }, cmd = "RunCode", desc = "Save and Run Code" }, + { ft = "markdown", cmd = "PeekClose;PeekOpen", desc = "Reload Markdown Preview" }, + { ft = "dart", cmd = "Telescope flutter commands", desc = "Open Flutter Commands" }, + { ft = "go", cmd = "set splitbelow;sp;term go run %", desc = "Run Go file" }, +} + +-- 只有进入这些文件类型时,才会为当前 buffer 绑定 r 键 +local ft_group = vim.api.nvim_create_augroup("CodeRunnerLazy", { clear = true }) + +for _, entry in ipairs(mappings) do + vim.api.nvim_create_autocmd("FileType", { + pattern = entry.ft, + group = ft_group, + callback = function(args) + -- 为当前 buffer 绑定 r 键 + vim.keymap.set("n", "r", function() + if vim.bo.modified then vim.cmd("wall") end + -- 只有命令中包含 "RunCode" 时,才加载代码运行器插件 + if string.find(entry.cmd, "RunCode") then + PackUtils.load(P, function(plugin) + plugin.setup({ + -- project = { + -- ["~/sixsixsix"] = { + -- name = "sixsixsix", + -- description = "六爻网页排盘", + -- command = "cargo run --release" + -- }, + -- }, + filetype = { + python = "uv run $fileName", + rust = { "cargo run --release" }, + }, + }) + end) + end + -- 处理多条命令的情况 (用分号分隔) + for c in string.gmatch(entry.cmd, "[^;]+") do + vim.cmd(c) + end + end, { buffer = args.buf, desc = entry.desc, silent = true }) + end, + }) +end diff --git a/lua/pack/configs/conform.lua b/lua/pack/configs/conform.lua new file mode 100644 index 0000000..42c6ab2 --- /dev/null +++ b/lua/pack/configs/conform.lua @@ -0,0 +1,61 @@ +-- === 代码格式化 (conform.nvim) === +if vim.g.vscode then return end + +local P = { + name = "conform.nvim", + module = "conform", + deps = { + "mason.nvim", + "mason-registry", + }, +} + +-- 提取出统一的配置变量 +local formatters_by_ft = { + python = { "isort", "black" }, + rust = { "rust-analyzer", lsp_format = "fallback" }, + toml = { "templ" }, + html = { "djlint" }, + sh = { "shfmt" }, + zsh = { "shfmt" }, +} + + +-- 辅助函数:从指定文件类型的配置中提取所有工具名称(去重) +local function get_ensure_installed_for_ft(ft, ft_table) + local tools = {} + local cfg = ft_table[ft] + if type(cfg) == "table" then + for _, item in ipairs(cfg) do + if type(item) == "string" then + tools[item] = true + end + end + elseif type(cfg) == "string" then + tools[cfg] = true + end + local list = {} + for tool, _ in pairs(tools) do + table.insert(list, tool) + end + return list +end + +-- 快捷键纯懒加载:只在按下快捷键时激活 +vim.keymap.set({ "n", "v" }, "f", function() + PackUtils.load(P, function(plugin) + plugin.setup({ -- At a minimum, you will need to set up some formatters by filetype + formatters_by_ft = formatters_by_ft + }) + end) + local registry = require("mason-registry") + local ft = vim.bo.filetype + local tools = get_ensure_installed_for_ft(ft, formatters_by_ft) + for _, tool in ipairs(tools) do + if not registry.is_installed(tool) then + vim.notify("Installing formatter: " .. tool, vim.log.levels.INFO) + registry.get_package(tool):install() + end + end + require("conform").format({ async = true, lsp_fallback = true }) +end, { desc = "格式化代码(检测安装缺失工具)" }) diff --git a/lua/pack/configs/indentblankline.lua b/lua/pack/configs/indentblankline.lua new file mode 100644 index 0000000..6459fb2 --- /dev/null +++ b/lua/pack/configs/indentblankline.lua @@ -0,0 +1,42 @@ +-- === 彩虹缩进 === + +local P = { + name = "indent-blankline.nvim", -- 仓库名 + module = "ibl", -- require模块名 +} + +-- 懒加载触发器 +vim.api.nvim_create_autocmd({ + "UIEnter", -- vim.schedule(function() +}, { + callback = function() + vim.schedule(function() + PackUtils.load(P, function(plugin) + local highlight = { + "RainbowBlue", + "RainbowViolet", + "RainbowRed", + "RainbowYellow", + "RainbowGreen", + "RainbowOrange", + "RainbowCyan", + } + local hooks = require("ibl.hooks") + -- create the highlight groups in the highlight setup hook, so they are reset + -- every time the colorscheme changes + hooks.register(hooks.type.HIGHLIGHT_SETUP, function() + vim.api.nvim_set_hl(0, "RainbowRed", { fg = "#E06C75" }) + vim.api.nvim_set_hl(0, "RainbowYellow", { fg = "#E5C07B" }) + vim.api.nvim_set_hl(0, "RainbowBlue", { fg = "#61AFEF" }) + vim.api.nvim_set_hl(0, "RainbowOrange", { fg = "#D19A66" }) + vim.api.nvim_set_hl(0, "RainbowGreen", { fg = "#98C379" }) + vim.api.nvim_set_hl(0, "RainbowViolet", { fg = "#C678DD" }) + vim.api.nvim_set_hl(0, "RainbowCyan", { fg = "#56B6C2" }) + end) + plugin.setup({ + indent = { highlight = highlight } + }) + end) + end) + end +}) diff --git a/lua/pack/configs/kommentary.lua b/lua/pack/configs/kommentary.lua new file mode 100644 index 0000000..66176f0 --- /dev/null +++ b/lua/pack/configs/kommentary.lua @@ -0,0 +1,26 @@ +-- 注释快捷键 +local map = require("core.keymap") +map:key("n", "cn", "kommentary_line_increase") +map:key("x", "cn", "kommentary_visual_increase") -- 选择模式下注释/反注释后退出该模式 +map:key("n", "cu", "kommentary_line_decrease") +map:key("x", "cu", "kommentary_visual_decrease") + +local P = { + name = "b3nj5m1n/kommentary", -- 仓库名 + module = "kommentary.config", -- require模块名 +} + +-- 懒加载触发器 +vim.api.nvim_create_autocmd({ + "UIEnter", +}, { + callback = function() + vim.schedule(function() + PackUtils.load(P, function(plugin) + plugin.configure_language("default", { + prefer_single_line_comments = true, + }) + end) + end) + end +}) diff --git a/lua/pack/configs/lspconfig.lua b/lua/pack/configs/lspconfig.lua new file mode 100644 index 0000000..6f8ea51 --- /dev/null +++ b/lua/pack/configs/lspconfig.lua @@ -0,0 +1,121 @@ +-- === LSP 核心配置 (Lspconfig + Mason) === +if vim.g.vscode then return end + +-- 1. 环境探测:判断 CPU 架构,决定安装哪些 LSP +local arch = jit and jit.arch or "" +local is_arm = arch:match("arm") or arch:match("aarch64") + +local servers = { "lua_ls", "rust_analyzer", "pylsp" } +if not is_arm then + vim.list_extend(servers, { "marksman", "ts_ls", "svelte", "cssls", "html" }) +end + +-- 2. 插件配置清单 +local P = { + name = "nvim-lspconfig", + module = "lspconfig", + deps = { "mason.nvim", "mason-lspconfig.nvim", "inlay-hints.nvim" }, +} + +-- 3. 懒加载触发器:当打开文件时触发 +vim.api.nvim_create_autocmd({ "BufReadPost", "BufNewFile" }, { + callback = function() + PackUtils.load(P, function() + -- === A. 基础依赖初始化 (Mason) === + require("mason").setup() + require("mason-lspconfig").setup({ + ensure_installed = servers, + }) + require("inlay-hints").setup() + + -- === B. 全局诊断设置 === + vim.diagnostic.config({ + signs = { + text = { + [vim.diagnostic.severity.ERROR] = "✘", + [vim.diagnostic.severity.WARN] = "▲", + [vim.diagnostic.severity.HINT] = "⚑", + [vim.diagnostic.severity.INFO] = "»", + }, + }, + }) + + -- === C. 全局快捷键映射 === + -- local opts = { noremap = true, silent = true } + vim.keymap.set("n", "h", vim.lsp.buf.hover, opts) -- h显示提示文档 + vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts) -- gd跳转到定义 + vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts) -- gD跳转到声明(例如c语言中的头文件中的原型、一个变量的extern声明) + vim.keymap.set("n", "go", vim.lsp.buf.type_definition, opts) -- go跳转到变量类型定义的位置(例如一些自定义类型) + vim.keymap.set("n", "rn", vim.lsp.buf.rename, opts) -- rn变量重命名 + vim.keymap.set("n", "aw", vim.lsp.buf.code_action, opts) -- aw可以在出现警告或错误的地方打开建议的修复方法 + vim.keymap.set("n", "d", vim.diagnostic.open_float, opts) -- d浮动窗口显示所在行警告或错误信息 + vim.keymap.set("n", "-", vim.diagnostic.goto_prev, opts) -- -跳转到上一处警告或错误的地方 + vim.keymap.set("n", "=", vim.diagnostic.goto_next, opts) -- +跳转到下一处警告或错误的地方 + -- vim.keymap.set("n", "gr", vim.lsp.buf.references, opts) -- gr跳转到引用了对应变量或函数的位置,改用snacks + -- vim.keymap.set({ 'n', 'x' }, 'f', function() vim.lsp.buf.format({ async = true }) end, opts) -- f进行代码格式化 + + -- === D. 特定 LSP 配置 (使用 Neovim 0.11+ vim.lsp.config 语法) === + + -- Python (pylsp) + uv 虚拟环境自适应 + vim.lsp.config("pylsp", { + on_init = function(client) + -- 1. 安全获取 root_dir + local root_dir = client.config.root_dir + -- 2. 只有当 root_dir 不为 nil 时才进行后续探测 + if root_dir then + local venv_python = root_dir .. "/.venv/bin/python" + -- 3. 检查虚拟环境文件是否真的存在且可读 + if vim.fn.filereadable(venv_python) == 1 then + client.config.settings.pylsp.plugins.jedi.environment = venv_python + -- 只有修改了设置才发送通知 + client.notify("workspace/didChangeConfiguration", { + settings = client.config.settings + }) + end + end + -- 无论是否找到虚拟环境,都返回 true 让 LSP 继续启动 + return true + end, + settings = { + pylsp = { + plugins = { + jedi = { environment = nil } + } + } + }, + }) + + -- Lua (lua_ls) + vim.lsp.config("lua_ls", { + settings = { + ["Lua"] = { + hint = { enable = true }, + diagnostics = { globals = { "vim", "require", "opts", "PackUtils", "jit" } }, + }, + }, + }) + + -- Go (gopls) + vim.lsp.config("gopls", { + settings = { + ["gopls"] = { + hints = { + rangeVariableTypes = true, + parameterNames = true, + constantValues = true, + assignVariableTypes = true, + compositeLiteralFields = true, + compositeLiteralTypes = true, + functionTypeParameters = true, + }, + }, + }, + }) + + -- 遍历列表并正式启用服务器 + for _, server in ipairs(servers) do + vim.lsp.enable(server) + end + end) + end +}) diff --git a/lua/pack/configs/noice.lua b/lua/pack/configs/noice.lua new file mode 100644 index 0000000..71df01b --- /dev/null +++ b/lua/pack/configs/noice.lua @@ -0,0 +1,28 @@ +-- === noice === +if vim.g.vscode then return end + +local P = { + name = "noice.nvim", + module = "noice", + deps = { "nui.nvim" }, +} + +-- 复刻 "VeryLazy" 策略 +vim.api.nvim_create_autocmd("UIEnter", { + callback = function() + -- vim.schedule 的作用是:别卡住界面的渲染,等 UI 画完了、闲下来了,再偷偷加载 + vim.schedule(function() + PackUtils.load(P, function(plugin) + plugin.setup({ + presets = { + bottom_search = true, -- use a classic bottom cmdline for search + command_palette = true, -- position the cmdline and popupmenu together + long_message_to_split = true, -- long messages will be sent to a split + inc_rename = false, -- enables an input dialog for inc-rename.nvim + lsp_doc_border = false, -- add a border to hover docs and signature help + }, + }) + end) + end) + end +}) diff --git a/lua/pack/configs/peek.lua b/lua/pack/configs/peek.lua new file mode 100644 index 0000000..482357d --- /dev/null +++ b/lua/pack/configs/peek.lua @@ -0,0 +1,36 @@ +if vim.g.vscode then return end + +local P = { + name = "peek.nvim", + module = "peek", + deps = {}, + -- 编译命令:需要环境中安装了 deno + build_cmd = { "deno", "task", "--quiet", "build:fast" }, +} + +PackUtils.setup_listener(P.name, P.build_cmd) + +-- 2. 封装加载逻辑 +local function load_peek() + PackUtils.load(P, function(plugin) + plugin.setup({ + port = 9000, + app = { "zen", "-private-window" }, + -- app = { "firefox-esr", "-private-window" }, + -- app = { "google-chrome-stable", "--app=http://localhost:9000/?theme=dark", "--incognito" }, + }) + end) +end + +-- 在插件未加载时,这些命令就存在了。一旦被调用,它们会先加载插件,再执行真正的功能。 +vim.api.nvim_create_user_command("PeekOpen", function() + load_peek() -- 触发 PackUtils.load (包含构建检查) + require("peek").open() +end, { desc = "Lazy load and open Peek" }) + +vim.api.nvim_create_user_command("PeekClose", function() + -- 如果插件没加载,Close 命令通常不需要做任何事,或者也触发加载 + if PackUtils.is_initialized[P.name] then + require("peek").close() + end +end, { desc = "Close Peek" }) diff --git a/lua/pack/configs/snacks.lua b/lua/pack/configs/snacks.lua new file mode 100644 index 0000000..c1e7ad4 --- /dev/null +++ b/lua/pack/configs/snacks.lua @@ -0,0 +1,37 @@ +-- === Snacks === +if vim.g.vscode then return end + +local P = { + name = "snacks.nvim", + module = "snacks", +} + +PackUtils.load(P, function(plugin) + plugin.setup({ + image = {}, + lazygit = {}, + notifier = {}, -- 替代了folke/noice.nvim插件的rcarriga/nvim-notify依赖 + picker = { + win = { + input = { + keys = { -- Esc直接关闭窗口,不进入normal模式 + [""] = { "close", mode = { "n", "i" } }, + [""] = { "list_down", mode = { "i", "n" } }, + [""] = { "list_up", mode = { "i", "n" } }, + } + } + } + }, + }) +end) + +local map = require("core.keymap") +map:lua("", "Snacks.lazygit()") +map:lua("gr", "Snacks.picker.lsp_references()") + +-- 彻底清除 Neovim 0.10+ 自带的 LSP 默认映射,解除 `gr` 的 1秒等待延迟 +local default_lsp_keys = { "grr", "grn", "gra", "gri", "grt", "grx" } +for _, key in ipairs(default_lsp_keys) do + pcall(vim.keymap.del, "n", key) -- 使用 pcall 忽略找不到映射时的报错 + pcall(vim.keymap.del, "x", key) +end diff --git a/lua/pack/configs/treesitter.lua b/lua/pack/configs/treesitter.lua new file mode 100644 index 0000000..e728d3f --- /dev/null +++ b/lua/pack/configs/treesitter.lua @@ -0,0 +1,53 @@ +if vim.g.vscode then return end + +local P = { + name = "nvim-treesitter", + module = "nvim-treesitter", + build_cmd = ":TSUpdate", +} + +PackUtils.setup_listener(P.name, P.build_cmd) + +local ensure_installed = { + "json", + "rust", + "python", + "lua", + "markdown", + "bash", + "java", +} + +-- 在 FileType 确定时,检查、安装并启动 +vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("NativeTreesitter", { clear = true }), + callback = function(args) + local buf = args.buf + local ft = vim.bo[buf].filetype + -- 过滤无效 buffer, 终端, 以及 yazi + if ft == "" or ft == "yazi" or vim.bo[buf].buftype ~= "" then return end + -- 过滤大型文件 (大于 100KB 不开启 Treesitter,防止卡顿) + local max_filesize = 100 * 1024 + local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf)) + if ok and stats and stats.size > max_filesize then return end + -- 获取标准化的 Parser 名称 + local lang = vim.treesitter.language.get_lang(ft) or ft + -- 检查该语言是否在我们的配置列表中 + if vim.tbl_contains(ensure_installed, lang) then + local no_err, is_added = pcall(vim.treesitter.language.add, lang) + if not no_err or not is_added then + -- 只有在需要安装时,才把 nvim-treesitter 插件加载进内存 + PackUtils.load(P, function() end) + vim.notify("🌱 Installing " .. lang .. " parser...", vim.log.levels.INFO) + local ts = require("nvim-treesitter") + if ts.install then + ts.install({ lang }):wait(60000) + else + vim.cmd("TSInstall " .. lang) + end + end + -- 万事俱备,启动高亮 + pcall(vim.treesitter.start, buf, lang) + end + end, +}) diff --git a/lua/pack/configs/tv.lua b/lua/pack/configs/tv.lua new file mode 100644 index 0000000..ab9b6cc --- /dev/null +++ b/lua/pack/configs/tv.lua @@ -0,0 +1,47 @@ +-- === television模糊查找 === +if vim.g.vscode then return end + +local P = { + name = "tv.nvim", -- 仓库名 + module = "tv", -- require模块名 +} + +local function load_plugin() + PackUtils.load(P, function(plugin) + local h = plugin.handlers + plugin.setup({ + channels = { + ["git-files"] = { + handlers = { + [""] = h.open_as_files, + }, + }, + files = { + handlers = { + [""] = h.open_as_files, -- default: open selected files + }, + }, + -- `text`: ripgrep search through file contents + text = { + handlers = { + [''] = h.open_at_line, -- Jump to line:col in file + }, + }, + }, + }) + end) +end + +vim.keymap.set({ "n", "v" }, "", function() + load_plugin() + if vim.fs.root(0, '.git') then + vim.cmd('Tv git-files') + else + vim.cmd('Tv files') + end +end, { desc = "Smart Tv: git-files or files" }) + +vim.keymap.set({ "n", "v" }, "", function() + load_plugin() + vim.cmd('Tv text') +end, { desc = "模糊查找字符串" }) diff --git a/lua/pack/configs/yazi.lua b/lua/pack/configs/yazi.lua new file mode 100644 index 0000000..f466659 --- /dev/null +++ b/lua/pack/configs/yazi.lua @@ -0,0 +1,32 @@ +-- VSCode 拦截 +if vim.g.vscode then + local vscode = require("vscode") + vim.keymap.set("n", "tt", function() + vscode.action("yazi-vscode.toggle") + end, { noremap = true, silent = true, desc = "Toggle Yazi (VSCode)" }) + return +end + +vim.g.loaded_netrwPlugin = 1 + +local P = { + name = "yazi.nvim", + module = "yazi", + deps = { "plenary.nvim" }, +} + +-- 快捷键触发懒加载 +vim.keymap.set({ "n", "v" }, "tt", function() + -- 核心:直接调用引擎,把配置逻辑传进去 + PackUtils.load(P, function(plugin) + plugin.setup({ + open_for_directories = false, + keymaps = { show_help = "" }, + }) + end) + -- 提前触发 BufReadPost 的事件钩子,让 LSP 悄悄在后台 require 完毕 + vim.schedule(function() + vim.api.nvim_exec_autocmds("BufReadPost", { modeline = false }) + end) + vim.cmd("Yazi") +end, { desc = "Open yazi" }) diff --git a/lua/pack/plugins.lua b/lua/pack/plugins.lua new file mode 100644 index 0000000..bec836d --- /dev/null +++ b/lua/pack/plugins.lua @@ -0,0 +1,318 @@ +-- ============================================================== +-- 插件花名册 +-- ============================================================== +local specs = { + -- 公共依赖 + 'https://github.com/nvim-lua/plenary.nvim', + 'https://github.com/williamboman/mason.nvim', + 'https://github.com/mason-org/mason-registry', + 'https://github.com/nvim-tree/nvim-web-devicons', + -- blinkcmp.lua 自动补全、代码片段 + 'https://github.com/saghen/blink.cmp', + 'https://github.com/rafamadriz/friendly-snippets', + -- lspconfig.lua + 'https://github.com/neovim/nvim-lspconfig', + 'https://github.com/williamboman/mason-lspconfig.nvim', + 'https://github.com/MysticalDevil/inlay-hints.nvim', + -- treesitter.lua 需要安装tree-sitter-cli工具包 + 'https://github.com/nvim-treesitter/nvim-treesitter', + -- indentblankline.lua 彩虹缩进 + "https://github.com/lukas-reineke/indent-blankline.nvim", + -- conform.lua 格式化工具formatter + "https://github.com/stevearc/conform.nvim", + -- kommentary.lua 注释插件 + "https://github.com/b3nj5m1n/kommentary", + -- noice.lua 取代消息、命令行和弹出菜单的 UI + "https://github.com/folke/noice.nvim", + "https://github.com/MunifTanjim/nui.nvim", + -- snacks.lua 图片预览、lazygit、lsp_references模糊查找 + "https://github.com/folke/snacks.nvim", + -- tv.lua 模糊查找television + "https://github.com/alexpasmantier/tv.nvim", + -- coderunner.lua 运行代码 + 'https://github.com/CRAG666/code_runner.nvim', + -- peek.lua 预览markdown + 'https://github.com/cap153/peek.nvim', + -- bufferline.lua 顶部状态栏 + 'https://github.com/akinsho/bufferline.nvim', + -- yazi.lua 文件管理器 + 'https://github.com/mikavilpas/yazi.nvim', + -- 查看可用键位 + "https://github.com/folke/which-key.nvim", +} +-- 禁用插件:不会加载,不会下载(如果是新添加的),已在硬盘上不会被删除 +local disabled = { + -- { src = 'https://github.com/saghen/blink.cmp', version = 'v1.10.2' }, -- 指定版本,暂时不想用,但想留着源码 +} + +-- ============================================================== +-- 快捷管理命令 +-- ============================================================== + +-- 获取所有已安装插件的名称列表(用于 Tab 补全) +local function get_plugin_names(arg_lead) + local installed = vim.pack.get(nil, { info = false }) + local names = {} + for _, p in ipairs(installed) do + local name = p.spec.name + -- 只添加匹配开头字符串的插件 + if name:lower():find(arg_lead:lower(), 1, true) == 1 then + table.insert(names, name) + end + end + -- 排序让补全列表更整洁 + table.sort(names) + return names +end + +-- :PackUpdate 命令更新插件,不带参数更新全部 +vim.api.nvim_create_user_command("PackUpdate", function(opts) + local targets = #opts.fargs > 0 and opts.fargs or nil + if targets then + vim.notify("Checking updates for: " .. table.concat(targets, ", "), vim.log.levels.INFO) + else + vim.notify("Checking updates for all plugins...", vim.log.levels.INFO) + end + vim.pack.update(targets) +end, { + nargs = "*", -- 支持 0 到多个参数 + complete = get_plugin_names, -- 绑定补全函数 + desc = "Update specified or all plugins", +}) + +-- :PackStatus 命令查看插件当前状态和版本 +vim.api.nvim_create_user_command("PackStatus", function(opts) + local targets = #opts.fargs > 0 and opts.fargs or nil + vim.pack.update(targets, { offline = true }) +end, { + nargs = "*", + complete = get_plugin_names, + desc = "Check plugin status without downloading", +}) + +-- ============================================================== +-- 插件管理引擎 (PackUtils) (暴露给全局,供 configs/*.lua 调用) +-- ============================================================== +_G.PackUtils = { + is_building = {}, -- 记录各插件的构建状态,防止重复构建 + is_initialized = {}, -- 统一在这里管理所有插件的初始化状态 + disabled_plugins = {}, -- 专门记录被禁用的插件,供 load 拦截使用 +} + +-- [解析插件名] +function PackUtils.get_name(spec) + local url = type(spec) == "table" and spec.src or spec + return type(spec) == "table" and spec.name or url:match("([^/]+)$"):gsub("%.git$", "") +end + +-- [同步清理] 自动删除孤儿,并注册禁用名单 +function PackUtils.sync(active_specs, disabled_specs) + disabled_specs = disabled_specs or {} + local protected_names = {} + + -- 将插件加入受保护名单 + for _, spec in ipairs(active_specs) do + protected_names[PackUtils.get_name(spec)] = true + end + for _, spec in ipairs(disabled_specs) do + local name = PackUtils.get_name(spec) + protected_names[name] = true + PackUtils.disabled_plugins[name] = true -- 写入字典,供 load 拦截 + end + + -- 扫描磁盘 + local pack_dir = vim.fn.stdpath("data") .. "/site/pack" + local installed_plugins = {} + local packages = vim.fn.expand(pack_dir .. "/*", false, true) + for _, pkg in ipairs(packages) do + for _, type_dir in ipairs({ "start", "opt" }) do + local path = pkg .. "/" .. type_dir + if vim.fn.isdirectory(path) == 1 then + local dirs = vim.fn.expand(path .. "/*", false, true) + for _, dir in ipairs(dirs) do + local name = dir:match("([^/]+)$") + if name ~= "README.md" and name ~= "doc" then + table.insert(installed_plugins, name) + end + end + end + end + end + -- 找出既不在 active 也不在 disabled 里的孤儿 + local to_delete = {} + for _, installed in ipairs(installed_plugins) do + if not protected_names[installed] then + table.insert(to_delete, installed) + end + end + + if #to_delete > 0 then + vim.schedule(function() + vim.notify("🧹 Clean Up Orphaned Plugins: " .. table.concat(to_delete, ", "), vim.log.levels.INFO) + vim.pack.del(to_delete) + end) + end +end + +-- [动态路径] 获取插件根目录 +function PackUtils.get_root(name) + local paths = vim.api.nvim_get_runtime_file("pack/*/*/" .. name, true) + if #paths > 0 then return paths[1] end + local glob = vim.fn.globpath(vim.o.packpath, "pack/*/*/" .. name, 0, 1) + return glob[1] or nil +end + +-- [构建执行] 执行编译任务 +function PackUtils.run_build(name, build_cmd) + if PackUtils.disabled_plugins[name] then return end + if not build_cmd or PackUtils.is_building[name] then return end + local path = PackUtils.get_root(name) + if not path then return end + local stamp = path .. "/.build_done" + PackUtils.is_building[name] = true + + -- 判断是否为 Neovim 内部命令 (以 : 开头) + local is_vim_cmd = false + local vim_cmd_str = "" + + if type(build_cmd) == "string" and build_cmd:sub(1, 1) == ":" then + is_vim_cmd = true + vim_cmd_str = build_cmd:sub(2) + elseif type(build_cmd) == "table" and type(build_cmd[1]) == "string" and build_cmd[1]:sub(1, 1) == ":" then + is_vim_cmd = true + vim_cmd_str = table.concat(build_cmd, " "):sub(2) + end + + if is_vim_cmd then + -- 在当前实例的空闲时执行 vim.cmd + vim.schedule(function() + vim.notify("⚙️ Running " .. name .. " setup command...", vim.log.levels.INFO) + -- 确保插件在当前实例已经被加载 + pcall(vim.cmd.packadd, name) + -- 保护执行,防止命令错误导致编辑器崩溃 + local ok, err = pcall(vim.cmd, vim_cmd_str) + PackUtils.is_building[name] = false + if ok then + local f = io.open(stamp, "w") + if f then f:close() end + vim.notify("✅ " .. name .. " setup success.", vim.log.levels.INFO) + else + vim.notify("❌ " .. name .. " setup failed: " .. tostring(err), vim.log.levels.ERROR) + end + end) + else + local final_cmd = {} + if type(build_cmd) == "string" then + for word in build_cmd:gmatch("%S+") do + table.insert(final_cmd, word) + end + else + final_cmd = build_cmd + end + vim.schedule(function() vim.notify("⚙️ Building " .. name .. " (Background)...", vim.log.levels.INFO) end) + vim.system(final_cmd, { cwd = path }, function(out) + PackUtils.is_building[name] = false + if out.code == 0 then + local f = io.open(stamp, "w") + if f then f:close() end + vim.schedule(function() vim.notify("✅ " .. name .. " build success.", vim.log.levels.INFO) end) + else + vim.schedule(function() + vim.notify("❌ " .. name .. " build failed: " .. (out.stderr or "Unknown Error"), vim.log.levels.ERROR) + end) + end + end) + end +end + +-- [监听器] 注册安装/更新监听 +function PackUtils.setup_listener(name, build_cmd) + if PackUtils.disabled_plugins[name] then return end + if not build_cmd then return end + vim.api.nvim_create_autocmd('PackChanged', { + pattern = '*', + callback = function(ev) + if ev.data.spec.name == name and (ev.data.kind == "update" or ev.data.kind == "install") then + local stamp = ev.data.path .. "/.build_done" + os.remove(stamp) -- 自动删除.build_done文件触发构建 + PackUtils.run_build(name, build_cmd) + end + end + }) +end + +-- [健康检查] 如果没标记且有构建命令,则触发构建 +function PackUtils.check_health(name, build_cmd) + if PackUtils.disabled_plugins[name] then return end + if not build_cmd then return end + local path = PackUtils.get_root(name) + if path then + local stamp = path .. "/.build_done" + if vim.fn.filereadable(stamp) == 0 then + PackUtils.run_build(name, build_cmd) + end + end +end + +-- 全方位防崩加载引擎 +function PackUtils.load(P, config_fn) + if PackUtils.disabled_plugins[P.name] then return end + if PackUtils.is_initialized[P.name] then return end + PackUtils.check_health(P.name, P.build_cmd) + + -- 强制将主插件挂载到 runtimepath + pcall(vim.cmd.packadd, P.name) + + -- 保护依赖加载 (防止 dependencies 里的插件没下载) + if P.deps then + for _, dep in ipairs(P.deps) do + local dep_ok = pcall(vim.cmd.packadd, dep) + if not dep_ok then + vim.notify("Warning: " .. P.name .. " dependency [" .. dep .. "] missing", vim.log.levels.WARN) + end + end + end + + -- 保护 require (防止插件文件夹还没下载完) + local req_ok, plugin = pcall(require, P.module) + -- 如果失败,说明插件还没下载好或者路径不对,优雅退出 + if not req_ok then + -- 经过上面强制挂载后还是失败,且硬盘上确实有这个文件夹,那绝对是 module 填错了 + if PackUtils.get_root(P.name) then + vim.notify("Error: Plugin [" .. P.name .. "] module not found", vim.log.levels.ERROR) + end + return + end + + -- 保护 Setup 执行:使用 pcall 包裹传进来的匿名函数,防止 setup 里的参数写错导致崩溃 + if config_fn then + local setup_ok, err = pcall(config_fn, plugin) + if not setup_ok then + vim.notify("Error: " .. P.name .. " setup failed: " .. tostring(err), vim.log.levels.ERROR) + return + end + end + + -- 只有全部流程走通,才标记为已初始化 + PackUtils.is_initialized[P.name] = true +end + +-- ============================================================== +-- 执行启动流程 +-- ============================================================== + +-- 同步清理孤儿插件并注册禁用名单 +PackUtils.sync(specs, disabled) + +-- 正式下载/更新插件 +vim.pack.add(specs) + +-- 加载 configs/ 注册所有监听器 +local config_path = vim.fn.stdpath("config") .. "/lua/pack/configs" +if vim.fn.isdirectory(config_path) == 1 then + for name, type in vim.fs.dir(config_path) do + if type == "file" and name:match("%.lua$") then + pcall(require, "pack.configs." .. name:gsub("%.lua$", "")) + end + end +end diff --git a/nvim-pack-lock.json b/nvim-pack-lock.json new file mode 100644 index 0000000..e5e7104 --- /dev/null +++ b/nvim-pack-lock.json @@ -0,0 +1,92 @@ +{ + "plugins": { + "blink.cmp": { + "rev": "456d38d1cd3743926f329204c2340f3e7840aad6", + "src": "https://github.com/saghen/blink.cmp" + }, + "bufferline.nvim": { + "rev": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3", + "src": "https://github.com/akinsho/bufferline.nvim" + }, + "code_runner.nvim": { + "rev": "eed10eb4953bc172d7652f30d289a4e575028a98", + "src": "https://github.com/CRAG666/code_runner.nvim" + }, + "conform.nvim": { + "rev": "086a40dc7ed8242c03be9f47fbcee68699cc2395", + "src": "https://github.com/stevearc/conform.nvim" + }, + "friendly-snippets": { + "rev": "6cd7280adead7f586db6fccbd15d2cac7e2188b9", + "src": "https://github.com/rafamadriz/friendly-snippets" + }, + "indent-blankline.nvim": { + "rev": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03", + "src": "https://github.com/lukas-reineke/indent-blankline.nvim" + }, + "inlay-hints.nvim": { + "rev": "11be32be3761c6263df2311afb6baa0de0863967", + "src": "https://github.com/MysticalDevil/inlay-hints.nvim" + }, + "kommentary": { + "rev": "d5a111a3bc4109a8f913a5863c9092b3b3801482", + "src": "https://github.com/b3nj5m1n/kommentary" + }, + "mason-lspconfig.nvim": { + "rev": "25f609e7fca78af7cede4f9fa3af8a94b1c4950b", + "src": "https://github.com/williamboman/mason-lspconfig.nvim" + }, + "mason-registry": { + "rev": "0cf1d9c928faa72f625a7a595942daf69ad17e0d", + "src": "https://github.com/mason-org/mason-registry" + }, + "mason.nvim": { + "rev": "b03fb0f20bc1d43daf558cda981a2be22e73ac42", + "src": "https://github.com/williamboman/mason.nvim" + }, + "noice.nvim": { + "rev": "7bfd942445fb63089b59f97ca487d605e715f155", + "src": "https://github.com/folke/noice.nvim" + }, + "nui.nvim": { + "rev": "de740991c12411b663994b2860f1a4fd0937c130", + "src": "https://github.com/MunifTanjim/nui.nvim" + }, + "nvim-lspconfig": { + "rev": "bedca8b426b2fee0ccac596d167d71bbe971253f", + "src": "https://github.com/neovim/nvim-lspconfig" + }, + "nvim-treesitter": { + "rev": "4916d6592ede8c07973490d9322f187e07dfefac", + "src": "https://github.com/nvim-treesitter/nvim-treesitter" + }, + "nvim-web-devicons": { + "rev": "6e76c5e47e957fbf080b1fdac165c66dbd2e7cfb", + "src": "https://github.com/nvim-tree/nvim-web-devicons" + }, + "peek.nvim": { + "rev": "803815d18689b8f9e69d433b08ed767d7555128c", + "src": "https://github.com/cap153/peek.nvim" + }, + "plenary.nvim": { + "rev": "b9fd5226c2f76c951fc8ed5923d85e4de065e509", + "src": "https://github.com/nvim-lua/plenary.nvim" + }, + "snacks.nvim": { + "rev": "ad9ede6a9cddf16cedbd31b8932d6dcdee9b716e", + "src": "https://github.com/folke/snacks.nvim" + }, + "tv.nvim": { + "rev": "c0603ca8f31299c83deaa9a24a63d67116db25cd", + "src": "https://github.com/alexpasmantier/tv.nvim" + }, + "which-key.nvim": { + "rev": "3aab2147e74890957785941f0c1ad87d0a44c15a", + "src": "https://github.com/folke/which-key.nvim" + }, + "yazi.nvim": { + "rev": "78f7dc1ff5fef3ea60897ec356681f9aa0ed53d8", + "src": "https://github.com/mikavilpas/yazi.nvim" + } + } +} diff --git a/root_init.lua b/root_init.lua new file mode 100644 index 0000000..886bad5 --- /dev/null +++ b/root_init.lua @@ -0,0 +1,18 @@ +--[[ +用以下命令创建root账户neovim配置软链接 +sudo mkdir -p /root/.config/nvim/lua/ +sudo ln -s ~/.config/nvim/lua/core /root/.config/nvim/lua/core +sudo ln -s ~/.config/nvim/root_init.lua /root/.config/nvim/init.lua +]]-- + +-- 基本配置 +require("core.init") + +-- 基本键盘映射 +require("core.keymap") + +-- vim综合症 +require("core.cursor") + +-- markdown snippets +require("core.md-snippets") diff --git a/snippets/lua.json b/snippets/lua.json new file mode 100644 index 0000000..f4f34ca --- /dev/null +++ b/snippets/lua.json @@ -0,0 +1,62 @@ +{ + "sec": { + "prefix": "sec", + "body": [ + "-- ===", + "-- === ${0}", + "-- ===" + ], + "description": "Section comment" + }, + "github": { + "prefix": "github", + "body": [ + "\"https:\/\/github.com\/${0}\"," + ], + "description": "Github url" + }, + "nvpack": { + "prefix": "nvpack", + "body": [ + "-- === ${1:插件描述} ===", + "if vim.g.vscode then return end", + "", + "local P = {", + "\tname = \"${2:<++>}\", -- 仓库名", + "\tmodule = \"${3:<++>}\", -- require模块名", + "\tdeps = { ${4} },", + "\tbuild_cmd = ${5:nil},", + "}", + "", + "${6}-- 注册构建监听器", + "PackUtils.setup_listener(P.name, P.build_cmd)", + "", + "-- 懒加载触发器", + "vim.api.nvim_create_autocmd({", + "\t${7}-- \"InsertEnter\", \"CmdlineEnter\", -- 补全/命令行", + "\t-- \"BufReadPost\", \"BufNewFile\", -- 界面/语法类", + "\t-- \"LspAttach\", -- LSP 相关", + "\t-- \"FileType\", -- 确定文件类型", + "\t-- \"VimEnter\", -- 核心初始化完成", + "\t-- \"UIEnter\", -- vim.schedule(function()", + "}, {", + "\tcallback = function()", + "\t\tPackUtils.load(P, function(plugin)", + "\t\t\tplugin.setup({", + "\t\t\t\t$8", + "\t\t\t})", + "\t\tend)", + "\tend", + "})", + "-- vim.keymap.set({ \"n\", \"v\" }, \"${9:<++>}\", function()", + "-- \tPackUtils.load(P, function(plugin)", + "-- \t\tplugin.setup({", + "-- \t\t\t$10", + "-- \t\t})", + "-- \tend)", + "-- \tvim.cmd(\"${11:Command}\")", + "-- end, { desc = \"${0:描述}\" })" + ], + "description": "Neovim Native Package Loader (Pure Config)" + } +} diff --git a/snippets/markdown.json b/snippets/markdown.json new file mode 100644 index 0000000..318c3e8 --- /dev/null +++ b/snippets/markdown.json @@ -0,0 +1,47 @@ +{ + "t2": { + "prefix": "t2", + "body": [ + "| ${2:<++>} | <++> |", + "|-|-|", + "| <++> | <++> |${1}" + ], + "description": "Snippet t2" + }, + "t3": { + "prefix": "t3", + "body": [ + "| ${2:<++>} | <++> | <++> |", + "|-|-|-|", + "| <++> | <++> | <++> |${1}" + ], + "description": "Snippet t3" + }, + "t4": { + "prefix": "t4", + "body": [ + "| ${2:<++>} | <++> | <++> | <++> |", + "|-|-|-|-|", + "| <++> | <++> | <++> | <++> |${1}" + ], + "description": "Snippet t4" + }, + "t5": { + "prefix": "t5", + "body": [ + "| ${2:<++>} | <++> | <++> | <++> | <++> |", + "|-|-|-|-|-|", + "| <++> | <++> | <++> | <++> | <++> |${1}" + ], + "description": "Snippet t5" + }, + "t6": { + "prefix": "t6", + "body": [ + "| ${2:<++>} | <++> | <++> | <++> | <++> | <++> |", + "|-|-|-|-|-|-|", + "| <++> | <++> | <++> | <++> | <++> | <++> |${1}" + ], + "description": "Snippet t6" + } +} diff --git a/snippets/python.json b/snippets/python.json new file mode 100644 index 0000000..c026a11 --- /dev/null +++ b/snippets/python.json @@ -0,0 +1,17 @@ +{ + "pri": { + "prefix": "pri", + "body": [ + "print(${0})" + ], + "description": "print()" + }, + "main": { + "prefix": "main", + "body": [ + "if __name__ == '__main__':", + "\t${0}" + ], + "description": "if main" + } +} diff --git a/snippets/rust.json b/snippets/rust.json new file mode 100644 index 0000000..71b94b7 --- /dev/null +++ b/snippets/rust.json @@ -0,0 +1,73 @@ +{ + "main": { + "prefix": "main", + "body": [ + "fn main() {", + "\t${0}", + "}" + ], + "description": "custom main" + }, + "fn": { + "prefix": "fn", + "body": [ + "fn ${1}(${2}) ${3}{", + "\t${4}", + "}" + ], + "description": "custom function" + }, + "let":{ + "prefix": "let", + "body": [ + "let ${1} = ${2};" + ], + "description": "custom let" + }, + "letmut":{ + "prefix": "letm", + "body": [ + "let mut ${1} = ${2};" + ], + "description": "custom let mut" + }, + "pri":{ + "prefix": "pri", + "body": [ + "println!(\"${1}\"${2});" + ], + "description": "custom println" + }, + "println":{ + "prefix": "println", + "body": [ + "println!(\"${1}\"${2});" + ], + "description": "custom println" + }, + "print":{ + "prefix": "print", + "body": [ + "print!(\"${1}\"${2});" + ], + "description": "custom print" + }, + "if": { + "prefix": "if", + "body": [ + "if ${1} {", + "\t${2}", + "}" + ], + "description": "custom if" + }, + "for": { + "prefix": "for", + "body": [ + "for ${1} in ${2} {", + "\t${3}", + "}" + ], + "description": "custom for" + } +}