Initial Neovim config

- Add init.lua entry point
- Add lua/ modules: options, keymaps, autocmds, usercmds, pack, treesitter, lsp
- Add .gitignore
- Add nvim-pack-lock.json for plugin version locking

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-28 10:49:24 +08:00
commit a45929aff6
10 changed files with 488 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.omc/
others/

9
init.lua Normal file
View File

@ -0,0 +1,9 @@
require("vim._core.ui2").enable({})
require("options")
require("keymaps")
require("autocmds")
require("usercmds")
require("pack")
require("treesitter")
require("lsp")

36
lua/autocmds.lua Normal file
View File

@ -0,0 +1,36 @@
local group = vim.api.nvim_create_augroup("UserAutocmds", { clear = true })
-- 恢复上次编辑位置
vim.api.nvim_create_autocmd("BufReadPost", {
group = group,
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local row = mark[1]
local ft = vim.bo.filetype
if row > 1 and row <= vim.fn.line("$") then
if ft ~= "commit" and ft ~= "gitcommit" and not ft:match("xxd") and not ft:match("gitrebase") then
vim.api.nvim_win_set_cursor(0, mark)
end
end
end,
})
-- 注释延续行为
vim.api.nvim_create_autocmd("FileType", {
group = group,
callback = function()
vim.opt_local.formatoptions:remove("o") -- o/O 换行不延续注释
vim.opt_local.formatoptions:append("r") -- 回车延续注释
end,
})
-- 延迟设置 Treesitter 折叠(避免启动时加载 treesitter 模块)
vim.api.nvim_create_autocmd("BufEnter", {
group = group,
once = true,
callback = function()
vim.o.foldmethod = "expr"
vim.o.foldexpr = "v:lua.vim.treesitter.foldexpr()"
end,
})

154
lua/keymaps.lua Normal file
View File

@ -0,0 +1,154 @@
vim.g.mapleader = " "
local map = vim.keymap.set
map("x", "p", [["_dP]], { desc = "Paste over selection without losing yanked text" })
-- map({ "n", "v" }, "<leader>d", [["_d]], { desc = "Delete without yanking" })
map("n", "<Esc>", ":nohl<CR>", { desc = "Clear search highlighting", silent = true })
map("v", "<", "<gv", { desc = "Unindent and keep selection" })
map("v", ">", ">gv", { desc = "Indent and keep selection" })
map("n", "J", "mzJ`z", { desc = "Join lines without moving cursor" })
map("n", "<leader>ss", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]],
{ desc = "Replace word cursor is on globally" })
-- search
map("v", "<leader>ss", ":s/\\%V", { desc = "Search and replace in visual selection" })
-- general
map("n", "$", "g_")
map("v", "$", "g_")
map("v", ">", ">gv")
map("v", "<", "<gv")
-- native undotree
vim.keymap.set("n", "<leader>u", function()
vim.cmd.packadd("nvim.undotree")
require("undotree").open()
end, { desc = "Toggle Builtin Undotree" })
map("t", "<C-x>", "<c-\\><c-n>", { desc = "Escape termainl" })
map("n", "<leader>tt", ":term<CR>", { desc = "Open new terminal" })
-- tabs
map("n", "<leader>tc", ":tabclose<CR>", { desc = "Close current tab" })
map("n", "<leader>tn", ":tabnew<CR>", { desc = "New tab" })
map("n", "<leader>]", ":tabnext<CR>", { desc = "Next tab" })
map("n", "<leader>[", ":tabprevious<CR>", { desc = "Previous tab" })
-- yank path
map("n", "<leader>yp", function()
local path = vim.fn.expand("%")
vim.fn.setreg("+", path)
vim.notify("Copied relative path: " .. path, vim.log.levels.INFO)
end, { desc = "Yank relative file path" })
map("n", "<leader>yP", function()
local path = vim.fn.expand("%:p")
vim.fn.setreg("+", path)
vim.notify("Copied absolute path: " .. path, vim.log.levels.INFO)
end, { desc = "Yank absolute file path" })
-- buffer
map("n", "<leader>x", function()
local buf = vim.api.nvim_get_current_buf()
if vim.bo[buf].modified then
vim.ui.select({ "Yes", "No" }, {
prompt = "Buffer has unsaved changes. Close without saving?",
format_item = function(item)
return item
end,
}, function(choice)
if choice == "Yes" then
vim.api.nvim_buf_delete(buf, { force = true })
end
end)
else
vim.cmd.bdelete()
end
end, { desc = "Close current buffer" })
map("n", "<leader>bn", "<cmd>enew<CR>", { desc = "Buffer new" })
map("n", "<leader>bo", function()
local current = vim.api.nvim_get_current_buf()
local skipped_ft = { "NvimTree", "oil", "aerial" }
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if buf ~= current and vim.api.nvim_buf_is_loaded(buf) then
local ft = vim.bo[buf].filetype
if not vim.tbl_contains(skipped_ft, ft) then
vim.api.nvim_buf_delete(buf, { force = true })
end
end
end
end, { desc = "Close other buffers" })
-- mini.pick buffer picker (telescope buffers equivalent)
map("n", "<leader><leader>", function()
local MiniPick = require("mini.pick")
local delete_buf = function()
local matches = MiniPick.get_picker_matches()
local item = matches and matches.current
if not item or not item.bufnr then return end
local bufnr = item.bufnr
-- 先删除 bufferpcall 保护,避免删除当前窗口 buffer 时出错)
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
-- 刷新列表:只保留仍然有效的 buffer
if MiniPick.is_picker_active() then
local items = vim.tbl_filter(function(i)
return i.bufnr and vim.api.nvim_buf_is_valid(i.bufnr)
end, MiniPick.get_picker_items() or {})
MiniPick.set_picker_items(items)
end
end
-- 收集所有 buffer过滤特殊类型和未列出的按最近使用排序
local bufs = vim.tbl_filter(function(b)
return vim.bo[b.bufnr].buftype == "" and b.listed == 1
end, vim.fn.getbufinfo())
table.sort(bufs, function(a, b)
return a.lastused > b.lastused
end)
local items = {}
for _, info in ipairs(bufs) do
local name = info.name ~= "" and vim.fn.fnamemodify(info.name, ":.") or "[No Name]"
table.insert(items, {
text = name,
bufnr = info.bufnr,
})
end
MiniPick.start({
source = {
name = "Buffers",
items = items,
show = function(buf_id, items_arr, query)
MiniPick.default_show(buf_id, items_arr, query, { show_icons = true })
end,
},
mappings = {
delete_buffer = { char = "<C-d>", func = delete_buf },
},
})
end, { desc = "Buffers" })
-- mini.pick filetype picker
map("n", "<leader>ft", function()
local MiniPick = require("mini.pick")
local filetypes = vim.fn.getcompletion("", "filetype")
MiniPick.start({
source = {
name = "Filetypes",
items = filetypes,
choose = function(item)
vim.bo.filetype = item
end,
},
})
end, { desc = "Change filetype" })

52
lua/lsp.lua Normal file
View File

@ -0,0 +1,52 @@
require("mason").setup()
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, { desc = "Go to definition" })
vim.keymap.set('n', 'gh', vim.lsp.buf.hover, { desc = "Hover" })
vim.keymap.set("n", "<leader>fm", vim.lsp.buf.format, { desc = "Format Local buffer" })
vim.keymap.set("n", "df", vim.diagnostic.open_float, { desc = "Show line diagnostics" })
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "Code action" })
local diagnostic_goto = function(next, severity)
return function()
vim.diagnostic.jump({
count = (next and 1 or -1) * vim.v.count1,
severity = severity and vim.diagnostic.severity[severity] or nil,
float = true,
})
end
end
vim.keymap.set("n", "]d", diagnostic_goto(true), { desc = "Next Diagnostic" })
vim.keymap.set("n", "[d", diagnostic_goto(false), { desc = "Prev Diagnostic" })
vim.keymap.set("n", "]e", diagnostic_goto(true, "ERROR"), { desc = "Next Error" })
vim.keymap.set("n", "[e", diagnostic_goto(false, "ERROR"), { desc = "Prev Error" })
vim.keymap.set("n", "]w", diagnostic_goto(true, "WARN"), { desc = "Next Warning" })
vim.keymap.set("n", "[w", diagnostic_goto(false, "WARN"), { desc = "Prev Warning" })
vim.diagnostic.config({ virtual_text = true })
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = vim.tbl_deep_extend("force", capabilities, require("mini.completion").get_lsp_capabilities())
vim.lsp.config("*", { capabilities = capabilities })
vim.lsp.config("lua_ls", {
settings = {
Lua = {
diagnostics = { globals = { "vim" } },
},
},
})
vim.lsp.enable({
"html",
"cssls",
"gopls",
"vtsls",
"rust_analyzer",
"lua_ls",
"taplo",
"svelte",
"dartls",
"kotlin_lsp"
})

46
lua/options.lua Normal file
View File

@ -0,0 +1,46 @@
vim.g.netrw_banner = 0
vim.opt.nu = true
vim.opt.relativenumber = true
vim.opt.cursorline = true
vim.opt.cursorlineopt = "both"
vim.opt.autoread = true
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true
vim.opt.inccommand = "split"
vim.opt.splitbelow = true
vim.opt.splitright = true
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.laststatus = 3
vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.undodir = vim.fn.stdpath("data") .. "/undodir"
vim.opt.undofile = true
vim.opt.completeopt = "menuone,noselect,fuzzy,nosort"
vim.opt.shortmess:append("c")
vim.opt.clipboard:append("unnamedplus")
vim.opt.isfname:append("@-@")
vim.opt.guicursor = ""
vim.opt.scrolloff = 8
vim.opt.colorcolumn = "0"
vim.opt.signcolumn = "yes"
vim.api.nvim_create_autocmd("TextYankPost", {
desc = "Highlight when yanking (copying) text",
callback = function()
vim.hl.on_yank()
end,
})
vim.opt.foldlevel = 99 -- 打开文件时默认展开所有折叠

91
lua/pack.lua Normal file
View File

@ -0,0 +1,91 @@
vim.pack.add({
"https://github.com/nvim-mini/mini.nvim",
"https://github.com/rafamadriz/friendly-snippets",
{ src = "https://github.com/nvim-treesitter/nvim-treesitter", branch = "main" },
"https://github.com/neovim/nvim-lspconfig",
"https://github.com/mason-org/mason.nvim",
"https://github.com/tpope/vim-fugitive",
})
-- mini files ----
local MiniFiles = require("mini.files")
MiniFiles.setup({
mappings = {
go_in = "<CR>",
go_in_plus = "L",
go_out = "_",
go_out_plus = "H",
},
})
vim.keymap.set("n", "<leader>-", "<cmd>lua MiniFiles.open()<CR>", { desc = "Toggle mini file explorer" })
vim.keymap.set("n", "-", function()
MiniFiles.open(vim.api.nvim_buf_get_name(0), false)
MiniFiles.reveal_cwd()
end, { desc = "Toggle into currently opened file" })
---- mini notify ----
require("mini.notify").setup({
-- only show messages
content = {
format = function(notif)
return notif.msg
end,
},
})
--- mini cmdline completion ---
require("mini.cmdline").setup({
autocorrect = { enable = false }
})
--- mini surround ---
require("mini.surround").setup()
-- Default Keymaps
-- | `sa` | Add surrounding or Direct with 'saiw' |
-- | `sd` | Delete surrounding |
-- | `sr` | Replace surrounding |
-- | `sf` | Find surrounding (right) |
-- | `sF` | Find surrounding (left) |
-- | `sh` | Highlight surrounding |
-- | `sn` | Update n_lines |
-- | `l` / `n` | as suffix for prev/next |
--- mini picker ---
local MiniPick = require("mini.pick")
local MiniExtra = require("mini.extra")
MiniPick.setup()
MiniExtra.setup()
-- keymaps
vim.keymap.set("n", "<leader>ff", function() MiniPick.builtin.files() end, { desc = "Mini File Picker" })
vim.keymap.set("n", "<leader>fw", function() MiniPick.builtin.grep({ pattern = vim.fn.expand("<cword>") }) end, { desc = "Grep word/Search word" })
vim.keymap.set("n", "<leader>vh", function() MiniPick.builtin.help() end, { desc = "Mini Help" })
vim.keymap.set("n", "<leader>ds", function() MiniExtra.pickers.diagnostic() end, { desc = "Mini Picker Diagnostics" })
vim.keymap.set("n", "<leader>sk", function() MiniExtra.pickers.keymaps() end, { desc = 'Search keymaps' })
--- mini completions ---
require("mini.completion").setup({
lsp_completion = {
auto_setup = true,
}
})
--- mini snippets ---
local MiniSnippets = require("mini.snippets")
MiniSnippets.setup({
snippets = {
MiniSnippets.gen_loader.from_lang(), -- loads friendly-snippets
},
})
MiniSnippets.start_lsp_server({ match = false })
--- mini diff and fugitive ---
local MiniDiff = require("mini.diff")
MiniDiff.setup({
source = MiniDiff.gen_source.git({ index = false }),
})
vim.keymap.set("n", "<leader>gg", "<cmd>tabnew | Git | only<cr>", { desc = "Fugitive Full Page New Tab" })
vim.keymap.set("n", "<leader>gd", "<cmd>Gvdiffsplit<CR>", { desc = "Git diff split", })

49
lua/treesitter.lua Normal file
View File

@ -0,0 +1,49 @@
local treesitter = require("nvim-treesitter")
local ensure_installed = {
"lua",
"vim",
"vimdoc",
-- Web
"javascript",
"typescript",
"tsx",
"jsdoc",
"json",
"html",
"css",
"yaml",
-- Backend
"c",
"rust",
"toml",
"go",
"gomod",
"gosum",
"gowork",
-- Infra
"dockerfile",
"make",
}
treesitter.install(ensure_installed)
vim.api.nvim_create_autocmd("FileType", {
pattern = "*",
callback = function(args)
local buf = args.buf
local ft = vim.bo[buf].filetype
local lang = vim.treesitter.language.get_lang(ft)
if not lang then
return
end
local ok_add = pcall(vim.treesitter.language.add, lang)
if not ok_add then
return
end
pcall(vim.treesitter.start, buf, lang)
end,
})

21
lua/usercmds.lua Normal file
View File

@ -0,0 +1,21 @@
vim.api.nvim_create_user_command("PackAdd", function(opts)
vim.pack.add(opts.fargs)
end, { nargs = "+", desc = "Add plugins (:PackAdd user/repo1 user/repo2)" })
-- Pack Delete and Update cmds are built-in on Nightly 0.13
vim.api.nvim_create_user_command("PackDel", function(opts)
vim.pack.del(opts.fargs)
end, { nargs = "+", desc = "Delete plugins (:PackDel plugin1 plugin2)" })
vim.api.nvim_create_user_command("PackUpdate", function(opts)
-- checks if any argument is passed
if opts.args:match("%S") then
-- update specific plugins
local plugins = vim.split(opts.args, "%s+", { trimempty = true })
-- update only specified plugins
vim.pack.update(plugins)
else
-- update all
vim.pack.update()
end
end, { nargs = "*", desc = "Update all plugins or specific ones" })

28
nvim-pack-lock.json Normal file
View File

@ -0,0 +1,28 @@
{
"plugins": {
"friendly-snippets": {
"rev": "6cd7280adead7f586db6fccbd15d2cac7e2188b9",
"src": "https://github.com/rafamadriz/friendly-snippets"
},
"mason.nvim": {
"rev": "bb639d4bf385a4d89f478b83af4d770be05ab7eb",
"src": "https://github.com/mason-org/mason.nvim"
},
"mini.nvim": {
"rev": "44657837c7338e52727facc85c1d95bec1f6bd7c",
"src": "https://github.com/nvim-mini/mini.nvim"
},
"nvim-lspconfig": {
"rev": "6f76a3eeadc2ee235d74cd7d5319e95a261084af",
"src": "https://github.com/neovim/nvim-lspconfig"
},
"nvim-treesitter": {
"rev": "4916d6592ede8c07973490d9322f187e07dfefac",
"src": "https://github.com/nvim-treesitter/nvim-treesitter"
},
"vim-fugitive": {
"rev": "3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0",
"src": "https://github.com/tpope/vim-fugitive"
}
}
}