Neovim配置——从入门到放弃
前言
本来这个教程想分成十几篇来写的,但实在是懒,就放在这一篇里吧,有什么更新也会直接添加到文章后面。
本篇博文声明:
- 本篇博客不以完整的教程为目的,仅作为一个提纲性的参考
- 默认读者对vim/neovim有一定的了解,或者已经用了一段时间了,但是想系统性的调整自己的配置文件
- 任何第三方的博客都是二手资料,一般都是不如官方文档的,除非实在写的太烂,比如luasnip:),如果你看这篇博客看不懂,直接去翻插件的文档,如果英文看不懂,就算翻译一下总能看懂的,总比到处问强
- 不要盲目添加插件,没有完美的配置,只有适合自己的配置,在使用的过程中添加/删除插件,修改配置
- 天天改配置不如多写几行代码:(
关于用现成的配置比如LazyVim, LunarVim,还是完全自己配,这个完全看个人
- 不想折腾就用现成的,开箱即用,然后加上一些自定义配置,缺点是出了问题不知道在哪
- 自己配,知根知底,相对麻烦一些,但实际上也没那么麻烦
我的配置在github,个人使用,仅供参考。
放几张效果图:
启动页 | 文件搜索 |
---|---|
![]() |
![]() |
定义/引用查找 | 目录树/大纲 |
![]() |
![]() |
我的neovim配置结构
我的配置没有完全放弃对vim的支持,这部分用viml来设置,主要是一些基础的配置,比如高亮行,设置行号等等,还有一些基础的按键设置,目的是保留对部分服务器的简单支持,直接copy就行,当然,如果是自己的开发机的话,也可以通过rsync同步的方式把配置上传到开发机上。
配置的基本目录结构如下:
~/.config/nvim/ ├── debug.lua ├── init.lua neovim配置入口 ├── lazy-lock.json 插件版本信息 ├── lua │ ├── autoload/ 自己写的自动加载的脚本(插件) │ ├── helper/ 一些工具函数 │ └── plugins 插件目录 │ ├── dap/ 调试 │ ├── edit/ 编辑类 │ ├── lang/ 特定语言类,例如tex, c/cpp, python这样分类 │ ├── lsp/ lsp相关 │ ├── tools/ 工具类插件 │ ├── ui/ ui美化类插件 │ └── unused/ 暂时不用的插件,不加载 ├── my-snippets/ 我的代码片段 ├── README.md ├── stylua.toml lua格式化 ├── tasks.ini 代码运行全局配置 ├── test/ 各种乱七八糟的文件,测试效果用的 └── vimrc 基础配置
配置文件入口为 ~/.config/nvim/init.lua
,里面先加载vimrc,然后才是用lua写的插件配置。
插件管理器用的是folke大神写的lazy.nvim,目前主流的Neovim预配置项目都采用了这个管理器,推荐,具体用法在后面介绍。
vim.cmd.source(vim.fn.stdpath("config") .. "/vimrc") require("helper.path") require("helper.lazy").setup({ spec = { -- 经过这种import 方式导入插件的lua 文件必须返回一个table { import = "plugins.ui" }, { import = "plugins.edit" }, { import = "plugins.lsp" }, { import = "plugins.dap" }, { import = "plugins.tools" }, { import = "plugins.lang" }, { import = "autoload" }, }, dev = { path = "~/Projects/neovim/", }, })
同步配置
服务器连接github比较困难的情况下,可以用rsync把本地配置同步到远程,享受几乎一样的体验
sync_nvim() { local server=$1 rsync ~/.vimrc $server:~/.vimrc rsync -av --progress --delete --exclude test/ ~/.config/nvim/ $server:~/.config/nvim/ rsync -av --progress --exclude .git/ ~/.local/share/nvim/lazy/ $server:~/.local/share/nvim/lazy/ }
用法
sync_nvim ubuntu@192.168.1.2 # 或者 sync_nvim server_alias
基础配置
基础配置在~/.config/nvim/vimrc
,neovim会先加载这个文件,vim如果使用的话通过软链接的方式ln -s ~/.config/nvim/vimrc ~/.vimrc
这个文件大概分为这几个部分
options
常用设置选项keymaps
无插件按键映射autocmd
设置了几个自动命令gui config
主要是字体设置(不常用)vim only config
主要是vim内置的目录树netrw
和apt安装的vim-airline
由于这些配置具有一定的个人喜好,所以仅列出部分,详情可以去翻我的仓库
options
" 编码 set encoding=utf-8 set fileencodings=utf-8,gb2312,gbk,gb18030,latin1 set fileformat=unix set fileformats=unix,dos " 缩进与格式 filetype indent on set autoindent set smarttab set cindent set shiftwidth=4 set tabstop=4 set expandtab set softtabstop=4 set backspace=eol,start,indent " 搜索 set hlsearch set incsearch set ignorecase set smartcase
keymaps
这里先祭出一张忘了从哪抄来的表格
Mode | Norm | Ins | Cmd | Vis | Sel | Opr | Term | Lang | ~ Command +------+-----+-----+-----+-----+-----+------+------+ ~ [nore]map | yes | - | - | yes | yes | yes | - | - | n[nore]map | yes | - | - | - | - | - | - | - | [nore]map! | - | yes | yes | - | - | - | - | - | i[nore]map | - | yes | - | - | - | - | - | - | c[nore]map | - | - | yes | - | - | - | - | - | v[nore]map | - | - | - | yes | yes | - | - | - | x[nore]map | - | - | - | yes | - | - | - | - | s[nore]map | - | - | - | - | yes | - | - | - | o[nore]map | - | - | - | - | - | yes | - | - | t[nore]map | - | - | - | - | - | - | yes | - | l[nore]map | - | yes | yes | - | - | - | - | yes | Modes normal_mode = "n", insert_mode = "i", visual_mode = "v", visual_block_mode = "x", term_mode = "t", command_mode = "c",
下面列出部分映射
" 优化jk设置 nnoremap j gj nnoremap k gk vnoremap j gj vnoremap k gk " alt+hjkl移动行 " mini.move nnoremap <M-j> :m +1<CR>== vnoremap <M-j> :m '>+1<CR>gv=gv nnoremap <M-k> :m -2<CR>== vnoremap <M-k> :m '<-2<CR>gv=gv nnoremap <M-h> << vnoremap <M-h> <gv nnoremap <M-l> >> vnoremap <M-l> >gv " H, L jump to line home / end nnoremap H ^ nnoremap L $ vnoremap H ^ vnoremap L $ onoremap H ^ onoremap L $ " 切换buffer nnoremap <C-n> <cmd>bnext<CR> nnoremap <C-p> <cmd>bprevious<CR> " 简单的自动括号实现,给vim用的 if !has('nvim') inoremap ( ()<Left> inoremap <expr> ) getline(line('.'))[col('.')-1]==')' ? '<Right>' : ')' inoremap [ []<Left> inoremap <expr> ] getline(line('.'))[col('.')-1]==']' ? '<Right>' : ']' inoremap { {}<Left> inoremap <expr> } getline(line('.'))[col('.')-1]=='}' ? '<Right>' : '}' inoremap < <><Left> inoremap <expr> > getline(line('.'))[col('.')-1]=='>' ? '<Right>' : '>' " inoremap ' ''<Left> " inoremap <expr> ' getline(line('.'))[col('.')-1]=="'" ? '<Right>' : "'" " inoremap " ""<Left> " inoremap <expr> " getline(line('.'))[col('.')-1]=='"' ? '<Right>' : '"' " inoremap ` ``<Left> " inoremap <expr> ` getline(line('.'))[col('.')-1]=='`' ? '<Right>' : '`' endif
autocmd
augroup _general_settings autocmd! "这些文件类型按q退出 autocmd FileType qf,help,man,checkhealth,startuptime nnoremap <silent><buffer> q <cmd>close<CR> " json等文件tab设置为2,并高亮所在列 autocmd FileType json,json5,yaml setlocal shiftwidth=2 tabstop=2 softtabstop=2 cursorcolumn " neovim复制文本后高亮 if has('nvim') autocmd TextYankPost * silent!lua require('vim.highlight').on_yank({ timeout = 500 }) endif autocmd BufWinEnter * set formatoptions-=cro autocmd CursorHold * set nohlsearch augroup end " 剪贴板设置 " wsl用户,在vim中复制后同步到系统剪贴板 " 从系统剪贴板复制到vim中用终端的Ctrl-Shift-V if has('wsl') let s:clip = '/mnt/c/Windows/System32/clip.exe' if executable(s:clip) augroup WSLYank autocmd! autocmd TextYankPost * if v:event.operator ==# 'y' | call system(s:clip, @0) | endif augroup end endif else set clipboard=unnamedplus endif
gui font
这里主要是针对gvim字体设置
if has('gui_running') " 去掉gvim的标题栏 set guioptions-=m set guioptions-=T " 设置字体,注意不同系统的分隔符还不一样 if has('win32') || has('mac') || has('nvim') set guifont=JetBrainsMono\ Nerd\ Font:10,Consolas:10 else set guifont=JetBrainsMono\ Nerd\ Font\ 10,Consolas\ 10,DejaVu\ Sans\ Mono\ 10 endif endif
插件管理器 Lazy.nvim
Lazy.nvim是一个非常方便,模块化管理的插件管理器,现在主流的neovim配置都是用的这个。
自己配置neovim,第一个设置的肯定是插件管理器。还是强调一下无论什么插件,基本用法在官方文档里都是有的。
Lazy.nvim 文档
lazy.nvim有一段自动安装的脚本,这个你放在配置的开头,然后在启动lazy.nvim,我自己呢是做了一个简单的包装
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.loop.fs_stat(lazypath) then vim.fn.system( "git clone --filter=blob:none --branch=stable https://github.com/folke/lazy.nvim.git " .. lazypath ) end vim.opt.rtp:prepend(lazypath)
设置lazy.nvim
require("helper.lazy").setup({ spec = { -- 经过这种import 方式导入插件的lua 文件必须返回一个table { import = "plugins.ui" }, -- 对应~/.config/nvim/lua/plugins/ui 目录 { import = "plugins.edit" },-- 对应~/.config/nvim/lua/plugins/edit目录 { import = "plugins.lsp" }, { import = "plugins.dap" }, { import = "plugins.tools" }, { import = "plugins.lang" }, { import = "autoload" }, -- 对应~/.config/nvim/lua/autoload 目录 }, })
然后把插件文件放进这些目录里就行,lazy.nvim会自动加载,不过每个插件文件需要满足一定的格式,每个文件必须返回一个table,例如
return { "folke/lazy.nvim", version = "*" }
一个文件也可以包含多个插件
return { { "folke/lazy.nvim", version = "*" }, { "williamboman/mason.nvim", version = "*", config = true }, ... }
常用的字段:
- dependencies 依赖插件,会先加载
- version 选择的插件版本(tag)常用version="*",即只使用打tag的release版本,相对稳定些
- commit 插件的某个版本commit id,用于某些更新频繁,不太稳定的插件
- event 触发event之后加载
- keys 快捷键映射,同时也作为插件加载的依据
- cmd 需要执行命令时加载
- opts 插件的opts,会自动传给require('...').setup(opts)
- config 与opts类似
如果同时使用config字段和opts字段,opts字段会不生效,需要改成:
opts = { ... }, config = function(_, opts) require('plugin').setup(opts) -- 上面的opts也可以直接放到这里 -- other setttings end,
设置event, keys, cmd之后默认是懒加载模式,如果需要在启动的时候加载,可以指定lazy=false
模糊搜索 fzf-lua/Telescope/Leaderf/vim-clap...
插件管理器之后,我认为最重要的插件应该是模糊搜索类插件,这类插件在我心中的地位,甚至超过了输入补全和lsp
这类插件可以非常方便的进行文件搜索和跳转,全项目范围的字符模糊搜索,同时一般也集成了内置比如查看按键映射,高亮,最近打开的文件等等功能,还有集成了lsp/dap等扩展功能,非常强大,非常方便
文件搜索 | mru |
---|---|
![]() |
![]() |
字符串搜索 | keymap查询 |
![]() |
![]() |
我把这类插件放在第二个来介绍,还不仅仅是因为上面这些强大的功能,主要是因为可以非常方便的搜索文档。
不知道某个插件怎么用?搜一下文档
不知道某个内置函数怎么用?搜一下文档
不知道neovim有什么内置函数可以用?搜一下文档
有这个功能之后,可以节省你很多的时间。
vim/neovim生态下的模糊搜索插件主要有以下几个:
- telescope.nvim 纯neovim,使用人数最多,插件比较多
- fzf-lua,和telescope差不多,但我感觉好用一点
- LeaderF,同时支持vim/neovim,优势在于支持gtags,缺点是不支持lsp,gtags可以作为lsp以外的补充。(依赖python,模糊搜索算法是作者自己设计的,不输fzf)
- vim-clap,和leaderf类似,同样是中国人开发,支持vim/neovim,这个插件我了解不多
这些插件在功能性上应该都差不多,选一个自己用的顺手的就行。我现在用的是fzf-lua
return { "ibhagwan/fzf-lua", dependencies = { "nvim-tree/nvim-web-devicons", }, event = { "VeryLazy" }, enabled = vim.pathlib.executable("fzf"), opts = { "default", winopts = { preview = { border = "noborder", vertical = "up:50%", horizontal = "right:50%", delay = 50, }, }, files = { path_shorten = 3, }, diagnostics = { split = "belowright new", }, previewers = { bat = { cmd = vim.pathlib.executable("batcat") and "batcat" or "bat", }, }, }, keys = { { "<leader>f/", "<cmd>FzfLua <CR>", desc = "FzfLua self" }, { "<leader>ff", "<cmd>FzfLua files<CR>", desc = "files" }, { "<leader>fb", "<cmd>FzfLua buffers<CR>", desc = "buffers" }, { "<leader>fl", "<cmd>FzfLua live_grep<CR>", desc = "live grep" }, { "<leader>fh", "<cmd>FzfLua help_tags<CR>", desc = "help" }, { "<leader>fH", "<cmd>FzfLua highlights<CR>", desc = "highlights" }, { "<leader>fm", "<cmd>FzfLua oldfiles<CR>", desc = "mru" }, -- mru: most recent used { "<leader>fc", "<cmd>FzfLua commands<CR>", desc = "commands" }, { "<leader>fj", "<cmd>FzfLua jumps<CR>", desc = "jumplist" }, { "<leader>fk", "<cmd>FzfLua keymaps<CR>", desc = "keymaps" }, { "<leader>fq", "<cmd>FzfLua quickfix<CR>", desc = "quickfix" }, { "<leader>fw", "<cmd>FzfLua grep_cword<CR>", desc = "cword" }, { "<leader>fa", "<cmd>lua require('helper.asynctask').fzf_select()<CR>", desc = "asynctask" }, { "<leader>d", "<cmd>FzfLua lsp_document_diagnostics<CR>", desc = "lsp_document_diagnostics" }, { "<leader>fd", "<cmd>FzfLua lsp_definitions<CR>", desc = "lsp_definition" }, { "<leader>fr", "<cmd>FzfLua lsp_references<CR>", desc = "lsp_references" }, { "<leader>fi", "<cmd>FzfLua lsp_implementations<CR>", desc = "lsp_implementations" }, { "<leader>fs", "<cmd>FzfLua lsp_document_symbols<CR>", desc = "lsp_document_symbols" }, { "<leader>fS", "<cmd>FzfLua lsp_workspace_symbols<CR>", desc = "lsp_workspace_symbols" }, { "<C-f>", "<cmd>FzfLua grep_curbuf<CR>", desc = "lines" }, }, }
在进行各个插件的按键映射的时候最好遵循一套自己的逻辑,在模糊搜索功能下主要以<leader>f
作为前缀(我的leader键是空格),然后有些用的非常频繁的可以作为个例,比如我这里的<C-f>
,算是使用windows留下的习惯吧。
输入补全 cmp
cmp是neovim中的一个补全框架,说时候配置比较繁琐,如果追求省事儿,可以选用coc.nvim,老牌插件了,也是国人开发的,开箱即用
cmp比较轻量级,但是文档冗长,经常出现意义不明的选项=_=
cmp插件本身只是一个框架,需要source来配合,例如:
return { "hrsh7th/nvim-cmp", dependencies = { -- cmp-nvim-lsp in lsp/init.lua -- cmp-luasnip in edit/luasnip.lua "hrsh7th/cmp-cmdline", "hrsh7th/cmp-buffer", "hrsh7th/cmp-path", }, config = function() -- end, }
配置及说明
--我在cmp配置文件开头有一个CMP_SOURCES 的表 CMP_SOURCES = { luasnip = { name = "luasnip", menu = "[Snip]" }, nvim_lsp = { name = "nvim_lsp", menu = "[LSP]", entry_filter = function(entry) return require("cmp").lsp.CompletionItemKind.Snippet ~= entry:get_kind() end, }, path = { name = "path", menu = "[Path]" }, buffer = { name = "buffer", menu = "[Buf]" }, cmdline = { name = "cmdline", menu = "[Cmd]" }, } -- cmp的配置 config = function() local cmp = require("cmp") local compare = require("cmp.config.compare") -- 这里设置的是插入模式下的补全source cmp.setup({ -- 补全菜单和文档框,是否要有边框,以及高亮组等 window = { completion = cmp.config.window.bordered({ border = "none", winhighlight = "Normal:Normal,FloatBorder:Normal,CursorLine:MyCmpSel", -- 重点是CursorLine }), documentation = cmp.config.window.bordered({ border = "none", }), }, -- 补全菜单的格式,见下面的效果图 formatting = { fields = { "abbr", "kind", "menu" }, expandable_indicator = false, format = function(entry, vim_item) vim_item.menu = CMP_SOURCES[entry.source.name].menu return vim_item end, }, -- global setting and can be overwritten in sources experimental = { ghost_text = false }, sources = { CMP_SOURCES.nvim_lsp, CMP_SOURCES.luasnip, CMP_SOURCES.buffer, CMP_SOURCES.path, }, -- 补全项的排序算法权重,我也比较迷惑 sorting = { comparators = { compare.score, compare.kind, compare.exact, compare.length, compare.offset, compare.sort_text, }, }, -- snippet settting in luasnip mapping = cmp.mapping.preset.insert({ -- expand snippet setting in luasnip ["<C-u>"] = cmp.mapping.scroll_docs(-4), ["<C-d>"] = cmp.mapping.scroll_docs(4), -- ["<C-n>"] = require("cmp_rime").mapping.select_next_item, -- ["<C-p>"] = require("cmp_rime").mapping.select_prev_item, -- toggle cmp 菜单 ["<C-Space>"] = cmp.mapping(function(fallback) if cmp.visible() then cmp.abort() else cmp.complete() end end), -- 回车选中 ["<CR>"] = cmp.mapping.confirm({ select = true, behavior = cmp.ConfirmBehavior.Replace }), }), }) -- 搜索模式 for _, cmd_type in ipairs({ "/", "?" }) do cmp.setup.cmdline(cmd_type, { mapping = cmp.mapping.preset.cmdline(), sources = { CMP_SOURCES.buffer, }, }) end -- 命令行模式 cmp.setup.cmdline(":", { mapping = cmp.mapping.preset.cmdline(), sources = { -- 有path 的时候屏蔽cmd vim.tbl_extend("force", CMP_SOURCES.path, { group_index = 1 }), vim.tbl_extend("force", CMP_SOURCES.cmdline, { group_index = 2 }), }, }) end,
效果:
![]() |
![]() |
![]() |
![]() |
LSP
在这里不解释LSP是什么,只介绍怎么配置。
如果使用的是coc,基本开箱即用,易用性也挺好的,如果是用的neovim官方的lsp,那还需要少量的配置。
用到的插件有
neovim/nvim-lspconfig
:neovim官方lsp插件hrsh7th/cmp-nvim-lsp
:用于cmp补全的sourcewilliamboman/mason.nvim
和williamboman/mason-lspconfig.nvim
:管理下载的插件folke/neodev.nvim
:这个可能nvimdev/lspsaga.nvim
:基于lsp协议实现查找定义,引用等的插件- 其他lsp应用类的插件
管理lsp的部分基本照抄就行了
-- ~/.config/nvim/lua/plugins/lsp/init.lua local on_attach = function(client, bufnr) vim.api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc") -- 禁用lsp 语义高亮 -- client.server_capabilities.semanticTokensProvider = nil end local capabilities = vim.lsp.protocol.make_client_capabilities() capabilities.textDocument.completion.completionItem = { documentationFormat = { "markdown", "plaintext" }, -- snippet禁用无效,要在cmp里设置 snippetSupport = false, } return { "neovim/nvim-lspconfig", dependencies = { { "folke/neodev.nvim", version = "*", config = true, lazy = true }, "hrsh7th/cmp-nvim-lsp", { "williamboman/mason-lspconfig.nvim", version = "*", config = true }, }, event = { "BufReadPre", "BufNewFile" }, config = function() require("mason-lspconfig").setup_handlers({ function(server_name) local configs = { on_attach = on_attach, capabilities = capabilities, root_dir = require("lspconfig").util.root_pattern(vim.g.ROOT_MARKERS), } if vim.pathlib.file_exist(("~/.config/nvim/lua/plugins/lsp/settings/%s.lua"):format(server_name)) then local spec_configs = require(("plugins.lsp.settings.%s"):format(server_name)) configs = vim.tbl_deep_extend("force", configs, spec_configs) end require("lspconfig")[server_name].setup(configs) end, }) end, }
mason.nvim
是一个用于下载neovim下第三方工具的一个插件,可以用于下载安装lsp、dap、语法检查工具linter、格式化工具等等,相比于手动apt下载,管理更加方便。与其配套的有
mason-lspconfig.nvim
mason-nvim-dap.nvim
mason-null-ls.nvim
我都用到了,除了方便安装,还可以通过配置做到即装即用,不用再手动的vim.lsp.start
上面这段配置做到了全局和局部的结合,用mason-lspconfig.nvim
启动会带上默认的配置,如果需要修改,将配置写到~/.config/nvim/lua/plugins/lsp/settings/
这个目录下,从这里读取的配置会覆盖全局设置和lsp默认设置,比如clangd:
-- ~/.config/nvim/lua/plugins/lsp/settings/clangd.lua return { capabilities = { offsetEncoding = { "utf-16" }, -- 解决全局的utf8编码问题 }, cmd = { "clangd", -- 同时开启的任务数量 "-j 8", -- 开启clang-tidy "--clang-tidy", -- xxxxxxxxx其他设置 }, -- 有些lsp可以用过这个字段来进行设置 settings = { } }
大部分lsp的默认设置已经可以用了,只需要少量的需要进行调整
nvimdev
这个插件是用于写lua配置和插件用的,主要功能是设置了lua_ls
这个lsp和runtimepath,使得写lua配置的时候可以对neovim配置的lua的api和lua插件进行补全和提示,需要在lua_ls
启动之前加载,所以这个插件作为lspconfig的依赖了
lspsaga.nvim
是lsp应用类的插件,基于lsp协议实现的定义/引用查找,查看文档等功能
模糊查找的插件比如telescope
和fzf-lua
也都内置了这些功能
查看error/warning信息的插件trouble.nvim
,用的人也比较多。
代码运行、调试、测试
代码运行 asynctask.vim
代码运行类的插件只推荐asynctask.vim这个插件,非常的优雅。
调试 dap
这个比较坑
测试 neotest
编辑类插件
注释 Comment.nvim
注释类插件我用的是Comment.nvim
,我习惯用<leader>c
/<leader>C
前缀。
return { "numToStr/Comment.nvim", event = { "BufRead" }, version = "*", opts = { padding = true, sticky = true, ignore = "^$", toggler = { line = "<leader>cc", block = "<leader>CC", }, opleader = { line = "<leader>c", block = "<leader>C", }, extra = { above = "<leader>cO", below = "<leader>co", eol = "<leader>cA", }, mappings = { basic = true, extra = true, }, }, }
nvim-autopair
nvim-surround
格式化 null-ls
代码片段 luasnip
其他
工具类插件
大纲解析 aerial
目录树 nvim-tree
悬浮终端 toggleterm
gitsigns
diffview.nvim
flash.nvim
mini.nvim
美化类插件
主题
dashboard
noice
lualine
dressing
indent-blankline
其他
luasnip
vimtex
弃用的插件
作者:lavateinn
出处:https://www.cnblogs.com/lavateinn/p/17904263.html
本文版权归作者和博客园共有,商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」