├── ftplugin ├── css.lua ├── go.lua ├── html.lua ├── toml.lua ├── javascript.lua ├── typescript.lua ├── javascriptreact.lua ├── typescriptreact.lua ├── bash.lua ├── sh.lua ├── java.lua ├── python.lua ├── zig.lua ├── c.lua ├── lua.lua ├── json.lua ├── xml.lua └── rust.lua ├── .gitignore ├── lua ├── experimental.lua ├── global.lua ├── kide │ ├── lsp │ │ ├── quarkus.lua │ │ ├── microprofile.lua │ │ ├── zls.lua │ │ ├── taplo.lua │ │ ├── jsonls.lua │ │ ├── cssls.lua │ │ ├── yamlls.lua │ │ ├── html.lua │ │ ├── clangd.lua │ │ ├── gopls.lua │ │ ├── lua-ls.lua │ │ ├── ts-ls.lua │ │ ├── lemminx.lua │ │ ├── rust-analyzer.lua │ │ ├── volar.lua │ │ ├── sonarlint.lua │ │ ├── pyright.lua │ │ ├── spring-boot.lua │ │ ├── rustowl.lua │ │ └── jdtls.lua │ ├── gpt │ │ ├── toole.lua │ │ ├── provide │ │ │ ├── init.lua │ │ │ ├── openrouter.lua │ │ │ └── deepseek.lua │ │ ├── commit.lua │ │ ├── code.lua │ │ ├── translate.lua │ │ └── chat.lua │ ├── icons.lua │ ├── tools │ │ ├── vscode.lua │ │ ├── curl.lua │ │ ├── mermaid.lua │ │ ├── pandoc.lua │ │ ├── plantuml.lua │ │ ├── maven.lua │ │ └── init.lua │ ├── lspui.lua │ ├── init.lua │ ├── lspkind.lua │ ├── yazi.lua │ ├── http │ │ └── sse.lua │ ├── codex.lua │ ├── melspconfig.lua │ ├── term.lua │ └── stl.lua ├── autocmds.lua ├── options.lua ├── plugins.lua └── mappings.lua ├── .stylua.toml ├── after └── ftplugin │ ├── jproperties.lua │ ├── yaml.lua │ └── java.lua ├── syntax └── qf.vim ├── README.md ├── init.lua ├── lsp └── copilot.lua ├── colors └── gruvboxl.lua └── LICENSE-APACHE /ftplugin/css.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.cssls").config) 2 | -------------------------------------------------------------------------------- /ftplugin/go.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.gopls").config) 2 | -------------------------------------------------------------------------------- /ftplugin/html.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.html").config) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .netrwhist 2 | .luarc.json 3 | plugin 4 | lazy-lock.json 5 | -------------------------------------------------------------------------------- /ftplugin/toml.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.taplo").config) 2 | -------------------------------------------------------------------------------- /ftplugin/javascript.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.ts-ls").config) 2 | -------------------------------------------------------------------------------- /ftplugin/typescript.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.ts-ls").config) 2 | -------------------------------------------------------------------------------- /ftplugin/javascriptreact.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.ts-ls").config) 2 | -------------------------------------------------------------------------------- /ftplugin/typescriptreact.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.start(require("kide.lsp.ts-ls").config) 2 | -------------------------------------------------------------------------------- /ftplugin/bash.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 4 2 | vim.bo.tabstop = 4 3 | vim.bo.softtabstop = 4 4 | -------------------------------------------------------------------------------- /ftplugin/sh.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 4 2 | vim.bo.tabstop = 4 3 | vim.bo.softtabstop = 4 4 | -------------------------------------------------------------------------------- /ftplugin/java.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 4 2 | vim.bo.tabstop = 4 3 | vim.bo.softtabstop = 4 4 | 5 | -------------------------------------------------------------------------------- /lua/experimental.lua: -------------------------------------------------------------------------------- 1 | local ok, tui = pcall(require, "vim._extui") 2 | if ok then 3 | tui.enable({}) 4 | end 5 | -------------------------------------------------------------------------------- /ftplugin/python.lua: -------------------------------------------------------------------------------- 1 | require("kide.lsp.pyright").init_dap() 2 | vim.lsp.start(require("kide.lsp.pyright").config) 3 | -------------------------------------------------------------------------------- /ftplugin/zig.lua: -------------------------------------------------------------------------------- 1 | if vim.fn.executable("zls") == 1 then 2 | vim.lsp.start(require("kide.lsp.zls").config) 3 | end 4 | -------------------------------------------------------------------------------- /ftplugin/c.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 4 2 | vim.bo.tabstop = 4 3 | vim.bo.softtabstop = 4 4 | vim.lsp.start(require("kide.lsp.clangd").config) 5 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferDouble" 6 | -------------------------------------------------------------------------------- /ftplugin/lua.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 2 2 | vim.bo.tabstop = 2 3 | vim.bo.softtabstop = 2 4 | vim.lsp.start(require("kide.lsp.lua-ls").config) 5 | -------------------------------------------------------------------------------- /ftplugin/json.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 2 2 | vim.bo.tabstop = 2 3 | vim.bo.softtabstop = 2 4 | 5 | vim.lsp.start(require("kide.lsp.jsonls").config) 6 | -------------------------------------------------------------------------------- /ftplugin/xml.lua: -------------------------------------------------------------------------------- 1 | local lemminx_home = vim.env["LEMMINX_HOME"] 2 | 3 | if lemminx_home then 4 | vim.lsp.start(require("kide.lsp.lemminx").config) 5 | end 6 | -------------------------------------------------------------------------------- /lua/global.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | vim.g.enable_spring_boot = vim.env["NVIM_SPRING_BOOT"] == "Y" 4 | vim.g.enable_quarkus = vim.env["NVIM_QUARKUS"] == "Y" 5 | 6 | return M 7 | -------------------------------------------------------------------------------- /ftplugin/rust.lua: -------------------------------------------------------------------------------- 1 | vim.bo.shiftwidth = 4 2 | vim.bo.tabstop = 4 3 | vim.bo.softtabstop = 4 4 | vim.lsp.start(require("kide.lsp.rust-analyzer").config) 5 | 6 | if vim.fn.executable("cargo-owlsp") == 1 then 7 | vim.lsp.start(require("kide.lsp.rustowl").config) 8 | end 9 | -------------------------------------------------------------------------------- /lua/kide/lsp/quarkus.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = require("quarkus.launch").lsp_config({ 5 | root_dir = vim.fs.root(0, { ".git" }), 6 | on_attach = me.on_attach, 7 | on_init = me.on_init, 8 | capabilities = me.capabilities(), 9 | }) 10 | return M 11 | -------------------------------------------------------------------------------- /lua/kide/lsp/microprofile.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = require("microprofile.launch").lsp_config({ 5 | root_dir = vim.fs.root(0, { ".git" }), 6 | on_attach = me.on_attach, 7 | on_init = me.on_init, 8 | capabilities = me.capabilities(), 9 | }) 10 | return M 11 | -------------------------------------------------------------------------------- /lua/kide/lsp/zls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "zls", 6 | cmd = { "zls" }, 7 | filetypes = { "zig" }, 8 | root_dir = vim.fs.root(0, { "build.zig" }), 9 | single_file_support = true, 10 | on_attach = me.on_attach, 11 | on_init = me.on_init, 12 | capabilities = me.capabilities(), 13 | settings = {}, 14 | } 15 | 16 | return M 17 | -------------------------------------------------------------------------------- /lua/kide/lsp/taplo.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "taplo", 6 | cmd = { "taplo", "lsp", "stdio" }, 7 | filetypes = { "toml" }, 8 | root_dir = vim.fs.root(0, { ".git" }), 9 | single_file_support = true, 10 | on_attach = me.on_attach, 11 | on_init = me.on_init, 12 | capabilities = me.capabilities(), 13 | settings = {}, 14 | } 15 | 16 | return M 17 | -------------------------------------------------------------------------------- /after/ftplugin/jproperties.lua: -------------------------------------------------------------------------------- 1 | if vim.g.enable_spring_boot == true then 2 | local c = require("kide.lsp.spring-boot").config 3 | if c and require("spring_boot.util").is_application_properties_buf(0) then 4 | vim.lsp.start(c) 5 | end 6 | end 7 | 8 | if vim.g.enable_quarkus == true then 9 | local qc = require("kide.lsp.quarkus").config 10 | vim.lsp.start(qc) 11 | local mc = require("kide.lsp.microprofile").config 12 | vim.lsp.start(mc) 13 | end 14 | -------------------------------------------------------------------------------- /lua/kide/gpt/toole.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | ---@param usage gpt.TokenUsage 3 | ---@return string 4 | function M.usage_str(title, usage) 5 | local data = "[token usage: " 6 | .. vim.inspect(usage.prompt_cache_hit_tokens or 0) 7 | .. " " 8 | .. vim.inspect(usage.prompt_tokens) 9 | .. " + " 10 | .. vim.inspect(usage.completion_tokens) 11 | .. " = " 12 | .. vim.inspect(usage.total_tokens) 13 | .. " ] " .. title 14 | return data 15 | end 16 | return M 17 | -------------------------------------------------------------------------------- /lua/kide/lsp/jsonls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "jsonls", 6 | cmd = { "vscode-json-language-server", "--stdio" }, 7 | filetypes = { "json", "jsonc" }, 8 | init_options = { 9 | provideFormatter = true, 10 | }, 11 | root_dir = vim.fs.root(0, { ".git" }), 12 | single_file_support = true, 13 | on_attach = me.on_attach, 14 | on_init = me.on_init, 15 | capabilities = me.capabilities(), 16 | settings = {}, 17 | } 18 | return M 19 | -------------------------------------------------------------------------------- /lua/kide/lsp/cssls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "cssls", 6 | cmd = { "vscode-css-language-server", "--stdio" }, 7 | filetypes = { "css", "scss", "less" }, 8 | init_options = { provideFormatter = true }, 9 | root_dir = vim.fs.root(0, { "package.json" }), 10 | single_file_support = true, 11 | settings = { 12 | css = { validate = true }, 13 | scss = { validate = true }, 14 | less = { validate = true }, 15 | }, 16 | on_attach = me.on_attach, 17 | on_init = me.on_init, 18 | capabilities = me.capabilities(), 19 | } 20 | 21 | return M 22 | -------------------------------------------------------------------------------- /lua/kide/lsp/yamlls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "yamlls", 6 | cmd = { "yaml-language-server", "--stdio" }, 7 | filetypes = { "yaml", "yml" }, 8 | root_dir = vim.fs.root(0, { ".git" }), 9 | single_file_support = true, 10 | on_attach = me.on_attach, 11 | on_init = me.on_init, 12 | capabilities = me.capabilities(), 13 | settings = { 14 | redhat = { 15 | telemetry = { 16 | enabled = false, 17 | }, 18 | }, 19 | yaml = { 20 | validate = true, 21 | hover = true, 22 | completion = true, 23 | }, 24 | }, 25 | } 26 | return M 27 | -------------------------------------------------------------------------------- /after/ftplugin/yaml.lua: -------------------------------------------------------------------------------- 1 | local yc = require("kide.lsp.yamlls").config 2 | if yc then 3 | vim.lsp.start(yc) 4 | end 5 | 6 | if vim.g.enable_spring_boot == true then 7 | local c = require("kide.lsp.spring-boot").config 8 | if c and require("spring_boot.util").is_application_yml_buf(0) then 9 | vim.lsp.start(c) 10 | end 11 | end 12 | 13 | if vim.g.enable_quarkus == true then 14 | local qc = require("kide.lsp.quarkus").config 15 | vim.lsp.start(qc) 16 | local mc = require("kide.lsp.microprofile").config 17 | vim.lsp.start(mc) 18 | local buf = vim.api.nvim_get_current_buf() 19 | require("microprofile.yaml").registerYamlSchema(buf) 20 | end 21 | -------------------------------------------------------------------------------- /lua/kide/lsp/html.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "html", 6 | cmd = { "vscode-html-language-server", "--stdio" }, 7 | filetypes = { "html", "templ" }, 8 | root_dir = vim.fs.root(0, { "package.json", ".git" }) or vim.uv.cwd(), 9 | single_file_support = true, 10 | settings = {}, 11 | init_options = { 12 | provideFormatter = true, 13 | embeddedLanguages = { css = true, javascript = true }, 14 | configurationSection = { "html", "css", "javascript" }, 15 | }, 16 | on_attach = me.on_attach, 17 | on_init = me.on_init, 18 | capabilities = me.capabilities(), 19 | } 20 | 21 | return M 22 | -------------------------------------------------------------------------------- /lua/kide/lsp/clangd.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "clangd", 6 | cmd = { "clangd" }, 7 | filetypes = { "c", "cpp", "objc", "objcpp", "cuda", "proto" }, 8 | root_dir = vim.fs.root(0, { 9 | ".git", 10 | ".clangd", 11 | ".clang-tidy", 12 | ".clang-format", 13 | "compile_commands.json", 14 | "compile_flags.txt", 15 | "configure.ac", -- AutoTools 16 | }) or vim.uv.cwd(), 17 | single_file_support = true, 18 | capabilities = me.capabilities({ 19 | textDocument = { 20 | completion = { 21 | editsNearCursor = true, 22 | }, 23 | }, 24 | offsetEncoding = { "utf-8", "utf-16" }, 25 | }), 26 | on_attach = function(client, bufnr) 27 | me.on_attach(client, bufnr) 28 | end, 29 | on_init = me.on_init, 30 | } 31 | 32 | return M 33 | -------------------------------------------------------------------------------- /syntax/qf.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | syn match qfFileName /^[^│]*/ nextgroup=qfSeparatorLeft 6 | syn match qfSeparatorLeft /│/ contained nextgroup=qfLineNr 7 | syn match qfLineNr /[^│]*/ contained nextgroup=qfSeparatorRight 8 | syn match qfSeparatorRight '│' contained nextgroup=qfError,qfWarning,qfInfo,qfNote 9 | syn match qfError / E .*$/ contained 10 | syn match qfWarning / W .*$/ contained 11 | syn match qfInfo / I .*$/ contained 12 | syn match qfNote / [NH] .*$/ contained 13 | 14 | hi def link qfFileName Directory 15 | hi def link qfSeparatorLeft Delimiter 16 | hi def link qfSeparatorRight Delimiter 17 | hi def link qfLineNr LineNr 18 | hi def link qfError DiagnosticError 19 | hi def link qfWarning DiagnosticWarn 20 | hi def link qfInfo DiagnosticInfo 21 | hi def link qfNote DiagnosticHint 22 | 23 | let b:current_syntax = 'qf' 24 | -------------------------------------------------------------------------------- /lua/kide/lsp/gopls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "gopls", 6 | cmd = { "gopls" }, 7 | filetypes = { "go", "gomod", "gowork", "gotmpl" }, 8 | root_dir = vim.fs.root(0, { "go.work", "go.mod", ".git" }), 9 | single_file_support = true, 10 | settings = { 11 | gopls = { 12 | analyses = { 13 | -- https://staticcheck.dev/docs/checks/#SA5008 14 | -- Invalid struct tag 15 | SA5008 = true, 16 | -- Incorrect or missing package comment 17 | ST1000 = true, 18 | -- Incorrectly formatted error string 19 | ST1005 = true, 20 | }, 21 | staticcheck = true, -- 启用 staticcheck 检查 22 | }, 23 | }, 24 | init_options = vim.empty_dict(), 25 | handlers = {}, 26 | on_attach = me.on_attach, 27 | on_init = me.on_init, 28 | capabilities = me.capabilities(), 29 | } 30 | 31 | return M 32 | -------------------------------------------------------------------------------- /lua/kide/icons.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Namespace = "󰌗", 3 | Text = "󰉿", 4 | Method = "󰆧", 5 | Function = "󰆧", 6 | Constructor = "", 7 | Field = "󰜢", 8 | Variable = "󰀫", 9 | Class = "󰠱", 10 | Interface = "", 11 | Module = "", 12 | Property = "󰜢", 13 | Unit = "󰑭", 14 | Value = "󰎠", 15 | Enum = "", 16 | Keyword = "󰌋", 17 | Snippet = "", 18 | Color = "󰏘", 19 | File = "󰈚", 20 | Reference = "󰈇", 21 | Folder = "󰉋", 22 | EnumMember = "", 23 | Constant = "󰏿", 24 | Struct = "󰙅", 25 | Event = "", 26 | Operator = "󰆕", 27 | TypeParameter = "󰊄", 28 | Table = "", 29 | Object = "󰅩", 30 | Tag = "", 31 | Array = "", 32 | Boolean = "", 33 | Number = "", 34 | Null = "󰟢", 35 | Supermaven = "", 36 | String = "󰉿", 37 | Calendar = "", 38 | Watch = "󰥔", 39 | Package = "", 40 | Copilot = "", 41 | Codeium = "", 42 | TabNine = "", 43 | BladeNav = "", 44 | } 45 | -------------------------------------------------------------------------------- /lua/kide/lsp/lua-ls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | M.config = { 5 | name = "lua_ls", 6 | cmd = { "lua-language-server" }, 7 | filetypes = { "lua" }, 8 | root_dir = vim.fs.root(0, { ".stylua.toml", ".git" }) or vim.uv.cwd(), 9 | on_attach = me.on_attach, 10 | capabilities = me.capabilities(), 11 | on_init = me.on_init, 12 | settings = { 13 | Lua = { 14 | diagnostics = { 15 | globals = { "vim" }, 16 | }, 17 | workspace = { 18 | library = { 19 | vim.fn.expand("$VIMRUNTIME/lua"), 20 | vim.fn.expand("$VIMRUNTIME/lua/vim/lsp"), 21 | vim.fn.stdpath("data") .. "/lazy/lazy.nvim/lua/lazy", 22 | "${3rd}/luv/library", 23 | }, 24 | maxPreload = 100000, 25 | preloadFileSize = 10000, 26 | }, 27 | }, 28 | }, 29 | single_file_support = true, 30 | log_level = vim.lsp.protocol.MessageType.Warning, 31 | } 32 | return M 33 | -------------------------------------------------------------------------------- /lua/kide/lsp/ts-ls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local me = require("kide.melspconfig") 3 | 4 | M.config = { 5 | name = "ts_ls", 6 | cmd = { "typescript-language-server", "--stdio" }, 7 | on_attach = me.on_attach, 8 | on_init = me.on_init, 9 | root_dir = vim.fs.root(0, { "tsconfig.json", "jsconfig.json", "package.json", ".git" }), 10 | capabilities = me.capabilities(), 11 | init_options = { 12 | plugins = { 13 | { 14 | name = "@vue/typescript-plugin", 15 | location = vim.fs.joinpath(me.global_node_modules(), "@vue", "typescript-plugin"), 16 | languages = { "javascript", "typescript", "vue" }, 17 | }, 18 | }, 19 | }, 20 | filetypes = { 21 | "javascript", 22 | "javascriptreact", 23 | "javascript.jsx", 24 | "typescript", 25 | "typescriptreact", 26 | "typescript.tsx", 27 | "vue", 28 | }, 29 | settings = { 30 | ts_ls = {}, 31 | }, 32 | single_file_support = true, 33 | } 34 | return M 35 | -------------------------------------------------------------------------------- /lua/kide/lsp/lemminx.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local lemminx_home = vim.env["LEMMINX_HOME"] 3 | 4 | if lemminx_home then 5 | local utils = require("kide.tools") 6 | local me = require("kide.melspconfig") 7 | local lemminx_jars = {} 8 | for _, bundle in ipairs(vim.split(vim.fn.glob(lemminx_home .. "/*.jar"), "\n")) do 9 | table.insert(lemminx_jars, bundle) 10 | end 11 | vim.fn.join(lemminx_jars, utils.is_win and ";" or ":") 12 | M.config = { 13 | name = "lemminx", 14 | cmd = { 15 | utils.java_bin(), 16 | "-cp", 17 | vim.fn.join(lemminx_jars, ":"), 18 | "org.eclipse.lemminx.XMLServerLauncher", 19 | }, 20 | settings = { 21 | lemminx = {}, 22 | }, 23 | filetypes = { "xml", "xsd", "xsl", "xslt", "svg" }, 24 | root_dir = vim.fs.root(0, { ".git" }) or vim.uv.cwd(), 25 | single_file_support = true, 26 | on_attach = me.on_attach, 27 | on_init = me.on_init, 28 | capabilities = me.capabilities(), 29 | } 30 | end 31 | 32 | return M 33 | -------------------------------------------------------------------------------- /lua/kide/lsp/rust-analyzer.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local me = require("kide.melspconfig") 4 | local function reload_workspace(bufnr) 5 | local clients = vim.lsp.get_clients({ bufnr = bufnr, name = "rust-analyzer" }) 6 | for _, client in ipairs(clients) do 7 | vim.notify("Reloading Cargo Workspace") 8 | client:request("rust-analyzer/reloadWorkspace", nil, function(err) 9 | if err then 10 | error(tostring(err)) 11 | end 12 | vim.notify("Cargo workspace reloaded") 13 | end, 0) 14 | end 15 | end 16 | 17 | M.config = { 18 | name = "rust-analyzer", 19 | cmd = { "rust-analyzer" }, 20 | filetypes = { "rust" }, 21 | single_file_support = true, 22 | init_options = { 23 | provideFormatter = true, 24 | }, 25 | root_dir = vim.fs.root(0, { ".git", "Cargo.toml" }), 26 | on_attach = me.on_attach, 27 | on_init = me.on_init, 28 | capabilities = me.capabilities({ 29 | experimental = { 30 | serverStatusNotification = true, 31 | }, 32 | }), 33 | } 34 | return M 35 | -------------------------------------------------------------------------------- /lua/kide/tools/vscode.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local env = { 3 | VSCODE_EXTENSIONS = vim.env["VSCODE_EXTENSIONS"], 4 | LOMBOK_JAR = vim.env["LOMBOK_JAR"], 5 | } 6 | M.get_vscode_extensions = function() 7 | return env.VSCODE_EXTENSIONS or "~/.vscode/extensions" 8 | end 9 | M.find_one = function(extension_path) 10 | local v = vim.fn.glob(M.get_vscode_extensions() .. extension_path) 11 | if v and v ~= "" then 12 | if type(v) == "string" then 13 | local pt = vim.split(v, "\n") 14 | return pt[#pt] 15 | elseif type(v) == "table" then 16 | return v[1] 17 | end 18 | return v 19 | end 20 | end 21 | 22 | local mason, _ = pcall(require, "mason-registry") 23 | M.get_lombok_jar = function() 24 | local lombok_jar = env.LOMBOK_JAR 25 | if lombok_jar == nil then 26 | lombok_jar = M.find_one("/redhat.java-*/lombok/lombok-*.jar") 27 | if lombok_jar == nil and mason and require("mason-registry").has_package("jdtls") then 28 | lombok_jar = require("mason-registry").get_package("jdtls"):get_install_path() .. "/lombok.jar" 29 | end 30 | end 31 | return lombok_jar 32 | end 33 | 34 | return M 35 | -------------------------------------------------------------------------------- /lua/kide/tools/curl.lua: -------------------------------------------------------------------------------- 1 | local outfmt = "\n┌─────────────────────────\n" 2 | .. "│ dnslookup : %{time_namelookup}\n" 3 | .. "│ connect : %{time_connect}\n" 4 | .. "│ appconnect : %{time_appconnect}\n" 5 | .. "│ pretransfer : %{time_pretransfer}\n" 6 | .. "│ starttransfer : %{time_starttransfer}\n" 7 | .. "│ total : %{time_total}\n" 8 | .. "│ size : %{size_download}\n" 9 | .. "│ HTTPCode=%{http_code}\n\n" 10 | local M = {} 11 | 12 | local exec = function(cmd) 13 | require("kide.term").toggle(cmd) 14 | end 15 | 16 | M.setup = function() 17 | vim.api.nvim_create_user_command("Curl", function(opt) 18 | if opt.args == "" then 19 | local ok, url = pcall(vim.fn.input, "URL: ") 20 | if ok then 21 | exec({ 22 | "curl", 23 | "-w", 24 | outfmt, 25 | url, 26 | }) 27 | end 28 | else 29 | local cmd = { 30 | "curl", 31 | "-w", 32 | outfmt, 33 | } 34 | vim.list_extend(cmd, vim.split(opt.args, " ")) 35 | exec(cmd) 36 | end 37 | end, { 38 | nargs = "*", 39 | complete = function() 40 | return { "-vvv", "--no-sessionid" } 41 | end, 42 | }) 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /lua/kide/lsp/volar.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local me = require("kide.melspconfig") 3 | local vfn = vim.fn 4 | local function get_typescript_server_path(root_dir) 5 | local found_ts = vim.fs.joinpath(root_dir, "node_modules", "typescript", "lib") 6 | if vfn.isdirectory(found_ts) == 1 then 7 | return found_ts 8 | end 9 | return vim.fs.joinpath(me.global_node_modules(), "typescript", "lib") 10 | end 11 | 12 | -- 需要安装 Vue LSP 插件 13 | -- npm install -g @vue/language-server 14 | -- npm install -g @vue/typescript-plugin 15 | M.config = { 16 | name = "volar", 17 | cmd = { "vue-language-server", "--stdio" }, 18 | filetypes = { "vue" }, 19 | root_dir = vim.fs.root(0, { "package.json" }), 20 | init_options = { 21 | typescript = { 22 | tsdk = "", 23 | }, 24 | }, 25 | on_attach = me.on_attach, 26 | on_init = me.on_init, 27 | capabilities = me.capabilities(), 28 | settings = { 29 | volar = {}, 30 | }, 31 | on_new_config = function(new_config, new_root_dir) 32 | if 33 | new_config.init_options 34 | and new_config.init_options.typescript 35 | and new_config.init_options.typescript.tsdk == "" 36 | then 37 | new_config.init_options.typescript.tsdk = get_typescript_server_path(new_root_dir) 38 | end 39 | end, 40 | } 41 | return M 42 | -------------------------------------------------------------------------------- /lua/kide/tools/mermaid.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local function exec(opt) 4 | if not vim.fn.executable("mmdc") then 5 | vim.notify("Mermaid: 没有 mmdc 命令", vim.log.levels.ERROR) 6 | return 7 | end 8 | 9 | local p = vim.fn.expand("%:p:r") 10 | local cmd 11 | if opt.args and #opt.args > 0 then 12 | cmd = vim.deepcopy(opt.args) 13 | else 14 | local args = { 15 | "-i", 16 | opt.file, 17 | "-o", 18 | p .. ".svg", 19 | } 20 | cmd = args 21 | end 22 | table.insert(cmd, 1, "mmdc") 23 | local sid = require("kide").timer_stl_status("") 24 | local result = vim.system(cmd):wait() 25 | require("kide").clean_stl_status(sid, result.code) 26 | if result.code == 0 then 27 | vim.notify("Mermaid: export success", vim.log.levels.INFO) 28 | else 29 | vim.notify("Mermaid: export error", vim.log.levels.ERROR) 30 | end 31 | end 32 | 33 | local function init() 34 | local group = vim.api.nvim_create_augroup("mermaid_export", { clear = true }) 35 | vim.api.nvim_create_autocmd({ "FileType" }, { 36 | group = group, 37 | pattern = { "mermaid" }, 38 | desc = "Export Mermaid file", 39 | callback = function(o) 40 | vim.api.nvim_buf_create_user_command(o.buf, "Mmdc", function(opts) 41 | exec({ 42 | args = opts.fargs, 43 | file = o.file, 44 | }) 45 | end, { 46 | nargs = "*", 47 | }) 48 | end, 49 | }) 50 | end 51 | 52 | M.setup = function() 53 | init() 54 | end 55 | return M 56 | -------------------------------------------------------------------------------- /lua/kide/tools/pandoc.lua: -------------------------------------------------------------------------------- 1 | local utils = require("kide.tools") 2 | local M = {} 3 | local cjk_mainfont = function() 4 | if utils.is_win then 5 | return "Microsoft YaHei UI" 6 | elseif utils.is_linux then 7 | return "Noto Sans CJK SC" 8 | else 9 | return "Yuanti SC" 10 | end 11 | end 12 | 13 | -- pandoc --pdf-engine=xelatex --highlight-style tango -N --toc -V CJKmainfont="Yuanti SC" -V mainfont="Hack" -V geometry:"top=2cm, bottom=1.5cm, left=2cm, right=2cm" test.md -o out.pdf 14 | M.markdown_to_pdf = function() 15 | local group = vim.api.nvim_create_augroup("kide_utils_pandoc", { clear = true }) 16 | vim.api.nvim_create_autocmd({ "FileType" }, { 17 | group = group, 18 | pattern = { "markdown" }, 19 | desc = "Markdown to PDF", 20 | callback = function(o) 21 | vim.api.nvim_buf_create_user_command(o.buf, "PandocMdToPdf", function(_) 22 | require("pandoc.render").file({ 23 | { "--pdf-engine", "xelatex" }, 24 | { "--highlight-style", "tango" }, 25 | { "--number-sections" }, 26 | { "--toc" }, 27 | { "--variable", "CJKmainfont=" .. cjk_mainfont() }, 28 | { "--variable", "mainfont=Hack" }, 29 | { "--variable", "sansfont=Hack" }, 30 | { "--variable", "monofont=Hack" }, 31 | { "--variable", "geometry:top=2cm, bottom=1.5cm, left=2cm, right=2cm" }, 32 | }) 33 | end, { 34 | nargs = "*", 35 | complete = require("pandoc.utils").complete, 36 | }) 37 | end, 38 | }) 39 | end 40 | 41 | M.setup = function() 42 | M.markdown_to_pdf() 43 | end 44 | return M 45 | -------------------------------------------------------------------------------- /lua/kide/gpt/provide/init.lua: -------------------------------------------------------------------------------- 1 | ---@class gpt.Message 2 | ---@field role string 3 | ---@field content string 4 | 5 | ---@class gpt.TokenUsage 6 | ---@field prompt_cache_hit_tokens number? 7 | ---@field prompt_tokens number? 8 | ---@field completion_tokens number? 9 | ---@field total_tokens number? 10 | 11 | ---@class gpt.Event 12 | ---@field done boolean? 13 | ---@field exit number? 14 | ---@field data string? 15 | ---@field usage gpt.TokenUsage? 16 | ---@field reasoning string? 17 | 18 | ---@class gpt.Client 19 | ---@field model string? 20 | ---@field models string? 21 | ---@field request fun(self: gpt.Client, message: table, callback: fun(data: gpt.Event)) 22 | ---@field close fun(self: gpt.Client) 23 | ---@field set_model fun(self: gpt.Client, model: string) 24 | 25 | local M = {} 26 | local deepseek = require("kide.gpt.provide.deepseek") 27 | local openrouter = require("kide.gpt.provide.openrouter") 28 | 29 | M.gpt_provide = deepseek 30 | local _list = { 31 | deepseek = deepseek, 32 | openrouter = openrouter 33 | } 34 | M.provide_keys = function() 35 | local keys = {} 36 | for key, _ in pairs(_list) do 37 | table.insert(keys, key) 38 | end 39 | return keys 40 | end 41 | M.select_provide = function(name) 42 | M.gpt_provide = _list[name] or deepseek 43 | end 44 | 45 | M.models = function() 46 | return M.gpt_provide.models 47 | end 48 | 49 | M.select_model = function(model) 50 | M.gpt_provide.set_model(model) 51 | end 52 | 53 | ---@param type string 54 | ---@return gpt.Client 55 | function M.new_client(type) 56 | return M.gpt_provide.new(type) 57 | end 58 | 59 | M.GptType = { 60 | chat = "chat", 61 | reasoner = "reasoner", 62 | } 63 | return M 64 | -------------------------------------------------------------------------------- /lua/kide/lspui.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.open_info() 4 | -- 获取当前窗口的高度 5 | local columns = vim.o.columns 6 | local lines = vim.o.lines 7 | local width = math.floor(columns * 0.8) 8 | local height = math.floor(lines * 0.8) 9 | 10 | local opts = { 11 | row = math.floor((lines - height) * 0.5), 12 | col = math.floor((columns - width) * 0.5), 13 | relative = "editor", 14 | width = width, -- 窗口的宽度 15 | height = height, -- 窗口的高度 16 | style = "minimal", -- 最小化样式 17 | border = "rounded", -- 窗口边框样式 18 | } 19 | local buf = vim.api.nvim_create_buf(false, true) 20 | local win = vim.api.nvim_open_win(buf, true, opts) 21 | vim.wo[win].number = false 22 | 23 | vim.keymap.set("n", "q", function() 24 | vim.api.nvim_win_close(win, true) 25 | end, { noremap = true, silent = true, buffer = buf }) 26 | local clients = vim.lsp.get_clients() 27 | 28 | local client_info = { 29 | "Lsp Clients:", 30 | "", 31 | } 32 | local function lsp_buffers(id) 33 | local client = vim.lsp.get_client_by_id(id) 34 | return client and vim.tbl_keys(client.attached_buffers) or {} 35 | end 36 | for _, client in pairs(clients) do 37 | vim.list_extend(client_info, { 38 | "Name: " .. client.name, 39 | " Id: " .. client.id, 40 | " buffers: " .. vim.inspect(lsp_buffers(client.id)), 41 | " filetype: " .. vim.inspect(client.config.filetypes), 42 | " root_dir: " .. vim.inspect(client.config.root_dir), 43 | " cmd: " .. vim.inspect(client.config.cmd), 44 | "", 45 | }) 46 | end 47 | vim.api.nvim_put(client_info, "c", true, true) 48 | vim.bo[buf].modifiable = false 49 | vim.bo[buf].readonly = true 50 | end 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /lua/kide/init.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | stl_timer = vim.uv.new_timer(), 3 | stl_stop = false, 4 | } 5 | 6 | function M.set_buf_stl(buf, stl) 7 | vim.b[buf].stl = stl 8 | vim.cmd.redrawstatus() 9 | end 10 | 11 | function M.gpt_stl(buf, icon, title, usage) 12 | if usage then 13 | M.set_buf_stl(buf, { " %#DiagnosticInfo#", icon, " %#StatusLine#", title, " %#Comment#", usage }) 14 | else 15 | M.set_buf_stl(buf, { " %#DiagnosticInfo#", icon, " %#StatusLine#", title }) 16 | end 17 | end 18 | function M.term_stl(buf, cmd) 19 | local cmd_0 = cmd[1] 20 | if cmd_0 == "curl" then 21 | M.set_buf_stl(buf, { " %#DiagnosticInfo#", "󰢩", " %#StatusLine#", "cURL" }) 22 | elseif cmd_0 == "mvn" then 23 | M.set_buf_stl(buf, { " %#DiagnosticError#", "", " %#StatusLine#", "Maven (" .. table.concat(cmd, " ") .. ")" }) 24 | end 25 | end 26 | 27 | function M.lsp_stl(message) 28 | require("kide.stl").set_lsp_status(message) 29 | vim.cmd.redrawstatus() 30 | M.stl_timer:stop() 31 | M.stl_timer:start( 32 | 500, 33 | 0, 34 | vim.schedule_wrap(function() 35 | require("kide.stl").set_lsp_status(nil) 36 | vim.cmd.redrawstatus() 37 | end) 38 | ) 39 | end 40 | 41 | ---清理全局状态 42 | ---@param id number stl id 43 | ---@param code number exit code 44 | function M.clean_stl_status(id, code) 45 | M.stl_stop = true 46 | M.stl_timer:stop() 47 | require("kide.stl").exit_status(id, code) 48 | end 49 | 50 | ---@param title string 51 | ---@param buf? number 52 | function M.timer_stl_status(title, buf) 53 | local id = require("kide.stl").new_status(title) 54 | M.stl_stop = false 55 | M.stl_timer:stop() 56 | M.stl_timer:start( 57 | 0, 58 | 200, 59 | vim.schedule_wrap(function() 60 | if not M.stl_stop then 61 | vim.cmd.redrawstatus() 62 | end 63 | end) 64 | ) 65 | return id 66 | end 67 | 68 | return M 69 | -------------------------------------------------------------------------------- /lua/kide/lspkind.lua: -------------------------------------------------------------------------------- 1 | local icons = require("kide.icons") 2 | local M = {} 3 | M.symbol_map = { 4 | Text = { icon = icons.Text, hl = "@text" }, 5 | Method = { icon = icons.Method, hl = "@function.method" }, 6 | Function = { icon = icons.Function, hl = "@function" }, 7 | Constructor = { icon = icons.Constructor, hl = "@constructor" }, 8 | Field = { icon = icons.Field, hl = "@property" }, 9 | Variable = { icon = icons.Variable, hl = "@variable" }, 10 | Class = { icon = icons.Class, hl = "@type" }, 11 | Interface = { icon = icons.Interface, hl = "@type" }, 12 | Module = { icon = icons.Module, hl = "@namespace" }, 13 | Property = { icon = icons.Property, hl = "@property" }, 14 | Unit = { icon = icons.Unit }, 15 | Value = { icon = icons.Value }, 16 | Enum = { icon = icons.Enum, hl = "@lsp.type.enum" }, 17 | Keyword = { icon = icons.Keyword, hl = "@keyword" }, 18 | Snippet = { icon = icons.Snippet }, 19 | Color = { icon = icons.Color }, 20 | File = { icon = icons.File }, 21 | Reference = { icon = icons.Reference, hl = "@reference" }, 22 | Folder = { icon = icons.Folder }, 23 | EnumMember = { icon = icons.EnumMember, hl = "@lsp.type.enumMember" }, 24 | Constant = { icon = icons.Constant, hl = "@constant" }, 25 | Struct = { icon = icons.Struct, hl = "@type" }, 26 | Event = { icon = icons.Event, hl = "@type" }, 27 | Operator = { icon = icons.Operator, hl = "@operator" }, 28 | TypeParameter = { icon = "", hl = "@lsp.type.parameter" }, 29 | Key = { icon = icons.Keyword, hl = "@type" }, 30 | Null = { icon = icons.Null, hl = "@type" }, 31 | Namespace = { icon = icons.Namespace, hl = "@namespace" }, 32 | Package = { icon = icons.Package, hl = "@namespace" }, 33 | String = { icon = icons.String, hl = "@string" }, 34 | Number = { icon = icons.Number, hl = "@number" }, 35 | Boolean = { icon = icons.Boolean, hl = "@boolean" }, 36 | Array = { icon = icons.Array, hl = "@constant" }, 37 | Object = { icon = icons.Object, hl = "@type" }, 38 | --------------------------------------------------------- 39 | Component = { icon = "󰡀", hl = "@function" }, 40 | Fragment = { icon = "", hl = "@constant" }, 41 | } 42 | 43 | return M 44 | -------------------------------------------------------------------------------- /lua/kide/yazi.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local state = {} 3 | local function open_file(open) 4 | if vim.fn.filereadable(vim.fn.expand(state.chooserfile)) == 1 then 5 | local filenames = vim.fn.readfile(state.chooserfile) 6 | for _, filename in ipairs(filenames) do 7 | if vim.fn.filereadable(filename) == 1 then 8 | vim.cmd(open .. " " .. filename) 9 | end 10 | end 11 | end 12 | end 13 | local function yazi_close() 14 | if state.chooserfile then 15 | vim.fn.delete(state.chooserfile) 16 | state.chooserfile = nil 17 | end 18 | end 19 | 20 | function M.yazi(open) 21 | if vim.api.nvim_get_mode().mode == "i" then 22 | vim.cmd("stopinsert") 23 | end 24 | open = open or "edit" 25 | state.path = vim.fn.getcwd() 26 | state.filename = vim.api.nvim_buf_get_name(0) 27 | if state.filename == "" then 28 | state.filename = state.path 29 | end 30 | state.chooserfile = vim.fn.tempname() 31 | 32 | local columns = vim.o.columns 33 | local lines = vim.o.lines 34 | local width = math.floor(columns * 0.9) 35 | local height = math.floor(lines * 0.9) 36 | local opts = { 37 | relative = "editor", 38 | style = "minimal", 39 | row = math.floor((lines - height) * 0.5), 40 | col = math.floor((columns - width) * 0.5), 41 | width = width, 42 | height = height, 43 | focusable = true, 44 | border = "rounded", 45 | title = "Yazi", 46 | title_pos = "center", 47 | } 48 | 49 | state.buf = vim.api.nvim_create_buf(false, true) 50 | state.win = vim.api.nvim_open_win(state.buf, true, opts) 51 | vim.bo[state.buf].modified = false 52 | 53 | vim.api.nvim_create_autocmd("WinLeave", { 54 | buffer = state.buf, 55 | callback = function() 56 | vim.api.nvim_buf_delete(state.buf, { force = true }) 57 | state.buf = nil 58 | end, 59 | }) 60 | vim.api.nvim_create_autocmd({ "TermOpen", "BufEnter" }, { 61 | buffer = state.buf, 62 | command = "startinsert!", 63 | once = true, 64 | }) 65 | 66 | vim.fn.jobstart({ "yazi", state.filename, "--chooser-file", state.chooserfile }, { 67 | term = true, 68 | on_exit = function() 69 | if vim.api.nvim_win_is_valid(state.win) then 70 | pcall(vim.api.nvim_win_close, state.win, true) 71 | state.winid = nil 72 | open_file(open) 73 | end 74 | yazi_close() 75 | end, 76 | }) 77 | end 78 | return M 79 | -------------------------------------------------------------------------------- /lua/kide/lsp/sonarlint.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.setup = function() 4 | local vscode = require("kide.tools.vscode") 5 | local utils = require("kide.tools") 6 | local sonarlint_ls = vscode.find_one("/sonarsource.sonarlint-vscode*/server/sonarlint-ls.jar") 7 | if not sonarlint_ls then 8 | vim.notify("sonarlint not found", vim.log.levels.WARN) 9 | return 10 | end 11 | local analyzer_path = vscode.find_one("/sonarsource.sonarlint-vscode*/analyzers") 12 | 13 | local analyzer_jar = vim.split(vim.fn.glob(analyzer_path .. "/*.jar"), "\n") 14 | 15 | -- https://github.com/SonarSource/sonarlint-vscode/blob/fc8e3f2f6d811dd7d7a7d178f2a471173c233a27/src/lsp/server.ts#L35 16 | analyzer_jar = vim.tbl_filter(function(value) 17 | return false 18 | -- or vim.endswith(value, "sonargo.jar") 19 | or vim.endswith(value, "sonarjava.jar") 20 | -- or vim.endswith(value, "sonarjs.jar") 21 | -- or vim.endswith(value, "sonarphp.jar") 22 | or vim.endswith(value, "sonarpython.jar") 23 | -- or vim.endswith(value, "sonarhtml.jar") 24 | -- or vim.endswith(value, "sonarxml.jar") 25 | -- or vim.endswith(value, "sonarcfamily.jar") 26 | -- or vim.endswith(value, "sonartext.jar") 27 | -- or vim.endswith(value, "sonariac.jar") 28 | -- or vim.endswith(value, "sonarlintomnisharp.jar") 29 | end, analyzer_jar) 30 | 31 | local cmd = { 32 | utils.java_bin(), 33 | "-Xmx1g", 34 | "-XX:+UseZGC", 35 | "-Dsonarlint.telemetry.disabled=true", 36 | "-jar", 37 | sonarlint_ls, 38 | "-stdio", 39 | "-analyzers", 40 | } 41 | vim.list_extend(cmd, analyzer_jar) 42 | require("sonarlint").setup({ 43 | server = { 44 | cmd = cmd, 45 | init_options = { 46 | connections = {}, 47 | rules = {}, 48 | }, 49 | settings = { 50 | sonarlint = { 51 | connectedMode = { 52 | connections = {}, 53 | }, 54 | disableTelemetry = true, 55 | }, 56 | -- https://github.com/SonarSource/sonarlint-language-server/blob/351c430da636462a39ddeecc5a40ae04c832d73c/src/main/java/org/sonarsource/sonarlint/ls/settings/SettingsManager.java#L322 57 | -- 这里尝试获取 files.exclude 返回了 null 导致类型转换异常 58 | files = { exclude = { test = false } }, 59 | }, 60 | }, 61 | filetypes = { 62 | "java", 63 | "python", 64 | }, 65 | }) 66 | end 67 | 68 | return M 69 | -------------------------------------------------------------------------------- /lua/kide/lsp/pyright.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | M._init_dap = false 3 | 4 | local function get_python_path() 5 | if vim.env.VIRTUAL_ENV then 6 | return vim.fs.joinpath(vim.env.VIRTUAL_ENV, "bin", "python") 7 | end 8 | if vim.env.PY_BIN then 9 | return vim.env.PY_BIN 10 | end 11 | local cwd = vim.loop.cwd() 12 | if vim.fn.executable(vim.fs.joinpath(cwd, ".venv")) then 13 | return vim.fs.joinpath(cwd, ".venv", "bin", "python") 14 | end 15 | local python = vim.fn.exepath("python3") 16 | if python == nil or python == "" then 17 | python = vim.fn.exepath("python") 18 | end 19 | return python 20 | end 21 | 22 | function M.init_dap() 23 | if M._init_dap then 24 | return 25 | end 26 | M._init_dap = true 27 | require("dap-python").setup(get_python_path()) 28 | end 29 | 30 | local me = require("kide.melspconfig") 31 | 32 | -- see nvim-lspconfig 33 | function M.organize_imports() 34 | local params = { 35 | command = "pyright.organizeimports", 36 | arguments = { vim.uri_from_bufnr(0) }, 37 | } 38 | 39 | local clients = vim.lsp.get_clients({ 40 | bufnr = vim.api.nvim_get_current_buf(), 41 | name = "pyright", 42 | }) 43 | for _, client in ipairs(clients) do 44 | client:request("workspace/executeCommand", params, nil, 0) 45 | end 46 | end 47 | 48 | M.config = { 49 | name = "pyright", 50 | cmd = { "pyright-langserver", "--stdio" }, 51 | root_dir = vim.fs.root(0, { ".git", "requirements.txt", "pyproject.toml" }) or vim.uv.cwd(), 52 | on_attach = function(client, bufnr) 53 | local dap_py = require("dap-python") 54 | vim.keymap.set("n", "dc", dap_py.test_class, { desc = "Dap Test Class", buffer = bufnr }) 55 | vim.keymap.set("n", "dm", dap_py.test_method, { desc = "Dap Test Method", buffer = bufnr }) 56 | vim.keymap.set("v", "ds", dap_py.debug_selection, { desc = "Dap Debug Selection", buffer = bufnr }) 57 | 58 | local create_command = vim.api.nvim_buf_create_user_command 59 | create_command(bufnr, "OR", M.organize_imports, { 60 | nargs = 0, 61 | }) 62 | me.on_attach(client, bufnr) 63 | end, 64 | on_init = me.on_init, 65 | capabilities = me.capabilities(), 66 | settings = { 67 | python = { 68 | pythonPath = get_python_path(), 69 | analysis = { 70 | autoSearchPaths = true, 71 | useLibraryCodeForTypes = true, 72 | diagnosticMode = "openFilesOnly", 73 | }, 74 | }, 75 | }, 76 | } 77 | 78 | return M 79 | -------------------------------------------------------------------------------- /lua/kide/http/sse.lua: -------------------------------------------------------------------------------- 1 | ---@class http.SseEvent 2 | ---@field data table? 3 | ---@field exit number? 4 | 5 | ---@class http.SseClient 6 | ---@field url string 7 | ---@field method string? 8 | ---@field token string? 9 | ---@field payload string? 10 | ---@field callback fun(error, event: http.SseEvent)? 11 | ---@field job number? 12 | local SseClient = {} 13 | SseClient.__index = SseClient 14 | 15 | ---@return http.SseClient 16 | function SseClient.new(url) 17 | local self = setmetatable({}, SseClient) 18 | self.url = url 19 | return self 20 | end 21 | 22 | ---@return http.SseClient 23 | function SseClient:POST() 24 | self.method = "POST" 25 | return self 26 | end 27 | 28 | ---@return http.SseClient 29 | function SseClient:body(body) 30 | self.payload = body 31 | return self 32 | end 33 | 34 | ---@return http.SseClient 35 | function SseClient:handle(handle) 36 | self.callback = handle 37 | return self 38 | end 39 | 40 | ---@return http.SseClient 41 | function SseClient:auth(token) 42 | self.token = token 43 | return self 44 | end 45 | 46 | ---@param client http.SseClient 47 | ---@return table 48 | local function _cmd(client) 49 | local body = vim.fn.json_encode(client.payload) 50 | local cmd = { 51 | "curl", 52 | "--no-buffer", 53 | "-s", 54 | "-X", 55 | client.method, 56 | "-H", 57 | "Content-Type: application/json", 58 | "-H", 59 | "Authorization: Bearer " .. client.token, 60 | "-d", 61 | body, 62 | client.url, 63 | } 64 | return cmd 65 | end 66 | 67 | ---@param client http.SseClient 68 | local function handle_sse_events(client) 69 | local sid = require("kide").timer_stl_status("") 70 | client.job = vim.fn.jobstart(_cmd(client), { 71 | on_stdout = function(_, data, _) 72 | client.callback(nil, { 73 | data = data, 74 | }) 75 | end, 76 | on_stderr = function(_, _, _) 77 | end, 78 | on_exit = function(_, code, _) 79 | require("kide").clean_stl_status(sid, code) 80 | client.callback(nil, { 81 | data = nil, 82 | exit = code, 83 | }) 84 | end, 85 | }) 86 | end 87 | 88 | ---@return http.SseClient 89 | function SseClient:send() 90 | handle_sse_events(self) 91 | return self 92 | end 93 | 94 | function SseClient:stop() 95 | if self.job then 96 | pcall(vim.fn.jobstop, self.job) 97 | end 98 | end 99 | 100 | return SseClient 101 | -------------------------------------------------------------------------------- /lua/kide/lsp/spring-boot.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local me = require("kide.melspconfig") 3 | local function ls_path() 4 | local path = vim.env["JDTLS_SPRING_TOOLS_PATH"] 5 | if path == nil or path == "" then 6 | return nil 7 | end 8 | return require("spring_boot").get_boot_ls(path .. "/language-server") 9 | end 10 | local lspath = ls_path() 11 | if lspath == nil then 12 | return M 13 | end 14 | M.config = require("spring_boot.launch").update_ls_config(require("spring_boot").setup({ 15 | ls_path = lspath, 16 | server = { 17 | on_attach = function(client, bufnr) 18 | me.on_attach(client, bufnr) 19 | M.bootls_user_command(bufnr) 20 | end, 21 | on_init = function(client, ctx) 22 | client.server_capabilities.documentHighlightProvider = false 23 | me.on_init(client, ctx) 24 | end, 25 | capabilities = me.capabilities(), 26 | }, 27 | autocmd = false, 28 | })) 29 | 30 | M.bootls_user_command = function(buf) 31 | local create_command = vim.api.nvim_buf_create_user_command 32 | create_command(buf, "SpringBoot", function(opt) 33 | local on_choice = function(choice) 34 | if choice == "Annotations" then 35 | vim.lsp.buf.workspace_symbol("@") 36 | elseif choice == "Beans" then 37 | vim.lsp.buf.workspace_symbol("@+") 38 | elseif choice == "RequestMappings" then 39 | vim.lsp.buf.workspace_symbol("@/") 40 | elseif choice == "Prototype" then 41 | vim.lsp.buf.workspace_symbol("@>") 42 | end 43 | end 44 | if opt.args and opt.args ~= "" then 45 | on_choice(opt.args) 46 | else 47 | vim.ui.select({ "Annotations", "Beans", "RequestMappings", "Prototype" }, { 48 | prompt = "Spring Symbol:", 49 | format_item = function(item) 50 | if item == "Annotations" then 51 | return "shows all Spring annotations in the code" 52 | elseif item == "Beans" then 53 | return "shows all defined beans" 54 | elseif item == "RequestMappings" then 55 | return "shows all defined request mappings" 56 | elseif item == "Prototype" then 57 | return "shows all functions (prototype implementation)" 58 | end 59 | end, 60 | }, on_choice) 61 | end 62 | end, { 63 | desc = "Spring Boot", 64 | nargs = "?", 65 | range = false, 66 | complete = function() 67 | return { "Annotations", "Beans", "RequestMappings", "Prototype" } 68 | end, 69 | }) 70 | end 71 | 72 | return M 73 | -------------------------------------------------------------------------------- /lua/kide/codex.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local state = { 3 | buf = nil, 4 | win = nil, 5 | } 6 | 7 | local function win_opts() 8 | local columns = vim.o.columns 9 | local lines = vim.o.lines 10 | local width = math.floor(columns * 0.9) 11 | local height = math.floor(lines * 0.9) 12 | return { 13 | relative = "editor", 14 | style = "minimal", 15 | row = math.floor((lines - height) * 0.5), 16 | col = math.floor((columns - width) * 0.5), 17 | width = width, 18 | height = height, 19 | focusable = true, 20 | border = "rounded", 21 | title = "Codex", 22 | title_pos = "center", 23 | } 24 | end 25 | 26 | local function close_window(force) 27 | if state.win and vim.api.nvim_win_is_valid(state.win) then 28 | pcall(vim.api.nvim_win_close, state.win, force or false) 29 | end 30 | state.win = nil 31 | end 32 | 33 | local function clear_buffer() 34 | if state.buf and vim.api.nvim_buf_is_valid(state.buf) then 35 | vim.api.nvim_buf_delete(state.buf, { force = true }) 36 | end 37 | state.buf = nil 38 | end 39 | 40 | local function reset_state() 41 | close_window(true) 42 | clear_buffer() 43 | state.job = nil 44 | end 45 | 46 | function M.codex() 47 | if vim.api.nvim_get_mode().mode == "i" then 48 | vim.cmd("stopinsert") 49 | end 50 | if state.buf ~= nil then 51 | if state.win ~= nil then 52 | close_window(true) 53 | return 54 | end 55 | state.win = vim.api.nvim_open_win(state.buf, true, win_opts()) 56 | return 57 | end 58 | 59 | state.buf = vim.api.nvim_create_buf(false, true) 60 | state.win = vim.api.nvim_open_win(state.buf, true, win_opts()) 61 | vim.bo[state.buf].modified = false 62 | vim.b[state.buf].q_close = false 63 | 64 | vim.api.nvim_create_autocmd("WinLeave", { 65 | buffer = state.buf, 66 | callback = function() 67 | close_window(false) 68 | end, 69 | }) 70 | vim.api.nvim_create_autocmd({ "TermOpen" }, { 71 | buffer = state.buf, 72 | command = "startinsert!", 73 | once = true, 74 | }) 75 | 76 | local job_opts = { 77 | term = true, 78 | on_exit = function() 79 | reset_state() 80 | end, 81 | } 82 | 83 | local ok, job_or_err = pcall(vim.fn.jobstart, { "codex" }, job_opts) 84 | if not ok then 85 | reset_state() 86 | vim.notify(("Codex failed to start: %s"):format(job_or_err), vim.log.levels.ERROR) 87 | return 88 | end 89 | 90 | if job_or_err <= 0 then 91 | reset_state() 92 | vim.notify("Codex failed to start: invalid job id", vim.log.levels.ERROR) 93 | return 94 | end 95 | 96 | state.job = job_or_err 97 | end 98 | return M 99 | -------------------------------------------------------------------------------- /after/ftplugin/java.lua: -------------------------------------------------------------------------------- 1 | local jc = require("kide.lsp.jdtls") 2 | if jc.config then 3 | local config 4 | -- 防止 start_or_attach 重复修改 config 5 | if jc.init then 6 | config = { 7 | cmd = {}, 8 | } 9 | else 10 | config = jc.config 11 | jc.init = true 12 | if vim.g.enable_spring_boot == true then 13 | local boot_jar_path = vim.env["JDTLS_SPRING_TOOLS_PATH"] 14 | if boot_jar_path then 15 | vim.list_extend(config["init_options"].bundles, require("spring_boot").get_jars(boot_jar_path .. "/jars")) 16 | else 17 | vim.list_extend(config["init_options"].bundles, require("spring_boot").java_extensions()) 18 | end 19 | end 20 | 21 | if vim.g.enable_quarkus == true then 22 | -- 添加 jdtls 扩展 jar 包 23 | local ok_microprofile, microprofile = pcall(require, "microprofile") 24 | if ok_microprofile then 25 | vim.list_extend(config["init_options"].bundles, microprofile.java_extensions()) 26 | end 27 | 28 | local ok_quarkus, quarkus = pcall(require, "quarkus") 29 | if ok_quarkus then 30 | vim.list_extend(config["init_options"].bundles, quarkus.java_extensions()) 31 | end 32 | local on_init = config.on_init 33 | config.on_init = function(client, ctx) 34 | if ok_quarkus then 35 | require("quarkus.bind").try_bind_qute_all_request() 36 | end 37 | if ok_microprofile then 38 | require("microprofile.bind").try_bind_microprofile_all_request() 39 | end 40 | if on_init then 41 | on_init(client, ctx) 42 | end 43 | end 44 | end 45 | end 46 | require("jdtls").start_or_attach(config, { dap = { config_overrides = {}, hotcodereplace = "auto" } }) 47 | 48 | if vim.g.enable_spring_boot == true then 49 | local sc = require("kide.lsp.spring-boot").config 50 | require("spring_boot.launch").start(sc) 51 | end 52 | if vim.g.enable_quarkus == true then 53 | local qc = require("kide.lsp.quarkus").config 54 | vim.lsp.start(qc) 55 | local mc = require("kide.lsp.microprofile").config 56 | vim.lsp.start(mc) 57 | end 58 | end 59 | 60 | -- see mfussenegger/dotfiles 61 | local checkstyle_config = vim.uv.cwd() .. "/checkstyle.xml" 62 | local has_checkstyle = vim.uv.fs_stat(checkstyle_config) and vim.fn.executable("checkstyle") 63 | local is_main = vim.api.nvim_buf_get_name(0):find("src/main/java") ~= nil 64 | if has_checkstyle and is_main then 65 | local bufnr = vim.api.nvim_get_current_buf() 66 | require("lint.linters.checkstyle").config_file = checkstyle_config 67 | vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, { 68 | buffer = bufnr, 69 | group = vim.api.nvim_create_augroup("checkstyle-" .. bufnr, { clear = true }), 70 | callback = function() 71 | if not vim.bo[bufnr].modified then 72 | require("lint").try_lint("checkstyle") 73 | end 74 | end, 75 | }) 76 | end 77 | -------------------------------------------------------------------------------- /lua/kide/melspconfig.lua: -------------------------------------------------------------------------------- 1 | local lsp = vim.lsp 2 | local keymap = vim.keymap 3 | local vfn = vim.fn 4 | local M = {} 5 | local kide = require("kide") 6 | 7 | local function notify_progress() 8 | vim.api.nvim_create_autocmd("LspProgress", { 9 | ---@param ev {data: {client_id: integer, params: lsp.ProgressParams}} 10 | callback = function(ev) 11 | local client = vim.lsp.get_client_by_id(ev.data.client_id) 12 | local value = ev.data.params.value 13 | if not client or type(value) ~= "table" then 14 | return 15 | end 16 | kide.lsp_stl("[" .. client.name .. "] " .. (value.message or "")) 17 | end, 18 | }) 19 | end 20 | 21 | M.on_attach = function(client, bufnr) 22 | if vim.lsp.document_color then 23 | vim.lsp.document_color.enable(true, bufnr, { style = "virtual" }) 24 | end 25 | local kopts = { noremap = true, silent = true, buffer = bufnr } 26 | keymap.set({ "n", "v" }, "ca", vim.lsp.buf.code_action, kopts) 27 | keymap.set("n", "K", function() 28 | lsp.buf.hover({ border = "rounded" }) 29 | end, kopts) 30 | keymap.set("n", "gs", function() 31 | lsp.buf.signature_help({ border = "rounded" }) 32 | end, kopts) 33 | keymap.set("n", "gd", lsp.buf.definition, kopts) 34 | keymap.set("n", "gD", lsp.buf.type_definition, kopts) 35 | keymap.set("n", "gr", lsp.buf.references, kopts) 36 | keymap.set("n", "gi", lsp.buf.implementation, kopts) 37 | keymap.set("n", "rn", lsp.buf.rename, kopts) 38 | vim.keymap.set("n", "]r", function() 39 | Snacks.words.jump(1) 40 | end, kopts) 41 | vim.keymap.set("n", "[r", function() 42 | Snacks.words.jump(-1) 43 | end, kopts) 44 | end 45 | 46 | M.on_init = function(client, _) 47 | -- 由于卡顿,暂时禁用semanticTokens 48 | -- 看起来已经修复了,可以试试 49 | -- if client.supports_method("textDocument/semanticTokens") then 50 | -- client.server_capabilities.semanticTokensProvider = nil 51 | -- end 52 | end 53 | M.capabilities = function(opt) 54 | local capabilities = vim.lsp.protocol.make_client_capabilities() 55 | if opt then 56 | capabilities = vim.tbl_deep_extend("force", capabilities, opt) 57 | end 58 | 59 | return require("blink.cmp").get_lsp_capabilities(capabilities) 60 | end 61 | 62 | M.init_lsp = function() 63 | notify_progress() 64 | if vim.env["COPILOT_ENABLE"] == "Y" then 65 | vim.lsp.enable("copilot") 66 | end 67 | end 68 | 69 | function M.global_node_modules() 70 | local global_path = "" 71 | if vfn.isdirectory("/opt/homebrew/lib/node_modules") == 1 then 72 | global_path = "/opt/homebrew/lib/node_modules" 73 | elseif vfn.isdirectory("/usr/local/lib/node_modules") == 1 then 74 | global_path = "/usr/local/lib/node_modules" 75 | elseif vfn.isdirectory("/usr/lib64/node_modules") == 1 then 76 | global_path = "/usr/lib64/node_modules" 77 | else 78 | global_path = vim.fs.joinpath(os.getenv("HOME"), ".npm", "lib", "node_modules") 79 | end 80 | if vfn.isdirectory(global_path) == 0 then 81 | vim.notify("Global node_modules not found", vim.log.levels.DEBUG) 82 | end 83 | return global_path 84 | end 85 | 86 | return M 87 | -------------------------------------------------------------------------------- /lua/kide/term.lua: -------------------------------------------------------------------------------- 1 | -- 使用 https://github.com/mfussenegger/dotfiles/blob/master/vim/dot-config/nvim/lua/me/term.lua 2 | local api = vim.api 3 | 4 | local M = {} 5 | 6 | local job = nil 7 | local termwin = nil 8 | local repls = { 9 | python = "py", 10 | lua = "lua", 11 | } 12 | local sid 13 | 14 | local function launch_term(cmd, opts) 15 | opts = opts or {} 16 | 17 | opts.term = true 18 | local path = vim.bo.path 19 | vim.cmd("belowright new") 20 | 21 | termwin = api.nvim_get_current_win() 22 | require("kide").term_stl(vim.api.nvim_get_current_buf(), cmd) 23 | vim.bo.path = path 24 | vim.bo.buftype = "nofile" 25 | vim.bo.bufhidden = "wipe" 26 | vim.bo.buflisted = false 27 | vim.bo.swapfile = false 28 | opts = vim.tbl_extend("error", opts, { 29 | on_exit = function(_, code, _) 30 | job = nil 31 | if sid then 32 | require("kide").clean_stl_status(sid, code) 33 | end 34 | end, 35 | }) 36 | job = vim.fn.jobstart(cmd, opts) 37 | end 38 | 39 | local function close_term() 40 | if not job then 41 | return 42 | end 43 | vim.fn.jobstop(job) 44 | job = nil 45 | if termwin and api.nvim_win_is_valid(termwin) then 46 | -- avoid cannot close last window error 47 | pcall(api.nvim_win_close, termwin, true) 48 | end 49 | termwin = nil 50 | end 51 | 52 | function M.repl() 53 | local win = api.nvim_get_current_win() 54 | M.toggle(repls[vim.bo.filetype]) 55 | api.nvim_set_current_win(win) 56 | end 57 | 58 | function M.toggle(cmd, opts) 59 | if cmd then 60 | sid = require("kide").timer_stl_status("") 61 | end 62 | if job then 63 | close_term() 64 | else 65 | cmd = cmd or (vim.env["SHELL"] or "sh") 66 | launch_term(cmd, opts) 67 | end 68 | end 69 | 70 | function M.run() 71 | local filepath = api.nvim_buf_get_name(0) 72 | local lines = api.nvim_buf_get_lines(0, 0, 1, true) 73 | ---@type string|string[] 74 | local cmd = filepath 75 | if not vim.startswith(lines[1], "#!/usr/bin/env") then 76 | local choice = vim.fn.confirm("File has no shebang, sure you want to execute it?", "&Yes\n&No") 77 | if choice ~= 1 then 78 | return 79 | end 80 | end 81 | local stat = vim.loop.fs_stat(filepath) 82 | if stat then 83 | local user_execute = tonumber("00100", 8) 84 | if bit.band(stat.mode, user_execute) ~= user_execute then 85 | local newmode = bit.bor(stat.mode, user_execute) 86 | vim.loop.fs_chmod(filepath, newmode) 87 | end 88 | end 89 | close_term() 90 | launch_term(cmd) 91 | end 92 | 93 | function M.send_line(line) 94 | if not job then 95 | return 96 | end 97 | vim.fn.chansend(job, line .. "\n") 98 | end 99 | 100 | M.last_input = nil 101 | function M.input_run(last) 102 | if last then 103 | return M.toggle(M.last_input) 104 | end 105 | local ok, cmd = pcall(vim.fn.input, "CMD: ") 106 | if ok then 107 | if cmd == "" then 108 | M.toggle() 109 | else 110 | M.last_input = cmd 111 | M.toggle(cmd) 112 | end 113 | end 114 | end 115 | 116 | return M 117 | -------------------------------------------------------------------------------- /lua/kide/gpt/commit.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local gpt_provide = require("kide.gpt.provide") 4 | ---@type gpt.Client 5 | local client = nil 6 | 7 | function M.commit_message(diff, callback) 8 | local messages = { 9 | { 10 | content = "", 11 | role = "system", 12 | }, 13 | { 14 | content = "", 15 | role = "user", 16 | }, 17 | } 18 | messages[1].content = 19 | "I want you to act as a commit message generator. I will provide you with information about the task and the prefix for the task code, and I would like you to generate an appropriate commit message using the conventional commit format. Do not write any explanations or other words, just reply with the commit message." 20 | messages[2].content = diff 21 | client = gpt_provide.new_client("commit") 22 | client:request(messages, callback) 23 | end 24 | 25 | M.commit_diff_msg = function() 26 | local diff = vim.system({ "git", "diff", "--cached" }):wait() 27 | if diff.code ~= 0 then 28 | return 29 | end 30 | local codebuf = vim.api.nvim_get_current_buf() 31 | if "gitcommit" ~= vim.bo[codebuf].filetype then 32 | return 33 | end 34 | local closed = false 35 | vim.cmd("normal! gg0") 36 | 37 | vim.api.nvim_create_autocmd("BufWipeout", { 38 | buffer = codebuf, 39 | callback = function() 40 | closed = true 41 | if client then 42 | client:close() 43 | end 44 | end, 45 | }) 46 | vim.keymap.set("n", "", function() 47 | closed = true 48 | if client then 49 | client:close() 50 | end 51 | vim.keymap.del("n", "", { buffer = codebuf }) 52 | end, { buffer = codebuf, noremap = true, silent = true }) 53 | 54 | local callback = function(opt) 55 | local data = opt.data 56 | if closed then 57 | vim.fn.jobstop(opt.job) 58 | return 59 | end 60 | if opt.done then 61 | return 62 | end 63 | 64 | local put_data = {} 65 | if vim.api.nvim_buf_is_valid(codebuf) then 66 | if data:match("\n") then 67 | put_data = vim.split(data, "\n") 68 | else 69 | put_data = { data } 70 | end 71 | vim.api.nvim_put(put_data, "c", true, true) 72 | end 73 | end 74 | M.commit_message(diff.stdout, callback) 75 | end 76 | 77 | M.setup = function() 78 | local command = vim.api.nvim_buf_create_user_command 79 | local autocmd = vim.api.nvim_create_autocmd 80 | local function augroup(name) 81 | return vim.api.nvim_create_augroup("kide" .. name, { clear = true }) 82 | end 83 | autocmd("FileType", { 84 | group = augroup("gpt_commit_msg"), 85 | pattern = "gitcommit", 86 | callback = function(event) 87 | command(event.buf, "GptCommitMsg", function(_) 88 | M.commit_diff_msg() 89 | end, { 90 | desc = "Gpt Commit Message", 91 | nargs = 0, 92 | range = false, 93 | }) 94 | 95 | vim.keymap.set("n", "cm", function() 96 | M.commit_diff_msg() 97 | end, { buffer = event.buf, noremap = true, silent = true }) 98 | end, 99 | }) 100 | end 101 | 102 | return M 103 | -------------------------------------------------------------------------------- /lua/kide/gpt/code.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local gpt_provide = require("kide.gpt.provide") 3 | ---@type gpt.Client 4 | local client = nil 5 | 6 | function M.completions(param, callback) 7 | local messages = { 8 | { 9 | content = "帮我生成一个快速排序", 10 | role = "user", 11 | }, 12 | { 13 | content = "```python\n", 14 | prefix = true, 15 | role = "assistant", 16 | }, 17 | } 18 | messages[1].content = param.message 19 | messages[2].content = "```" .. param.filetype .. "\n" 20 | client = gpt_provide.new_client("code") 21 | client:request(messages, callback) 22 | end 23 | 24 | M.code_completions = function(opts) 25 | local codebuf = vim.api.nvim_get_current_buf() 26 | local codewin = vim.api.nvim_get_current_win() 27 | local filetype = vim.bo[codebuf].filetype 28 | local closed = false 29 | local message 30 | if opts.inputcode then 31 | message = "```" .. filetype .. "\n" .. table.concat(opts.inputcode, "\n") .. "```\n" .. opts.message 32 | vim.api.nvim_win_set_cursor(codewin, { vim.fn.getpos("'>")[2] + 1, 0 }) 33 | else 34 | message = opts.message 35 | end 36 | vim.api.nvim_create_autocmd("BufWipeout", { 37 | buffer = codebuf, 38 | callback = function() 39 | closed = true 40 | if client then 41 | client:close() 42 | end 43 | end, 44 | }) 45 | 46 | vim.keymap.set("n", "", function() 47 | closed = true 48 | if client then 49 | client:close() 50 | end 51 | vim.keymap.del("n", "", { buffer = codebuf }) 52 | end, { buffer = codebuf, noremap = true, silent = true }) 53 | 54 | local callback = function(opt) 55 | local data = opt.data 56 | if closed then 57 | vim.fn.jobstop(opt.job) 58 | return 59 | end 60 | if opt.done then 61 | return 62 | end 63 | 64 | local put_data = {} 65 | if vim.api.nvim_buf_is_valid(codebuf) then 66 | if data:match("\n") then 67 | put_data = vim.split(data, "\n") 68 | else 69 | put_data = { data } 70 | end 71 | vim.api.nvim_put(put_data, "c", true, true) 72 | end 73 | end 74 | M.completions({ 75 | filetype = filetype, 76 | message = message, 77 | }, callback) 78 | end 79 | 80 | M.setup = function() 81 | local command = vim.api.nvim_buf_create_user_command 82 | local autocmd = vim.api.nvim_create_autocmd 83 | local function augroup(name) 84 | return vim.api.nvim_create_augroup("kide" .. name, { clear = true }) 85 | end 86 | autocmd("FileType", { 87 | group = augroup("gpt_code_gen"), 88 | pattern = "*", 89 | callback = function(event) 90 | command(event.buf, "GptCode", function(opts) 91 | local code 92 | if opts.range > 0 then 93 | code = require("kide.tools").get_visual_selection() 94 | end 95 | M.code_completions({ 96 | inputcode = code, 97 | message = opts.args, 98 | }) 99 | end, { 100 | desc = "Gpt Code", 101 | nargs = "+", 102 | range = true, 103 | }) 104 | end, 105 | }) 106 | end 107 | 108 | return M 109 | -------------------------------------------------------------------------------- /lua/kide/lsp/rustowl.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local hlns = vim.api.nvim_create_namespace("rustowl") 3 | vim.api.nvim_set_hl(0, "lifetime", { undercurl = true, sp = "#00cc00" }) 4 | vim.api.nvim_set_hl(0, "imm_borrow", { undercurl = true, sp = "#0000cc" }) 5 | vim.api.nvim_set_hl(0, "mut_borrow", { undercurl = true, sp = "#cc00cc" }) 6 | vim.api.nvim_set_hl(0, "move", { undercurl = true, sp = "#cccc00" }) 7 | vim.api.nvim_set_hl(0, "call", { undercurl = true, sp = "#cccc00" }) 8 | vim.api.nvim_set_hl(0, "outlive", { undercurl = true, sp = "#cc0000" }) 9 | 10 | local function show_rustowl(bufnr) 11 | local clients = vim.lsp.get_clients({ bufnr = bufnr, name = "rustowl" }) 12 | for _, client in ipairs(clients) do 13 | local line, col = unpack(vim.api.nvim_win_get_cursor(0)) 14 | client.request("rustowl/cursor", { 15 | position = { 16 | line = line - 1, 17 | character = col, 18 | }, 19 | document = vim.lsp.util.make_text_document_params(), 20 | }, function(err, result, ctx) 21 | if result ~= nil then 22 | for _, deco in ipairs(result["decorations"]) do 23 | if deco["is_display"] == true then 24 | local start = { deco["range"]["start"]["line"], deco["range"]["start"]["character"] } 25 | local finish = { deco["range"]["end"]["line"], deco["range"]["end"]["character"] } 26 | vim.highlight.range(bufnr, hlns, deco["type"], start, finish, { regtype = "v", inclusive = true }) 27 | end 28 | end 29 | end 30 | end, bufnr) 31 | end 32 | end 33 | 34 | local function rustowl_on_attach(hover, client, bufnr, idle_time_ms) 35 | local timer = nil 36 | local augroup = vim.api.nvim_create_augroup("RustOwlCmd", { clear = true }) 37 | 38 | local function clear_timer() 39 | if timer then 40 | timer:stop() 41 | timer:close() 42 | timer = nil 43 | end 44 | end 45 | 46 | local function start_timer() 47 | clear_timer() 48 | timer = vim.uv.new_timer() 49 | timer:start( 50 | idle_time_ms, 51 | 0, 52 | vim.schedule_wrap(function() 53 | show_rustowl(bufnr) 54 | end) 55 | ) 56 | end 57 | 58 | vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { 59 | group = augroup, 60 | buffer = bufnr, 61 | callback = function() 62 | vim.api.nvim_buf_clear_namespace(bufnr, hlns, 0, -1) 63 | if hover == true then 64 | start_timer() 65 | end 66 | end, 67 | }) 68 | 69 | vim.api.nvim_create_autocmd("BufUnload", { 70 | group = augroup, 71 | buffer = bufnr, 72 | callback = clear_timer, 73 | }) 74 | 75 | start_timer() 76 | end 77 | M.rustowl_cursor = function(...) 78 | local args = { ... } 79 | local bufnr = args[1] or vim.api.nvim_get_current_buf() 80 | show_rustowl(bufnr) 81 | end 82 | 83 | local me = require("kide.melspconfig") 84 | M.config = { 85 | name = "rustowl", 86 | cmd = { "cargo", "owlsp" }, 87 | root_dir = vim.fs.root(0, { ".git", "Cargo.toml" }), 88 | filetypes = { "rust" }, 89 | 90 | on_attach = function(client, bufnr) 91 | rustowl_on_attach(false, client, bufnr, 2000) 92 | me.on_attach(client, bufnr) 93 | end, 94 | on_init = me.on_init, 95 | capabilities = me.capabilities({}), 96 | } 97 | return M 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NVIM IDE 2 | 3 | 支持 `Java`, `Python`, `Rust` 语言的 `LSP`, `DAP` 配置 4 | 5 | ## 安装 6 | 7 | ### Linux, Mac 8 | 9 | ```sh 10 | cd ~/.config 11 | git clone https://github.com/JavaHello/nvim.git 12 | ``` 13 | 14 | ## 依赖 15 | 16 | - [ripgrep](https://github.com/BurntSushi/ripgrep) 17 | - [fd](https://github.com/sharkdp/fd) 18 | - [yazi](https://github.com/sxyazi/yazi) 19 | - [JDK](https://openjdk.org/) 8/17+ 20 | - [maven](https://maven.apache.org/) 21 | - [nodejs](https://nodejs.org/en) 22 | - [yarn](https://yarnpkg.com/) 23 | 24 | 其他依赖可选安装,使用 [mason.nvim](https://github.com/williamboman/mason.nvim) 25 | 26 | > 此配置在 Linux, Mac 系统上长期使用 27 | 28 | ## 快捷键 29 | 30 | | 功能 | 模式 | 按键 | 31 | | :-----------------------------: | :------------------: | :-----------------------: | 32 | | 文件管理 | `Normal` | `e` | 33 | | 文件搜索 | `Normal` | `ff` | 34 | | 全局搜索 | `Normal` or `Visual` | `fw` | 35 | | Git 操作 | `Command` | `:Git` | 36 | | Outline | `Normal` | `o` | 37 | | 查看实现 | `Normal` | `gi` | 38 | | 查看引用 | `Normal` | `gr` | 39 | | 查看声明 | `Normal` | `gd` | 40 | | 格式化(LSP 提供支持) | `Normal` or `Visual` | `` | 41 | | 重命名 | `Normal` | `rn` | 42 | | Code Action | `Normal` | `ca` | 43 | | Debug | `Normal` | `:DapContinue` | 44 | | 断点 | `Normal` | `db` | 45 | | 内置终端 | `Command` | `` | 46 | | Java: Junit Test Method | `Normal` | `dm` | 47 | | Java: Junit Test Class | `Normal` | `dc` | 48 | | Run Last | `Normal` | `dl` | 49 | | Java: 更新项目配置 | `Command` | `:JdtUpdateConfig` | 50 | | Java: 刷新 Main 方法 Debug 配置 | `Command` | `:JdtRefreshDebugConfigs` | 51 | | Java: 预览项目依赖 | `Command` | `:JavaProjects` | 52 | 53 | 更多配置参考 [mappings](./lua/mappings.lua) 文件 54 | 55 | ## Java 配置 56 | 57 | - `maven pom.xml` 自动补全(目前需要[手动打包](https://www.bilibili.com/video/BV12N4y1f7Bh/)) 58 | 59 | - [NVIM 打造 Java IDE](https://javahello.github.io/dev/tools/NVIM-LSP-Java-IDE-vscode.html) 更新了配置,全部使用 vscode 扩展,简化安装步骤。 60 | 61 | - [手动编译 Java 开发环境](https://github.com/JavaHello/nvim/wiki) 这里提供了一个编译脚本 62 | 63 | ### Spring Boot LS 64 | 65 | - 依赖 vscode 插件 [VScode Spring Boot](https://marketplace.visualstudio.com/items?itemName=vmware.vscode-spring-boot) 66 | - [x] 查找`symbols`,`bean`定义,`bean`引用,`bean`实现等。 67 | - [x] `application.properties`, `application.yml` 文件提示 68 | 69 | ## GPT 功能 70 | 71 | 依赖 `DeepSeek` API 72 | 73 | - 命令 `:GptChat` 开启对话窗, `` 发送请求 74 | - 命令 `:TransXXX` 翻译文本 75 | - 在 `git` 提交窗口,快捷键 `cm` 生成 `git` 提交消息 76 | -------------------------------------------------------------------------------- /lua/kide/tools/plantuml.lua: -------------------------------------------------------------------------------- 1 | local utils = require("kide.tools") 2 | local plantuml_args_complete = utils.command_args_complete 3 | local M = {} 4 | M.config = {} 5 | 6 | local function plantuml_jar(default_jar) 7 | return vim.env["PLANTUML_JAR"] or default_jar 8 | end 9 | M.config.jar_path = plantuml_jar("/opt/software/puml/plantuml.jar") 10 | M.config.defaultTo = "svg" 11 | M.types = {} 12 | M.types["-tpng"] = "png" 13 | M.types["-tsvg"] = "svg" 14 | M.types["-teps"] = "eps" 15 | M.types["-tpdf"] = "pdf" 16 | M.types["-tvdx"] = "vdx" 17 | M.types["-txmi"] = "xmi" 18 | M.types["-tscxml"] = "scxml" 19 | M.types["-thtml"] = "html" 20 | M.types["-ttxt"] = "atxt" 21 | M.types["-tutxt"] = "utxt" 22 | M.types["-tlatex"] = "latex" 23 | M.types["-tlatex:nopreamble"] = "latex" 24 | 25 | local complete_list = (function() 26 | local cl = {} 27 | for k, _ in pairs(M.types) do 28 | table.insert(cl, k) 29 | end 30 | return cl 31 | end)() 32 | 33 | local function to_type() 34 | return "-t" .. M.config.defaultTo 35 | end 36 | 37 | local function exec(opt) 38 | if not vim.fn.filereadable(M.config.jar_path) then 39 | vim.notify("Plantuml: 没有文件 " .. M.config.jar_path, vim.log.levels.ERROR) 40 | return 41 | end 42 | if not vim.fn.executable("java") then 43 | vim.notify("Plantuml: 没有 java 环境", vim.log.levels.ERROR) 44 | return 45 | end 46 | local out_type = vim.tbl_filter(function(item) 47 | if vim.startswith(item, "-t") then 48 | return true 49 | end 50 | return false 51 | end, opt.args) 52 | if not out_type or vim.tbl_count(out_type) == 0 then 53 | local ot = to_type() 54 | opt.args = { ot } 55 | out_type = { ot } 56 | end 57 | local ot = M.types[out_type[1]] 58 | if not ot then 59 | vim.notify("Plantuml: 不支持的格式 " .. out_type[1], vim.log.levels.ERROR) 60 | return 61 | end 62 | 63 | local p = vim.fn.expand("%:p:h") 64 | table.insert(opt.args, 1, "-jar") 65 | table.insert(opt.args, 2, M.config.jar_path) 66 | table.insert(opt.args, opt.file) 67 | table.insert(opt.args, "-o") 68 | table.insert(opt.args, p) 69 | local cmd = opt.args 70 | table.insert(cmd, 1, "java") 71 | local sid = require("kide").timer_stl_status("") 72 | local result = vim.system(cmd):wait() 73 | require("kide").clean_stl_status(sid, result.code) 74 | if result.code == 0 then 75 | vim.notify("Plantuml: export success", vim.log.levels.INFO) 76 | else 77 | vim.notify("Plantuml: export error", vim.log.levels.ERROR) 78 | end 79 | end 80 | 81 | local function init() 82 | local group = vim.api.nvim_create_augroup("plantuml_export", { clear = true }) 83 | vim.api.nvim_create_autocmd({ "FileType" }, { 84 | group = group, 85 | pattern = { "plantuml" }, 86 | desc = "Export Plantuml file", 87 | callback = function(o) 88 | vim.api.nvim_buf_create_user_command(o.buf, "Plantuml", function(opts) 89 | exec({ 90 | args = opts.fargs, 91 | file = o.file, 92 | }) 93 | end, { 94 | nargs = "*", 95 | complete = plantuml_args_complete(complete_list, { single = true }), 96 | }) 97 | end, 98 | }) 99 | end 100 | 101 | M.setup = function(config) 102 | if config then 103 | M.config = vim.tbl_deep_extend("force", M.config, config) 104 | end 105 | init() 106 | end 107 | return M 108 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- 不保存 jumps 列表 '0 2 | vim.opt.shada = "!,'0,<50,s10,h" 3 | vim.opt_global.jumpoptions = "stack" 4 | vim.opt_global.encoding = "UTF-8" 5 | vim.opt.fileencoding = "UTF-8" 6 | vim.g.mapleader = " " 7 | vim.g.maplocalleader = " " 8 | local g = vim.g 9 | g.loaded_node_provider = 0 10 | g.loaded_python3_provider = 0 11 | g.loaded_perl_provider = 0 12 | g.loaded_ruby_provider = 0 13 | 14 | -- 禁用内置插件 15 | g.loaded_tohtml_plugin = 1 16 | 17 | g.loaded_netrw = 1 18 | g.loaded_netrwPlugin = 1 19 | 20 | vim.opt_global.grepprg = "rg --vimgrep --no-heading --smart-case" 21 | vim.opt_global.grepformat = "%f:%l:%c:%m,%f:%l:%m" 22 | 23 | local x = vim.diagnostic.severity 24 | vim.diagnostic.config({ 25 | virtual_text = { prefix = "" }, 26 | signs = { text = { [x.ERROR] = "󰅙", [x.WARN] = "", [x.INFO] = "󰋼", [x.HINT] = "󰌵" } }, 27 | float = { 28 | border = "rounded", 29 | }, 30 | }) 31 | 32 | vim.fn.sign_define("DapBreakpoint", { text = "", texthl = "Debug", linehl = "", numhl = "" }) 33 | vim.fn.sign_define("DapBreakpointCondition", { text = "", texthl = "Debug", linehl = "", numhl = "" }) 34 | vim.fn.sign_define("DapLogPoint", { text = "", texthl = "Debug", linehl = "", numhl = "" }) 35 | vim.fn.sign_define("DapStopped", { text = "", texthl = "Debug", linehl = "", numhl = "" }) 36 | vim.fn.sign_define("DapBreakpointRejected", { text = "", texthl = "Debug", linehl = "", numhl = "" }) 37 | 38 | if vim.g.neovide then 39 | vim.g.neovide_input_macos_option_key_is_meta = 'only_left' 40 | vim.g.neovide_cursor_vfx_mode = "railgun" 41 | vim.opt_global.guifont = vim.env["NVIM_GUI_FONT"] or "CaskaydiaMono Nerd Font Mono:h15" 42 | vim.g.neovide_fullscreen = true 43 | vim.g.transparency = 1.0 44 | local alpha = function() 45 | return string.format("%x", math.floor(255 * (vim.g.transparency or 0.8))) 46 | end 47 | -- g:neovide_transparency should be 0 if you want to unify transparency of content and title bar. 48 | vim.g.neovide_opacity = 1.0 49 | vim.g.neovide_background_color = "#282828" .. alpha() 50 | 51 | vim.g.neovide_floating_blur_amount_x = 2.0 52 | vim.g.neovide_floating_blur_amount_y = 2.0 53 | 54 | vim.g.neovide_hide_mouse_when_typing = true 55 | 56 | vim.g.neovide_profiler = false 57 | vim.g.neovide_padding_top = 0 58 | vim.g.neovide_padding_bottom = 0 59 | vim.g.neovide_padding_right = 0 60 | vim.g.neovide_padding_left = 0 61 | end 62 | require("global") 63 | require("experimental") 64 | 65 | require("options") 66 | -- Bootstrap lazy.nvim 67 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 68 | if not (vim.uv or vim.loop).fs_stat(lazypath) then 69 | local lazyrepo = "https://github.com/folke/lazy.nvim.git" 70 | local out = vim.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }):wait() 71 | if out.code ~= 0 then 72 | vim.api.nvim_echo({ 73 | { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, 74 | { out.stdout, "WarningMsg" }, 75 | { "\nPress any key to exit..." }, 76 | }, true, {}) 77 | vim.fn.getchar() 78 | os.exit(1) 79 | end 80 | end 81 | vim.opt.rtp:prepend(lazypath) 82 | require("lazy").setup({ 83 | defaults = { 84 | lazy = false, 85 | }, 86 | spec = { 87 | { import = "plugins" }, 88 | }, 89 | install = { colorscheme = { "gruvboxl" } }, 90 | checker = { enabled = false }, 91 | rocks = { 92 | enabled = false, 93 | }, 94 | }) 95 | 96 | vim.o.background = "dark" 97 | vim.cmd.colorscheme("gruvboxl") 98 | require("mappings") 99 | require("autocmds") 100 | -------------------------------------------------------------------------------- /lua/autocmds.lua: -------------------------------------------------------------------------------- 1 | local autocmd = vim.api.nvim_create_autocmd 2 | local function augroup(name) 3 | return vim.api.nvim_create_augroup("kide" .. name, { clear = true }) 4 | end 5 | -- Highlight on yank 6 | autocmd({ "TextYankPost" }, { 7 | group = augroup("highlight_yank"), 8 | callback = function() 9 | vim.highlight.on_yank() 10 | end, 11 | }) 12 | 13 | -- https://nvchad.com/docs/recipes 14 | autocmd("BufReadPost", { 15 | pattern = "*", 16 | callback = function() 17 | local line = vim.fn.line("'\"") 18 | if 19 | line > 1 20 | and line <= vim.fn.line("$") 21 | and vim.bo.filetype ~= "commit" 22 | and vim.fn.index({ "xxd", "gitrebase" }, vim.bo.filetype) == -1 23 | then 24 | vim.cmd('normal! g`"') 25 | end 26 | end, 27 | }) 28 | 29 | -- close some filetypes with 30 | autocmd("FileType", { 31 | group = augroup("close_with_q"), 32 | pattern = { 33 | "PlenaryTestPopup", 34 | "help", 35 | "lspinfo", 36 | "man", 37 | "notify", 38 | "qf", 39 | "spectre_panel", 40 | "startuptime", 41 | "tsplayground", 42 | "checkhealth", 43 | "fugitive", 44 | "git", 45 | "dbui", 46 | "dbout", 47 | "httpResult", 48 | "dap-repl", 49 | }, 50 | callback = function(event) 51 | vim.bo[event.buf].buflisted = false 52 | vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) 53 | end, 54 | }) 55 | 56 | autocmd({ "BufReadCmd" }, { 57 | group = augroup("git_close_with_q"), 58 | pattern = "fugitive://*", 59 | callback = function(event) 60 | vim.bo[event.buf].buflisted = false 61 | vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) 62 | end, 63 | }) 64 | 65 | autocmd("FileType", { 66 | group = augroup("close_with_q_bd"), 67 | pattern = { 68 | "oil", 69 | "DressingSelect", 70 | "dap-*", 71 | }, 72 | callback = function(event) 73 | vim.keymap.set("n", "q", "bd", { buffer = event.buf, silent = true }) 74 | end, 75 | }) 76 | 77 | autocmd({ "BufRead", "BufNewFile" }, { 78 | group = augroup("spell"), 79 | pattern = "*.md", 80 | command = "setlocal spell spelllang=en_us,cjk", 81 | }) 82 | 83 | -- outline 84 | autocmd("FileType", { 85 | group = augroup("OUTLINE"), 86 | pattern = { 87 | "OUTLINE", 88 | }, 89 | callback = function(_) 90 | vim.api.nvim_set_option_value("signcolumn", "no", { win = vim.api.nvim_get_current_win() }) 91 | end, 92 | }) 93 | 94 | -- LSP 95 | local function lsp_command(bufnr) 96 | vim.api.nvim_buf_create_user_command(bufnr, "LspIncomingCalls", vim.lsp.buf.incoming_calls, { 97 | desc = "Lsp incoming calls", 98 | nargs = 0, 99 | }) 100 | vim.api.nvim_buf_create_user_command(bufnr, "LspOutgoingCalls", vim.lsp.buf.outgoing_calls, { 101 | desc = "Lsp outgoing calls", 102 | nargs = 0, 103 | }) 104 | end 105 | autocmd("LspAttach", { 106 | group = augroup("lsp_a"), 107 | callback = function(args) 108 | local bufnr = args.buf 109 | lsp_command(bufnr) 110 | end, 111 | }) 112 | 113 | autocmd("TermOpen", { 114 | group = augroup("close_with_q_term"), 115 | pattern = "*", 116 | callback = function(event) 117 | -- mac 下 t 模式执行 bd! dap 终端会导致 nvim 退出 118 | -- 这里使用 n 模式下执行 119 | if vim.b[event.buf].q_close == nil or vim.b[event.buf].q_close == true then 120 | vim.keymap.set("n", "q", "bd!", { buffer = event.buf, silent = true }) 121 | end 122 | end, 123 | }) 124 | 125 | require("kide.melspconfig").init_lsp() 126 | -------------------------------------------------------------------------------- /lsp/copilot.lua: -------------------------------------------------------------------------------- 1 | ---@see https://github.com/neovim/nvim-lspconfig/blob/master/lsp/copilot.lua#L106 2 | ---@param bufnr integer, 3 | ---@param client vim.lsp.Client 4 | local function sign_in(bufnr, client) 5 | client:request( 6 | ---@diagnostic disable-next-line: param-type-mismatch 7 | "signIn", 8 | vim.empty_dict(), 9 | function(err, result) 10 | if err then 11 | vim.notify(err.message, vim.log.levels.ERROR) 12 | return 13 | end 14 | if result.command then 15 | local code = result.userCode 16 | local command = result.command 17 | vim.fn.setreg("+", code) 18 | vim.fn.setreg("*", code) 19 | local continue = vim.fn.confirm( 20 | "Copied your one-time code to clipboard.\n" .. "Open the browser to complete the sign-in process?", 21 | "&Yes\n&No" 22 | ) 23 | if continue == 1 then 24 | client:exec_cmd(command, { bufnr = bufnr }, function(cmd_err, cmd_result) 25 | if cmd_err then 26 | vim.notify(err.message, vim.log.levels.ERROR) 27 | return 28 | end 29 | if cmd_result.status == "OK" then 30 | vim.notify("Signed in as " .. cmd_result.user .. ".") 31 | end 32 | end) 33 | end 34 | end 35 | 36 | if result.status == "PromptUserDeviceFlow" then 37 | vim.notify("Enter your one-time code " .. result.userCode .. " in " .. result.verificationUri) 38 | elseif result.status == "AlreadySignedIn" then 39 | vim.notify("Already signed in as " .. result.user .. ".") 40 | end 41 | end 42 | ) 43 | end 44 | 45 | ---@param client vim.lsp.Client 46 | local function sign_out(_, client) 47 | client:request( 48 | ---@diagnostic disable-next-line: param-type-mismatch 49 | "signOut", 50 | vim.empty_dict(), 51 | function(err, result) 52 | if err then 53 | vim.notify(err.message, vim.log.levels.ERROR) 54 | return 55 | end 56 | if result.status == "NotSignedIn" then 57 | vim.notify("Not signed in.") 58 | end 59 | end 60 | ) 61 | end 62 | return { 63 | cmd = { 64 | "copilot-language-server", 65 | "--stdio", 66 | }, 67 | root_markers = { ".git" }, 68 | init_options = { 69 | editorInfo = { 70 | name = "Neovim", 71 | version = tostring(vim.version()), 72 | }, 73 | editorPluginInfo = { 74 | name = "Neovim", 75 | version = tostring(vim.version()), 76 | }, 77 | }, 78 | settings = { 79 | telemetry = { 80 | telemetryLevel = "all", 81 | }, 82 | }, 83 | on_attach = function(client, bufnr) 84 | vim.api.nvim_buf_create_user_command(bufnr, "LspCopilotSignIn", function() 85 | sign_in(bufnr, client) 86 | end, { desc = "Sign in Copilot with GitHub" }) 87 | vim.api.nvim_buf_create_user_command(bufnr, "LspCopilotSignOut", function() 88 | sign_out(bufnr, client) 89 | end, { desc = "Sign out Copilot with GitHub" }) 90 | 91 | if client:supports_method(vim.lsp.protocol.Methods.textDocument_inlineCompletion, bufnr) then 92 | if vim.lsp.inline_completion then 93 | vim.lsp.inline_completion.enable(true, { bufnr = bufnr }) 94 | 95 | vim.keymap.set( 96 | "i", 97 | "", 98 | vim.lsp.inline_completion.get, 99 | { desc = "LSP: accept inline completion", buffer = bufnr } 100 | ) 101 | vim.keymap.set( 102 | "i", 103 | "", 104 | vim.lsp.inline_completion.select, 105 | { desc = "LSP: switch inline completion", buffer = bufnr } 106 | ) 107 | end 108 | end 109 | end, 110 | } 111 | -------------------------------------------------------------------------------- /lua/kide/tools/maven.lua: -------------------------------------------------------------------------------- 1 | local utils = require("kide.tools") 2 | local M = { 3 | mvn = vim.fn.exepath("mvn"), 4 | } 5 | 6 | local function maven_settings() 7 | if vim.fn.filereadable(vim.fn.expand("~/.m2/settings.xml")) == 1 then 8 | return vim.fn.expand("~/.m2/settings.xml") 9 | end 10 | local maven_home = vim.env["MAVEN_HOME"] 11 | if maven_home and vim.fn.filereadable(maven_home .. "/conf/settings.xml") then 12 | return maven_home .. "/conf/settings.xml" 13 | end 14 | end 15 | 16 | M.get_maven_settings = function() 17 | return vim.env["MAVEN_SETTINGS_XML"] or maven_settings() 18 | end 19 | 20 | M.is_pom_file = function(file) 21 | return vim.endswith(file, "pom.xml") 22 | end 23 | 24 | local exec = function(cmd, args) 25 | local opt = vim.tbl_deep_extend("force", cmd, {}) 26 | local s = M.get_maven_settings() 27 | if s then 28 | table.insert(opt, "-s") 29 | table.insert(opt, s) 30 | end 31 | local p = vim.fn.expand("%") 32 | if M.is_pom_file(p) then 33 | table.insert(opt, "-f") 34 | table.insert(opt, p) 35 | end 36 | if args and vim.trim(args) ~= "" then 37 | vim.list_extend(opt, vim.split(args, " ")) 38 | end 39 | require("kide.term").toggle(opt) 40 | end 41 | local function create_command(buf, name, cmd, complete) 42 | vim.api.nvim_buf_create_user_command(buf, name, function(opts) 43 | if type(cmd) == "function" then 44 | cmd = cmd(opts) 45 | end 46 | if cmd == nil then 47 | return 48 | end 49 | exec(cmd, opts.args) 50 | end, { 51 | nargs = "*", 52 | complete = complete, 53 | }) 54 | end 55 | 56 | local maven_args_complete = utils.command_args_complete 57 | 58 | M.maven_command = function(buf) 59 | -- 判断为 java 文件 60 | if vim.api.nvim_get_option_value("filetype", { buf = buf }) == "java" then 61 | create_command(buf, "MavenExecJava", function(_) 62 | local filename = vim.fn.expand("%:p") 63 | filename = string.gsub(filename, "^[%-/%w%s]*%/src%/main%/java%/", "") 64 | filename = string.gsub(filename, "[/\\]", ".") 65 | filename = string.gsub(filename, "%.java$", "") 66 | return { "mvn", 'exec:java -Dexec.mainClass="' .. filename .. '"' } 67 | end, nil) 68 | end 69 | create_command( 70 | buf, 71 | "MavenCompile", 72 | { "mvn", "clean", "compile" }, 73 | maven_args_complete({ "test-compile" }, { model = "multiple" }) 74 | ) 75 | create_command( 76 | buf, 77 | "MavenInstall", 78 | { "mvn", "clean", "install" }, 79 | maven_args_complete({ "-DskipTests", "-Dmaven.test.skip=true" }, { model = "single" }) 80 | ) 81 | create_command( 82 | buf, 83 | "MavenPackage", 84 | { "mvn", "clean", "package" }, 85 | maven_args_complete({ "-DskipTests", "-Dmaven.test.skip=true" }, { model = "single" }) 86 | ) 87 | create_command( 88 | buf, 89 | "MavenDependencyTree", 90 | { "mvn", "dependency:tree" }, 91 | maven_args_complete({ "-Doutput=.dependency.txt" }, { model = "single" }) 92 | ) 93 | create_command(buf, "MavenDependencyAnalyzeDuplicate", { "mvn", "dependency:analyze-duplicate" }, nil) 94 | create_command(buf, "MavenDependencyAnalyzeOnly", { "mvn", "dependency:analyze-only", "-Dverbose" }, nil) 95 | create_command(buf, "MavenDownloadSources", { "mvn", "dependency:sources", "-DdownloadSources=true" }) 96 | create_command(buf, "MavenTest", { "mvn", "test" }, maven_args_complete({ "-Dtest=" }, { model = "single" })) 97 | end 98 | 99 | M.setup = function() 100 | local group = vim.api.nvim_create_augroup("kide_jdtls_java_maven", { clear = true }) 101 | vim.api.nvim_create_autocmd({ "FileType" }, { 102 | group = group, 103 | pattern = { "xml", "java" }, 104 | desc = "maven_command", 105 | callback = function(e) 106 | if vim.endswith(e.file, "pom.xml") or vim.endswith(e.file, ".java") then 107 | M.maven_command(e.buf) 108 | end 109 | end, 110 | }) 111 | 112 | vim.api.nvim_create_user_command("Maven", function(opts) 113 | exec({ "mvn" }, opts.args) 114 | end, { 115 | nargs = "*", 116 | complete = maven_args_complete({ 117 | "clean", 118 | "compile", 119 | "test-compile", 120 | "verify", 121 | "package", 122 | "install", 123 | "deploy", 124 | }, { model = "multiple" }), 125 | }) 126 | end 127 | return M 128 | -------------------------------------------------------------------------------- /lua/options.lua: -------------------------------------------------------------------------------- 1 | local fn = vim.fn 2 | local opt = vim.opt 3 | local o = vim.o 4 | 5 | vim.opt.statusline = "%!v:lua.require('kide.stl').statusline()" 6 | 7 | -- disable nvim intro 8 | opt.shortmess:append("sI") 9 | vim.opt.termguicolors = true 10 | vim.opt.number = true 11 | vim.opt.relativenumber = true 12 | vim.opt.numberwidth = 2 13 | vim.opt.signcolumn = "yes" 14 | 15 | o.undofile = true 16 | opt.fillchars = { eob = " " } 17 | 18 | -- go to previous/next line with h,l,left arrow and right arrow 19 | -- when cursor reaches end/beginning of line 20 | -- opt.whichwrap:append "<>[]hl" 21 | 22 | vim.opt.title = true 23 | vim.opt.exrc = true 24 | vim.opt.secure = false 25 | vim.opt.ttyfast = true 26 | vim.opt.scrollback = 100000 27 | 28 | -- 高亮所在行 29 | vim.opt.cursorline = true 30 | 31 | vim.opt.clipboard = "unnamedplus" 32 | vim.opt.cursorlineopt = "number,line" 33 | -- Indenting 34 | vim.opt.expandtab = true 35 | vim.opt.shiftwidth = 2 36 | vim.opt.smartindent = true 37 | vim.opt.tabstop = 2 38 | vim.opt.softtabstop = 2 39 | 40 | vim.opt.ignorecase = true 41 | vim.opt.smartcase = true 42 | 43 | vim.opt.showmode = true 44 | 45 | -- 菜单最多显示20行 46 | vim.opt.pumheight = 20 47 | 48 | vim.opt.updatetime = 300 49 | vim.opt.timeout = true 50 | vim.opt.timeoutlen = 450 51 | 52 | vim.opt.confirm = true 53 | 54 | -- 当文件被外部程序修改时,自动加载 55 | vim.opt.autoread = true 56 | 57 | -- split window 从下边和右边出现 58 | vim.opt.splitbelow = false 59 | vim.opt.splitright = true 60 | 61 | vim.opt.foldlevelstart = 99 62 | vim.opt.foldmethod = "expr" 63 | vim.opt.foldexpr = "nvim_treesitter#foldexpr()" 64 | vim.opt.foldnestmax = 10 65 | -- 默认不要折叠 66 | vim.opt.foldenable = false 67 | vim.opt.foldlevel = 1 68 | 69 | -- toggle invisible characters 70 | vim.opt.list = true 71 | -- vim.opt.listchars = { 72 | -- tab = "→ ", 73 | -- eol = "¬", 74 | -- trail = "⋅", 75 | -- extends = "❯", 76 | -- precedes = "❮", 77 | -- } 78 | 79 | -- jk移动时光标下上方保留8行 80 | vim.opt.scrolloff = 3 81 | vim.opt.sidescrolloff = 3 82 | 83 | vim.opt.linespace = 0 84 | 85 | -- quickfix 美化 86 | function _G.qftf(info) 87 | local items 88 | local ret = {} 89 | if info.quickfix == 1 then 90 | items = fn.getqflist({ id = info.id, items = 0 }).items 91 | else 92 | items = fn.getloclist(info.winid, { id = info.id, items = 0 }).items 93 | end 94 | local limit = 44 95 | local fnameFmt1, fnameFmt2 = "%-" .. limit .. "s", "…%." .. (limit - 1) .. "s" 96 | local validFmt = "%s │%5d:%-3d│%s %s" 97 | local fmt = true 98 | for i = info.start_idx, info.end_idx do 99 | local e = items[i] 100 | local fname = "" 101 | local str 102 | if e.valid == 1 then 103 | if e.bufnr > 0 then 104 | fname = fn.bufname(e.bufnr) 105 | fmt = true 106 | if fname == "" then 107 | fname = "[No Name]" 108 | else 109 | fname = fname:gsub("^" .. vim.env.HOME, "~") 110 | end 111 | if vim.startswith(fname, "jdt://") then 112 | local jar, pkg, class = fname:match("^jdt://contents/([^/]+)/([^/]+)/(.+)?") 113 | fname = "󰧮 " .. class .. "  " .. pkg .. "  " .. jar 114 | 115 | -- 加载 jdt:// 文件 116 | -- if vim.fn.bufloaded(e.bufnr) == 0 then 117 | -- vim.fn.bufload(e.bufnr) 118 | -- end 119 | fmt = false 120 | else 121 | fname = vim.fn.fnamemodify(fname, ":.") 122 | end 123 | -- char in fname may occur more than 1 width, ignore this issue in order to keep performance 124 | if fmt then 125 | if #fname <= limit then 126 | fname = fnameFmt1:format(fname) 127 | else 128 | fname = fnameFmt2:format(fname:sub(1 - limit)) 129 | end 130 | end 131 | end 132 | local lnum = e.lnum > 99999 and -1 or e.lnum 133 | local col = e.col > 999 and -1 or e.col 134 | local qtype = e.type == "" and "" or " " .. e.type:sub(1, 1):upper() 135 | str = validFmt:format(fname, lnum, col, qtype, e.text) 136 | else 137 | str = e.text 138 | end 139 | table.insert(ret, str) 140 | end 141 | return ret 142 | end 143 | 144 | vim.o.qftf = "{info -> v:lua._G.qftf(info)}" 145 | 146 | vim.opt.laststatus = 3 147 | vim.opt.splitkeep = "screen" 148 | 149 | -- lsp 时常出现 swapfile 冲突提示, 关闭 swapfile 150 | vim.opt.swapfile = false 151 | vim.opt.backup = false 152 | 153 | -- see noice 154 | -- vim.opt.cmdheight=0 155 | -- 1 只有多个 tab 时显示 156 | -- 2 一直显示(99% 情况下不需要) 157 | vim.opt.showtabline = 1 158 | vim.opt.tabline = "%!v:lua.require('kide.stl').tabline()" 159 | -------------------------------------------------------------------------------- /lua/kide/gpt/translate.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local gpt_provide = require("kide.gpt.provide") 4 | ---@type gpt.Client 5 | local client = nil 6 | 7 | ---@class kai.tools.TranslateRequest 8 | ---@field text string 9 | ---@field from string 10 | ---@field to string 11 | 12 | 13 | ---@param request kai.tools.TranslateRequest 14 | local function trans_system_prompt(request) 15 | local from = request.from 16 | if request.from == "auto" then 17 | return "你会得到一个需要你检测语言的文本, 将他翻译为" 18 | .. request.to 19 | .. "。我只需要你翻译不要解释或回答我提供的文本" 20 | end 21 | return "你会得到一个" 22 | .. from 23 | .. "文本, 将他翻译为" 24 | .. request.to 25 | .. "。我只需要你翻译不要解释或回答我提供的文本" 26 | end 27 | 28 | ---@param request kai.tools.TranslateRequest 29 | ---@param callback fun(data: string) 30 | function M.translate(request, callback) 31 | local messages = { 32 | { 33 | content = "", 34 | role = "system", 35 | }, 36 | { 37 | content = "Hi", 38 | role = "user", 39 | }, 40 | } 41 | messages[1].content = trans_system_prompt(request) 42 | messages[2].content = request.text 43 | client = gpt_provide.new_client("translate") 44 | client:request(messages, callback) 45 | end 46 | 47 | local max_width = 120 48 | local max_height = 40 49 | 50 | M.translate_float = function(request) 51 | local codebuf = vim.api.nvim_get_current_buf() 52 | local ctext = vim.fn.split(request.text, "\n") 53 | local width = math.min(max_width, vim.fn.strdisplaywidth(ctext[1])) 54 | for _, line in ipairs(ctext) do 55 | local l = vim.fn.strdisplaywidth(line) 56 | if l > width and l < max_width then 57 | width = l 58 | end 59 | end 60 | local height = math.min(max_height, #ctext) 61 | 62 | local opts = { 63 | relative = "cursor", 64 | row = 1, -- 相对于光标位置的行偏移 65 | col = 0, -- 相对于光标位置的列偏移 66 | width = width, -- 窗口的宽度 67 | height = height, -- 窗口的高度 68 | style = "minimal", -- 最小化样式 69 | border = "rounded", -- 窗口边框样式 70 | } 71 | local buf = vim.api.nvim_create_buf(false, true) 72 | vim.bo[buf].buftype = "nofile" 73 | vim.bo[buf].bufhidden = "wipe" 74 | vim.bo[buf].buflisted = false 75 | vim.bo[buf].swapfile = false 76 | local win = vim.api.nvim_open_win(buf, true, opts) 77 | vim.wo[win].number = false -- 不显示行号 78 | vim.wo[win].wrap = true 79 | if vim.api.nvim_buf_is_valid(codebuf) then 80 | local filetype = vim.bo[codebuf].filetype 81 | if filetype == "markdown" then 82 | vim.bo[buf].filetype = "markdown" 83 | end 84 | end 85 | 86 | local closed = false 87 | vim.keymap.set("n", "q", function() 88 | closed = true 89 | vim.api.nvim_win_close(win, true) 90 | end, { noremap = true, silent = true, buffer = buf }) 91 | 92 | vim.api.nvim_create_autocmd("BufWipeout", { 93 | buffer = buf, 94 | callback = function() 95 | closed = true 96 | pcall(vim.api.nvim_win_close, win, true) 97 | if client then 98 | client:close() 99 | end 100 | end, 101 | }) 102 | vim.api.nvim_create_autocmd("WinClosed", { 103 | buffer = buf, 104 | callback = function() 105 | closed = true 106 | if client then 107 | client:close() 108 | end 109 | end, 110 | }) 111 | vim.api.nvim_create_autocmd("WinLeave", { 112 | buffer = buf, 113 | callback = function() 114 | closed = true 115 | pcall(vim.api.nvim_win_close, win, true) 116 | if client then 117 | client:close() 118 | end 119 | end, 120 | }) 121 | 122 | local curlinelen = 0 123 | local count_line = 1 124 | ---@param opt gpt.Event 125 | local callback = function(opt) 126 | local data = opt.data 127 | local done = opt.done 128 | if closed then 129 | client:close() 130 | return 131 | end 132 | if done then 133 | vim.bo[buf].readonly = true 134 | vim.bo[buf].modifiable = false 135 | return 136 | end 137 | 138 | local put_data = {} 139 | if vim.api.nvim_buf_is_valid(buf) then 140 | if data and data:match("\n") then 141 | put_data = vim.split(data, "\n") 142 | else 143 | put_data = { data } 144 | end 145 | for i, v in pairs(put_data) do 146 | if i > 1 then 147 | curlinelen = 0 148 | count_line = count_line + 1 149 | end 150 | curlinelen = curlinelen + vim.fn.strdisplaywidth(v) 151 | if curlinelen > width then 152 | if curlinelen < max_width or width ~= max_width then 153 | width = math.min(curlinelen, max_width) 154 | if vim.api.nvim_win_is_valid(win) then 155 | vim.api.nvim_win_set_width(win, width) 156 | end 157 | else 158 | curlinelen = 0 159 | count_line = count_line + 1 160 | end 161 | end 162 | if count_line > height and count_line <= max_height then 163 | height = count_line 164 | if vim.api.nvim_win_is_valid(win) then 165 | vim.api.nvim_win_set_height(win, height) 166 | end 167 | end 168 | end 169 | vim.api.nvim_put(put_data, "c", true, true) 170 | end 171 | end 172 | M.translate(request, callback) 173 | end 174 | 175 | return M 176 | -------------------------------------------------------------------------------- /lua/kide/gpt/provide/openrouter.lua: -------------------------------------------------------------------------------- 1 | local sse = require("kide.http.sse") 2 | local max_tokens = 4096 * 2 3 | local code_json = { 4 | messages = { 5 | { 6 | content = "帮我生成一个快速排序", 7 | role = "user", 8 | }, 9 | { 10 | content = "```python\n", 11 | prefix = true, 12 | role = "assistant", 13 | }, 14 | }, 15 | model = "google/gemini-2.0-flash-001", 16 | max_tokens = max_tokens, 17 | stop = "```", 18 | stream = true, 19 | } 20 | 21 | local chat_json = { 22 | messages = { 23 | { 24 | content = "", 25 | role = "system", 26 | }, 27 | }, 28 | model = "google/gemini-2.0-flash-001", 29 | stream = true, 30 | } 31 | 32 | local reasoner_json = { 33 | messages = { 34 | }, 35 | model = "deepseek/deepseek-r1:free", 36 | stream = true, 37 | } 38 | 39 | local commit_json = { 40 | messages = { 41 | { 42 | content = "", 43 | role = "system", 44 | }, 45 | { 46 | content = "Hi", 47 | role = "user", 48 | }, 49 | }, 50 | model = "google/gemini-2.0-flash-001", 51 | stream = true, 52 | } 53 | 54 | local translate_json = { 55 | messages = { 56 | { 57 | content = "", 58 | role = "system", 59 | }, 60 | { 61 | content = "Hi", 62 | role = "user", 63 | }, 64 | }, 65 | model = "google/gemini-2.0-flash-001", 66 | stream = true, 67 | } 68 | 69 | ---@class gpt.OpenrouterClient : gpt.Client 70 | ---@field base_url string 71 | ---@field api_key string 72 | ---@field type string 73 | ---@field payload table 74 | ---@field sse http.SseClient? 75 | local Openrouter = { 76 | models = { 77 | "anthropic/claude-sonnet-4.5", 78 | "anthropic/claude-sonnet-4", 79 | "anthropic/claude-opus-4.1", 80 | "anthropic/claude-opus-4", 81 | "openai/gpt-4o", 82 | "anthropic/claude-3.7-sonnet", 83 | "google/gemini-2.0-flash-001", 84 | "google/gemini-2.5-flash-preview", 85 | "deepseek/deepseek-chat-v3-0324:free", 86 | "deepseek/deepseek-chat-v3-0324", 87 | "qwen/qwen3-235b-a22b", 88 | } 89 | } 90 | Openrouter.__index = Openrouter 91 | 92 | function Openrouter.new(type) 93 | local self = setmetatable({}, Openrouter) 94 | self.base_url = "https://openrouter.ai/api/v1" 95 | self.api_key = vim.env["OPENROUTER_API_KEY"] 96 | self.type = type or "chat" 97 | if self.type == "chat" then 98 | self.payload = chat_json 99 | elseif self.type == "reasoner" then 100 | self.payload = reasoner_json 101 | elseif self.type == "code" then 102 | self.payload = code_json 103 | elseif self.type == "commit" then 104 | self.payload = commit_json 105 | elseif self.type == "translate" then 106 | self.payload = translate_json 107 | end 108 | return self 109 | end 110 | 111 | function Openrouter.set_model(model) 112 | Openrouter._c_model = model 113 | end 114 | 115 | function Openrouter:payload_message(messages) 116 | self.model = self.payload.model 117 | local json = vim.deepcopy(self.payload); 118 | if Openrouter._c_model then 119 | json.model = Openrouter._c_model 120 | end 121 | self.model = json.model 122 | json.messages = messages 123 | return json 124 | end 125 | 126 | function Openrouter:url() 127 | if self.type == "chat" 128 | or self.type == "reasoner" 129 | or self.type == "commit" 130 | or self.type == "translate" 131 | then 132 | return self.base_url .. "/chat/completions" 133 | elseif self.type == "code" then 134 | return self.base_url .. "/chat/completions" 135 | end 136 | end 137 | 138 | ---@param messages table 139 | function Openrouter:request(messages, callback) 140 | local payload = self:payload_message(messages) 141 | local function callback_data(resp_json) 142 | if resp_json.error then 143 | vim.notify("Openrouter error: " .. vim.inspect(resp_json), vim.log.levels.ERROR) 144 | return 145 | end 146 | for _, message in ipairs(resp_json.choices) do 147 | callback({ 148 | role = message.delta.role, 149 | reasoning = message.delta.reasoning_content, 150 | data = message.delta.content, 151 | usage = resp_json.usage, 152 | }) 153 | end 154 | end 155 | local job 156 | local tmp = "" 157 | local is_json = function(text) 158 | return (vim.startswith(text, "{") and vim.endswith(text, "}")) 159 | or (vim.startswith(text, "[") and vim.endswith(text, "]")) 160 | end 161 | ---@param event http.SseEvent 162 | local callback_handle = function(_, event) 163 | if not event.data then 164 | return 165 | end 166 | for _, value in ipairs(event.data) do 167 | -- 忽略 SSE 换行输出 168 | if value ~= "" then 169 | if vim.startswith(value, "data: ") then 170 | local text = string.sub(value, 7, -1) 171 | if text == "[DONE]" then 172 | tmp = "" 173 | callback({ 174 | data = text, 175 | done = true, 176 | }) 177 | else 178 | tmp = tmp .. text 179 | if is_json(tmp) then 180 | local resp_json = vim.fn.json_decode(tmp) 181 | callback_data(resp_json) 182 | tmp = "" 183 | end 184 | end 185 | elseif vim.startswith(value, ": keep-alive") then 186 | -- 这里可能是心跳检测报文, 输出提示 187 | vim.notify("[SSE] " .. value, vim.log.levels.INFO, { id = "gpt:" .. job, title = "Openrouter" }) 188 | elseif vim.startswith(value, ": OPENROUTER PROCESSING") then 189 | -- ignore 190 | -- vim.notify("[SSE] " .. value, vim.log.levels.INFO, { id = "gpt:" .. job, title = "Openrouter" }) 191 | else 192 | tmp = tmp .. value 193 | if is_json(tmp) then 194 | local resp_json = vim.fn.json_decode(tmp) 195 | callback_data(resp_json) 196 | tmp = "" 197 | end 198 | end 199 | end 200 | end 201 | end 202 | 203 | self.sse = sse.new(self:url()) 204 | :POST() 205 | :auth(self.api_key) 206 | :body(payload) 207 | :handle(callback_handle) 208 | :send() 209 | job = self.sse.job 210 | end 211 | 212 | function Openrouter:close() 213 | if self.sse then 214 | self.sse:stop() 215 | end 216 | end 217 | 218 | return Openrouter 219 | -------------------------------------------------------------------------------- /lua/kide/gpt/provide/deepseek.lua: -------------------------------------------------------------------------------- 1 | local sse = require("kide.http.sse") 2 | local max_tokens = 4096 * 2 3 | local code_json = { 4 | messages = { 5 | { 6 | content = "帮我生成一个快速排序", 7 | role = "user", 8 | }, 9 | { 10 | content = "```python\n", 11 | prefix = true, 12 | role = "assistant", 13 | }, 14 | }, 15 | model = "deepseek-chat", 16 | max_tokens = max_tokens, 17 | stop = "```", 18 | stream = true, 19 | temperature = 0.0, 20 | } 21 | 22 | local chat_json = { 23 | messages = { 24 | { 25 | content = "", 26 | role = "system", 27 | }, 28 | }, 29 | model = "deepseek-chat", 30 | frequency_penalty = 0, 31 | max_tokens = 4096 * 2, 32 | presence_penalty = 0, 33 | response_format = { 34 | type = "text", 35 | }, 36 | stop = nil, 37 | stream = true, 38 | stream_options = nil, 39 | temperature = 1.3, 40 | top_p = 1, 41 | tools = nil, 42 | tool_choice = "none", 43 | logprobs = false, 44 | top_logprobs = nil, 45 | } 46 | 47 | local reasoner_json = { 48 | messages = {}, 49 | model = "deepseek-reasoner", 50 | max_tokens = 4096 * 2, 51 | response_format = { 52 | type = "text", 53 | }, 54 | stop = nil, 55 | stream = true, 56 | stream_options = nil, 57 | tools = nil, 58 | tool_choice = "none", 59 | } 60 | 61 | local commit_json = { 62 | messages = { 63 | { 64 | content = "", 65 | role = "system", 66 | }, 67 | { 68 | content = "Hi", 69 | role = "user", 70 | }, 71 | }, 72 | model = "deepseek-chat", 73 | frequency_penalty = 0, 74 | max_tokens = 4096 * 2, 75 | presence_penalty = 0, 76 | response_format = { 77 | type = "text", 78 | }, 79 | stop = nil, 80 | stream = true, 81 | stream_options = nil, 82 | temperature = 1.3, 83 | top_p = 1, 84 | tools = nil, 85 | tool_choice = "none", 86 | logprobs = false, 87 | top_logprobs = nil, 88 | } 89 | 90 | local translate_json = { 91 | messages = { 92 | { 93 | content = "", 94 | role = "system", 95 | }, 96 | { 97 | content = "Hi", 98 | role = "user", 99 | }, 100 | }, 101 | model = "deepseek-chat", 102 | frequency_penalty = 0, 103 | max_tokens = 4096 * 2, 104 | presence_penalty = 0, 105 | response_format = { 106 | type = "text", 107 | }, 108 | stop = nil, 109 | stream = true, 110 | stream_options = nil, 111 | temperature = 1.3, 112 | top_p = 1, 113 | tools = nil, 114 | tool_choice = "none", 115 | logprobs = false, 116 | top_logprobs = nil, 117 | } 118 | 119 | ---@class gpt.DeepSeekClient : gpt.Client 120 | ---@field base_url string 121 | ---@field api_key string 122 | ---@field type string 123 | ---@field payload table 124 | ---@field sse http.SseClient? 125 | local DeepSeek = { 126 | models = { 127 | "deepseek-chat", 128 | }, 129 | } 130 | DeepSeek.__index = DeepSeek 131 | 132 | function DeepSeek.new(type) 133 | local self = setmetatable({}, DeepSeek) 134 | self.base_url = "https://api.deepseek.com" 135 | self.api_key = vim.env["DEEPSEEK_API_KEY"] 136 | self.type = type or "chat" 137 | if self.type == "chat" then 138 | self.payload = chat_json 139 | elseif self.type == "reasoner" then 140 | self.payload = reasoner_json 141 | elseif self.type == "code" then 142 | self.payload = code_json 143 | elseif self.type == "commit" then 144 | self.payload = commit_json 145 | elseif self.type == "translate" then 146 | self.payload = translate_json 147 | end 148 | return self 149 | end 150 | 151 | function DeepSeek.set_model(_) 152 | --ignore 153 | end 154 | 155 | function DeepSeek:payload_message(messages) 156 | local json = vim.deepcopy(self.payload) 157 | self.model = json.model 158 | json.messages = messages 159 | return json 160 | end 161 | 162 | function DeepSeek:url() 163 | if self.type == "chat" or self.type == "reasoner" or self.type == "commit" or self.type == "translate" then 164 | return self.base_url .. "/chat/completions" 165 | elseif self.type == "code" then 166 | return self.base_url .. "/beta/v1/chat/completions" 167 | end 168 | end 169 | 170 | ---@param messages table 171 | function DeepSeek:request(messages, callback) 172 | local payload = self:payload_message(messages) 173 | local function callback_data(resp_json) 174 | for _, message in ipairs(resp_json.choices) do 175 | callback({ 176 | role = message.delta.role, 177 | reasoning = message.delta.reasoning_content, 178 | data = message.delta.content, 179 | usage = resp_json.usage, 180 | }) 181 | end 182 | end 183 | local job 184 | local tmp = "" 185 | local is_json = function(text) 186 | return (vim.startswith(text, "{") and vim.endswith(text, "}")) 187 | or (vim.startswith(text, "[") and vim.endswith(text, "]")) 188 | end 189 | ---@param event http.SseEvent 190 | local callback_handle = function(_, event) 191 | if not event.data then 192 | return 193 | end 194 | for _, value in ipairs(event.data) do 195 | -- 忽略 SSE 换行输出 196 | if value ~= "" then 197 | if vim.startswith(value, "data: ") then 198 | local text = string.sub(value, 7, -1) 199 | if text == "[DONE]" then 200 | tmp = "" 201 | callback({ 202 | data = text, 203 | done = true, 204 | }) 205 | else 206 | tmp = tmp .. text 207 | if is_json(tmp) then 208 | local resp_json = vim.fn.json_decode(tmp) 209 | callback_data(resp_json) 210 | tmp = "" 211 | end 212 | end 213 | elseif vim.startswith(value, ": keep-alive") then 214 | -- 这里可能是心跳检测报文, 输出提示 215 | vim.notify("[SSE] " .. value, vim.log.levels.INFO, { id = "gpt:" .. job, title = "DeepSeek" }) 216 | else 217 | tmp = tmp .. value 218 | if is_json(tmp) then 219 | local resp_json = vim.fn.json_decode(tmp) 220 | callback_data(resp_json) 221 | tmp = "" 222 | end 223 | end 224 | end 225 | end 226 | end 227 | 228 | self.sse = sse.new(self:url()) 229 | :POST() 230 | :auth(self.api_key) 231 | :body(payload) 232 | :handle(callback_handle) 233 | :send() 234 | job = self.sse.job 235 | end 236 | 237 | function DeepSeek:close() 238 | if self.sse then 239 | self.sse:stop() 240 | end 241 | end 242 | 243 | return DeepSeek 244 | -------------------------------------------------------------------------------- /lua/kide/stl.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | ---@class kide.stl.Status 3 | ---@field index number 4 | ---@field buf? number 5 | ---@field code? number 6 | ---@field code_msg? string 7 | ---@field title? string 8 | 9 | local glob_progress = { " ", " ", " ", "" } 10 | 11 | local glob_idx = 0 12 | 13 | local glob_stl = {} 14 | 15 | function M.new_status(title, buf, bg_proc) 16 | glob_idx = glob_idx + 1 17 | ---@type kide.stl.Status 18 | local stl = { 19 | id = glob_idx, 20 | index = 0, 21 | buf = buf, 22 | bg_proc = bg_proc, 23 | code = nil, 24 | code_msg = nil, 25 | title = title, 26 | } 27 | glob_stl[glob_idx] = stl 28 | return stl.id 29 | end 30 | 31 | local function next_status() 32 | local stl_bar = {} 33 | for _, cstl in pairs(glob_stl) do 34 | if cstl.code then 35 | vim.list_extend(stl_bar, { 36 | " %#DiagnosticWarn#", 37 | cstl.title, 38 | }) 39 | if cstl.code == 0 then 40 | vim.list_extend(stl_bar, { " %#DiagnosticOk#", cstl.code_msg }) 41 | else 42 | vim.list_extend(stl_bar, { " %#DiagnosticError#", cstl.code_msg }) 43 | end 44 | else 45 | if cstl.index >= #glob_progress then 46 | cstl.index = 0 47 | end 48 | cstl.index = cstl.index + 1 49 | vim.list_extend(stl_bar, { 50 | " %#DiagnosticWarn#", 51 | cstl.title, 52 | " ", 53 | glob_progress[cstl.index], 54 | }) 55 | end 56 | end 57 | return stl_bar 58 | end 59 | 60 | local function buf_status() 61 | return vim.b[0].stl 62 | end 63 | 64 | local _lsp_status = nil 65 | function M.set_lsp_status(message) 66 | _lsp_status = message 67 | end 68 | local function lsp_status() 69 | return _lsp_status 70 | end 71 | 72 | function M.exit_status(id, code) 73 | local cstl = glob_stl[id] 74 | if not cstl then 75 | return 76 | end 77 | if code then 78 | cstl.code = code 79 | if code == 0 then 80 | cstl.code_msg = "SUCCESS" 81 | else 82 | cstl.code_msg = "FAILED" 83 | end 84 | vim.defer_fn(function() 85 | glob_stl[id] = nil 86 | end, 2000) 87 | end 88 | vim.cmd.redrawstatus() 89 | end 90 | 91 | -- 参考 https://github.com/mfussenegger/dotfiles 92 | function M.statusline() 93 | local parts = { 94 | "%<", 95 | } 96 | local bstl = buf_status() 97 | local lspstatus = lsp_status() 98 | if bstl then 99 | if type(bstl) == "table" then 100 | vim.list_extend(parts, bstl) 101 | else 102 | table.insert(parts, bstl) 103 | end 104 | elseif lspstatus then 105 | vim.list_extend(parts, { "%#DiagnosticInfo#", lspstatus }) 106 | else 107 | local git = M.git_status() 108 | if git then 109 | table.insert(parts, " %#DiagnosticError# %#StatusLine#" .. git.head) 110 | if git.added and git.added > 0 then 111 | vim.list_extend(parts, { " %#Added# ", tostring(git.added) }) 112 | end 113 | if git.removed and git.removed > 0 then 114 | vim.list_extend(parts, { " %#Removed#󰍵 ", tostring(git.removed) }) 115 | end 116 | if git.changed and git.changed > 0 then 117 | vim.list_extend(parts, { " %#Changed# ", tostring(git.changed) }) 118 | end 119 | end 120 | 121 | local fstatus = M.file() 122 | vim.list_extend(parts, fstatus) 123 | 124 | local counts = vim.diagnostic.count(0, { severity = { min = vim.diagnostic.severity.WARN } }) 125 | local num_errors = counts[vim.diagnostic.severity.ERROR] or 0 126 | local num_warnings = counts[vim.diagnostic.severity.WARN] or 0 127 | table.insert(parts, " %#DiagnosticWarn#%r%m") 128 | if num_errors > 0 then 129 | vim.list_extend(parts, { "%#DiagnosticError#", " 󰅙 ", tostring(num_errors), " " }) 130 | elseif num_warnings > 0 then 131 | vim.list_extend(parts, { "%#DiagnosticWarn#", "  ", tostring(num_warnings), " " }) 132 | end 133 | end 134 | 135 | local cs = next_status() 136 | if cs then 137 | vim.list_extend(parts, cs) 138 | end 139 | 140 | table.insert(parts, "%=") 141 | vim.list_extend(parts, { "%#StatusLine#", "%l:%c", " " }) 142 | local ft = vim.bo.filetype 143 | if ft and ft ~= "" then 144 | local clients = vim.lsp.get_clients({ bufnr = 0 }) 145 | if clients and #clients > 0 then 146 | vim.list_extend(parts, { "%#DiagnosticInfo#", "[ ", clients[#clients].name, "] " }) 147 | end 148 | vim.list_extend(parts, { "%#StatusLine#", ft, " " }) 149 | end 150 | vim.list_extend(parts, { "%#StatusLine#", "%{&ff}", " " }) 151 | vim.list_extend(parts, { "%#StatusLine#", "%{&fenc}", " " }) 152 | return table.concat(parts, "") 153 | end 154 | 155 | function M.git_status() 156 | return vim.b[0].gitsigns_status_dict 157 | end 158 | 159 | function M.file() 160 | local buf = vim.api.nvim_get_current_buf() 161 | local filename = vim.uri_from_bufnr(buf) 162 | local devicons = require("nvim-web-devicons") 163 | local icon, name = devicons.get_icon_by_filetype(vim.bo[buf].filetype, { default = true }) 164 | if name then 165 | return { " ", "%#" .. name .. "#", icon, " %#StatusLine#", M.format_uri(filename) } 166 | else 167 | return { " ", icon, " ", M.format_uri(filename) } 168 | end 169 | end 170 | function M.format_uri(uri) 171 | if vim.startswith(uri, "jdt://") then 172 | local jar, pkg, class = uri:match("^jdt://contents/([^/]+)/([^/]+)/(.+)?") 173 | return string.format("%s::%s (%s)", pkg, class, jar) 174 | else 175 | local fname = vim.fn.fnamemodify(vim.uri_to_fname(uri), ":.") 176 | fname = fname:gsub("src/main/java/", "s/m/j/") 177 | fname = fname:gsub("src/test/java/", "s/t/j/") 178 | return fname 179 | end 180 | end 181 | function M.dap_status() 182 | local ok, dap = pcall(require, "dap") 183 | if not ok then 184 | return "" 185 | end 186 | local status = dap.status() 187 | if status ~= "" then 188 | return status .. " | " 189 | end 190 | return "" 191 | end 192 | 193 | function M.tabline() 194 | local parts = {} 195 | local devicons = require("nvim-web-devicons") 196 | for i = 1, vim.fn.tabpagenr("$") do 197 | local tabpage = vim.fn.gettabinfo(i)[1] 198 | local winid = tabpage.windows[1] 199 | if not winid or not vim.api.nvim_win_is_valid(winid) then 200 | goto continue 201 | end 202 | local bufnr = vim.api.nvim_win_get_buf(winid) 203 | if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then 204 | goto continue 205 | end 206 | local bufname = vim.fn.bufname(bufnr) 207 | local filename = vim.fn.fnamemodify(bufname, ":t") 208 | 209 | local icon, name = devicons.get_icon_by_filetype(vim.bo[bufnr].filetype, { default = true }) 210 | table.insert(parts, " %#" .. name .. "#") 211 | table.insert(parts, icon) 212 | table.insert(parts, " ") 213 | if i == vim.fn.tabpagenr() then 214 | table.insert(parts, "%#TabLineSel#") 215 | else 216 | table.insert(parts, "%#TabLine#") 217 | end 218 | if not filename or filename == "" then 219 | filename = "[No Name]" 220 | end 221 | table.insert(parts, filename) 222 | ::continue:: 223 | end 224 | return table.concat(parts, "") 225 | end 226 | return M 227 | -------------------------------------------------------------------------------- /colors/gruvboxl.lua: -------------------------------------------------------------------------------- 1 | -- 使用 morhetz/gruvbox 2 | -- nvchad 3 | local dark0_hard = "#1d2021" 4 | local dark0 = "#282828" 5 | local dark0_soft = "#32302f" 6 | local dark1 = "#3c3836" 7 | local dark2 = "#504945" 8 | local dark3 = "#665c54" 9 | local dark4 = "#7c6f64" 10 | local dark4_256 = "#7c6f64" 11 | 12 | local dark_ext1 = "#2e2e2e" 13 | local dark_ext2 = "#2c2c2c" 14 | 15 | local gray_ext1 = "#423e3c" 16 | local gray_ext2 = "#4b4b4b" 17 | local gray_ext3 = "#4e4e4e" 18 | local gray_ext4 = "#484442" 19 | local gray_ext5 = "#656565" 20 | 21 | local gray_245 = "#928374" 22 | local gray_244 = "#928374" 23 | 24 | local light0_hard = "#f9f5d7" 25 | local light0 = "#fbf1c7" 26 | local light0_soft = "#f2e5bc" 27 | local light1 = "#ebdbb2" 28 | local light2 = "#d5c4a1" 29 | local light3 = "#bdae93" 30 | local light4 = "#a89984" 31 | local light4_256 = "#a89984" 32 | 33 | local bright_red = "#fb4934" 34 | local bright_green = "#b8bb26" 35 | local bright_yellow = "#fabd2f" 36 | local bright_blue = "#83a598" 37 | local bright_purple = "#d3869b" 38 | local bright_aqua = "#8ec07c" 39 | local bright_orange = "#fe8019" 40 | 41 | local neutral_red = "#cc241d" 42 | local neutral_green = "#98971a" 43 | local neutral_yellow = "#d79921" 44 | local neutral_blue = "#458588" 45 | local neutral_purple = "#b16286" 46 | local neutral_aqua = "#689d6a" 47 | local neutral_orange = "#d65d0e" 48 | 49 | local faded_red = "#9d0006" 50 | local faded_green = "#79740e" 51 | local faded_yellow = "#b57614" 52 | local faded_blue = "#076678" 53 | local faded_purple = "#8f3f71" 54 | local faded_aqua = "#427b58" 55 | local faded_orange = "#af3a03" 56 | 57 | -- term 58 | 59 | vim.g.terminal_color_0 = dark0 -- 黑色 60 | vim.g.terminal_color_1 = neutral_red -- 红色 61 | vim.g.terminal_color_2 = neutral_green -- 绿色 62 | vim.g.terminal_color_3 = neutral_yellow -- 黄色 63 | vim.g.terminal_color_4 = neutral_blue -- 蓝色 64 | vim.g.terminal_color_5 = neutral_purple -- 洋红色 65 | vim.g.terminal_color_6 = neutral_aqua -- 青色 66 | vim.g.terminal_color_7 = light4 -- 白色 67 | vim.g.terminal_color_8 = gray_245 -- 亮黑色 68 | vim.g.terminal_color_9 = bright_red -- 亮红色 69 | vim.g.terminal_color_10 = bright_green -- 亮绿色 70 | vim.g.terminal_color_11 = bright_yellow -- 亮黄色 71 | vim.g.terminal_color_12 = bright_blue -- 亮蓝色 72 | vim.g.terminal_color_13 = bright_purple -- 亮洋红色 73 | vim.g.terminal_color_14 = bright_aqua -- 亮青色 74 | vim.g.terminal_color_15 = light1 -- 亮白色 75 | 76 | -- 设置高亮 77 | local function hl(theme) 78 | for k, v in pairs(theme) do 79 | vim.api.nvim_set_hl(0, k, v) 80 | end 81 | end 82 | -- 基础颜色 83 | hl({ 84 | NvimLightGrey2 = { fg = light2 }, 85 | 86 | Normal = { fg = light2, bg = dark0 }, 87 | CursorLine = { bg = dark_ext1 }, 88 | CursorLineNr = {}, 89 | WildMenu = { fg = bright_red, bg = bright_yellow }, 90 | 91 | WinBar = {}, 92 | WinBarNC = {}, 93 | 94 | WinSeparator = { fg = gray_ext2 }, 95 | Pmenu = { fg = light2, bg = dark0 }, 96 | PmenuSel = { fg = dark0, bg = bright_blue }, 97 | PmenuMatch = { bold = true }, 98 | PmenuMatchSel = { bold = true }, 99 | PmenuKind = { link = "Pmenu" }, 100 | PmenuKindSel = { link = "PmenuSel" }, 101 | PmenuExtra = { link = "Pmenu" }, 102 | PmenuExtraSel = { link = "PmenuSel" }, 103 | PmenuSbar = { bg = "#353535" }, 104 | PmenuThumb = { bg = gray_ext2 }, 105 | QuickFixLine = { bg = dark1 }, 106 | 107 | NormalFloat = {}, 108 | FloatBorder = { fg = gray_ext3 }, 109 | StatusLine = { bg = dark_ext2, fg = light1 }, 110 | StatusLineNC = { bg = dark_ext2 }, 111 | 112 | TabLine = { bg = dark_ext2, fg = gray_ext5 }, 113 | TabLineSel = { fg = light1, bg = dark0 }, 114 | Directory = { fg = bright_blue }, 115 | Title = { fg = bright_blue, bold = true }, 116 | Question = { fg = bright_blue }, 117 | Search = { fg = dark0, bg = bright_yellow }, 118 | IncSearch = { fg = dark0, bg = bright_orange }, 119 | CurSearch = { link = "IncSearch" }, 120 | 121 | Comment = { fg = gray_ext5, italic = true }, 122 | Todo = { fg = bright_green }, 123 | Error = { fg = dark0, bg = bright_red }, 124 | 125 | MoreMsg = { fg = bright_green }, 126 | ModeMsg = { fg = bright_green }, 127 | ErrorMsg = { fg = bright_red, bg = dark0 }, 128 | WarningMsg = { fg = bright_yellow }, 129 | 130 | DiffAdd = { fg = dark0, bg = bright_green }, 131 | DiffChange = { fg = dark0, bg = bright_aqua }, 132 | DiffDelete = { fg = dark0, bg = bright_red }, 133 | DiffText = { fg = dark0, bg = bright_yellow }, 134 | 135 | LineNr = { fg = gray_ext2 }, 136 | SignColumn = { fg = gray_ext4 }, 137 | 138 | Cursor = { reverse = true }, 139 | lCursor = { link = "Cursor" }, 140 | 141 | Type = { fg = bright_yellow }, 142 | PreProc = { fg = bright_yellow }, 143 | Include = { fg = bright_blue }, 144 | Function = { fg = bright_blue }, 145 | String = { fg = bright_green }, 146 | Statement = { fg = bright_red }, 147 | Constant = { fg = bright_red }, 148 | Special = { fg = bright_aqua }, 149 | Operator = { fg = bright_blue }, 150 | Delimiter = { fg = neutral_orange }, 151 | Identifier = { fg = bright_red }, 152 | 153 | Visual = { bg = gray_ext1 }, 154 | VisualNOS = { link = "Visual" }, 155 | Folded = { fg = gray_ext5, bg = dark_ext1 }, 156 | FoldColumn = { fg = gray_ext5, bg = dark_ext1 }, 157 | 158 | DiagnosticError = { fg = bright_red }, 159 | DiagnosticInfo = { fg = bright_aqua }, 160 | DiagnosticHint = { fg = bright_blue }, 161 | DiagnosticWarn = { fg = neutral_yellow }, 162 | DiagnosticOk = { fg = bright_green }, 163 | 164 | DiagnosticUnderlineError = { underline = true, sp = bright_blue }, 165 | DiagnosticUnderlineWarn = { underline = true, sp = bright_yellow }, 166 | DiagnosticUnderlineInfo = { underline = true, sp = bright_aqua }, 167 | DiagnosticUnderlineHint = { underline = true, sp = bright_blue }, 168 | DiagnosticUnderlineOk = { underline = true, sp = bright_green }, 169 | 170 | ColorColumn = { bg = dark_ext1 }, 171 | Debug = { fg = neutral_yellow }, 172 | ["@variable"] = { fg = light2 }, 173 | ["@variable.member"] = { fg = bright_red }, 174 | ["@punctuation.delimiter"] = { fg = neutral_orange }, 175 | ["@keyword.operator"] = { fg = bright_purple }, 176 | ["@keyword.exception"] = { fg = bright_red }, 177 | 178 | ["@markup"] = { link = "Special" }, 179 | ["@markup.strong"] = { bold = true }, 180 | ["@markup.italic"] = { italic = true }, 181 | ["@markup.strikethrough"] = { strikethrough = true }, 182 | ["@markup.underline"] = { underline = true }, 183 | ["@markup.heading"] = { fg = bright_blue }, 184 | ["@markup.link"] = { fg = bright_red }, 185 | 186 | ["@markup.quote"] = { bg = dark_ext1 }, 187 | ["@markup.list"] = { fg = bright_red }, 188 | ["@markup.link.label"] = { fg = bright_aqua }, 189 | ["@markup.link.url"] = { underline = true, fg = bright_orange }, 190 | ["@markup.raw"] = { fg = bright_orange }, 191 | -- lsp semanticTokens 192 | -- ["@lsp.type.macro.rust"] = { link = "@lsp" }, 193 | ["@lsp.type.modifier.java"] = { link = "@lsp" }, 194 | ["@lsp.type.namespace.java"] = { link = "@variable" }, 195 | 196 | LspReferenceWrite = { fg = "#e78a4e" }, 197 | LspReferenceText = { fg = "#e78a4e" }, 198 | 199 | NvimTreeGitNew = { fg = neutral_yellow }, 200 | NvimTreeFolderIcon = { fg = "#749689" }, 201 | NvimTreeSpecialFile = { fg = neutral_yellow, bold = true }, 202 | NvimTreeIndentMarker = { fg = "#313334" }, 203 | 204 | Added = { fg = bright_green }, 205 | Removed = { fg = bright_red }, 206 | Changed = { fg = neutral_yellow }, 207 | 208 | diffChanged = { fg = neutral_yellow }, 209 | diffAdded = { fg = bright_green }, 210 | 211 | BlinkCmpMenuBorder = { link = "FloatBorder" }, 212 | BlinkCmpDocBorder = { link = "FloatBorder" }, 213 | 214 | SnacksPickerBorder = { fg = gray_245 }, 215 | SnacksDiffContext = { fg = nil, bg = dark_ext1 }, 216 | SnacksDiffContextLineNr = { fg = nil, bg = dark_ext1 }, 217 | 218 | MarkviewCode = { bg = dark_ext1 }, 219 | MarkviewInlineCode = { bg = dark_ext1 }, 220 | }) 221 | -------------------------------------------------------------------------------- /lua/kide/tools/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | -- 69 %a "test.lua" 第 6 行 3 | -- 76 #h "README.md" 第 1 行 4 | -- 78 h "init.lua" 第 1 行 5 | M.close_other_buf = function() 6 | -- local cur_winnr = vim.fn.winnr() 7 | local cur_buf = vim.fn.bufnr("%") 8 | if cur_buf == -1 then 9 | return 10 | end 11 | -- local bf_no = vim.fn.winbufnr(cur_winnr) 12 | vim.fn.execute("bn") 13 | local next_buf = vim.fn.bufnr("%") 14 | 15 | local count = 999 16 | while next_buf ~= -1 and cur_buf ~= next_buf and count > 0 do 17 | local bdel = "bdel " .. next_buf 18 | vim.fn.execute("bn") 19 | vim.fn.execute(bdel) 20 | next_buf = vim.fn.bufnr("%") 21 | count = count - 1 22 | end 23 | end 24 | 25 | M.is_upper = function(c) 26 | return c >= 65 and c <= 90 27 | end 28 | 29 | M.is_lower = function(c) 30 | return c >= 97 and c <= 122 31 | end 32 | M.char_size = function(c) 33 | local code = c 34 | if code < 127 then 35 | return 1 36 | elseif code <= 223 then 37 | return 2 38 | elseif code <= 239 then 39 | return 3 40 | elseif code <= 247 then 41 | return 4 42 | end 43 | return nil 44 | end 45 | 46 | local function camel_case_t(word) 47 | if word:find("_") then 48 | return M.camel_case_c(word) 49 | else 50 | return M.camel_case_u(word) 51 | end 52 | end 53 | 54 | M.camel_case = function(word) 55 | if word == "" or word == nil then 56 | return 57 | end 58 | if word:find(" ") then 59 | local ws = {} 60 | for _, value in ipairs(vim.split(word, " ")) do 61 | table.insert(ws, camel_case_t(value)) 62 | end 63 | return table.concat(ws, " ") 64 | else 65 | return camel_case_t(word) 66 | end 67 | end 68 | M.camel_case_u = function(word) 69 | local result = {} 70 | local len = word:len() 71 | local i = 1 72 | local f = true 73 | while i <= len do 74 | local c = word:byte(i) 75 | local cs = M.char_size(c) 76 | local cf = f 77 | if cs == nil then 78 | return word 79 | end 80 | if cs == 1 and M.is_upper(c) then 81 | f = false 82 | if cf and i ~= 1 then 83 | table.insert(result, "_") 84 | end 85 | else 86 | f = true 87 | end 88 | local e = i + cs 89 | table.insert(result, word:sub(i, e - 1)) 90 | i = e 91 | end 92 | return table.concat(result, ""):upper() 93 | end 94 | M.camel_case_c = function(word) 95 | local w = word:lower() 96 | local result = {} 97 | local sc = 95 98 | local f = false 99 | local len = word:len() 100 | local i = 1 101 | while i <= len do 102 | local c = w:byte(i) 103 | local cs = M.char_size(c) 104 | local e = i + cs 105 | if cs == nil then 106 | return word 107 | end 108 | local cf = f 109 | if f then 110 | f = false 111 | end 112 | if c == sc then 113 | f = true 114 | else 115 | if cs == 1 and cf then 116 | table.insert(result, string.char(c):upper()) 117 | else 118 | table.insert(result, w:sub(i, e - 1)) 119 | end 120 | end 121 | i = e 122 | end 123 | return table.concat(result, "") 124 | end 125 | M.camel_case_start = function(r, l1, l2) 126 | local word 127 | if r == 0 then 128 | word = vim.fn.expand("") 129 | elseif l1 == l2 then 130 | word = M.get_visual_selection()[1] 131 | else 132 | vim.notify("请选择单行字符", vim.log.levels.WARN) 133 | end 134 | if word and word ~= "" then 135 | local reg_tmp = vim.fn.getreg("a") 136 | vim.fn.setreg("a", M.camel_case(word)) 137 | if r == 0 then 138 | vim.cmd('normal! viw"ap') 139 | else 140 | vim.cmd('normal! gv"ap') 141 | end 142 | vim.fn.setreg("a", reg_tmp) 143 | end 144 | end 145 | 146 | -- see https://github.com/nvim-pack/nvim-spectre/blob/master/lua/spectre/utils.lua#L120 147 | ---@return string[] 148 | M.get_visual_selection = function(mode) 149 | mode = mode or vim.fn.visualmode() 150 | --参考 @phanium @linrongbin @skywind3000 提供的方法。 151 | -- https://github.com/skywind3000/vim/blob/master/autoload/asclib/compat.vim 152 | return vim.fn.getregion(vim.fn.getpos("'<"), vim.fn.getpos("'>"), { type = mode }) 153 | end 154 | 155 | M.Windows = "Windows" 156 | M.Linux = "Linux" 157 | M.Mac = "Mac" 158 | 159 | M.os_type = function() 160 | local has = vim.fn.has 161 | local t = M.Linux 162 | if has("win32") == 1 or has("win64") == 1 then 163 | t = M.Windows 164 | elseif has("mac") == 1 then 165 | t = M.Mac 166 | end 167 | return t 168 | end 169 | 170 | M.is_win = M.os_type() == M.Windows 171 | M.is_linux = M.os_type() == M.Linux 172 | M.is_mac = M.os_type() == M.Mac 173 | 174 | --- complete 175 | ---@param opt {model:"single"|"multiple"} 176 | M.command_args_complete = function(complete, opt) 177 | opt = opt or {} 178 | if complete ~= nil then 179 | return function(_, cmd_line, _) 180 | if opt.model == "multiple" then 181 | local args = vim.split(cmd_line, " ") 182 | return vim.tbl_filter(function(item) 183 | return not vim.tbl_contains(args, item) 184 | end, complete) 185 | elseif opt.model == "single" then 186 | local args = vim.split(cmd_line, " ") 187 | for _, value in ipairs(args) do 188 | if vim.tbl_contains(complete, value) then 189 | return {} 190 | end 191 | end 192 | return complete 193 | else 194 | return complete 195 | end 196 | end 197 | end 198 | end 199 | 200 | M.open_fn = function(file) 201 | local cmd 202 | if M.is_linux then 203 | cmd = "xdg-open" 204 | elseif M.is_mac then 205 | cmd = "open" 206 | elseif M.is_win then 207 | cmd = "start" 208 | end 209 | vim.system({ cmd, file }) 210 | end 211 | 212 | M.tmpdir = function() 213 | local tmpdir = vim.env["TMPDIR"] or vim.env["TEMP"] 214 | if not tmpdir then 215 | if M.is_win then 216 | tmpdir = "C:\\Windows\\Temp\\" 217 | else 218 | tmpdir = "/tmp/" 219 | end 220 | end 221 | return tmpdir 222 | end 223 | 224 | M.tmpdir_file = function(file) 225 | return M.tmpdir() .. file 226 | end 227 | 228 | M.java_bin = function() 229 | local java_home = vim.env["JAVA_HOME"] 230 | if java_home then 231 | return java_home .. "/bin/java" 232 | end 233 | return "java" 234 | end 235 | 236 | -- URL safe base64 --> standard base64 237 | M.base64_url_safe_to_std = function(msg) 238 | if string.match(msg, "-") then 239 | msg = string.gsub(msg, "-", "+") 240 | end 241 | if string.match(msg, "_") then 242 | msg = string.gsub(msg, "_", "/") 243 | end 244 | if not vim.endswith(msg, "=") then 245 | local padding = #msg % 4 246 | if padding > 0 then 247 | msg = msg .. string.rep("=", 4 - padding) 248 | end 249 | end 250 | return msg 251 | end 252 | 253 | M.base64_url_safe = function(msg) 254 | return M.base64_std_to_url_safe(vim.base64.encode(msg)) 255 | end 256 | 257 | M.base64_std_to_url_safe = function(msg) 258 | if string.match(msg, "+") then 259 | msg = string.gsub(msg, "+", "-") 260 | end 261 | if string.match(msg, "/") then 262 | msg = string.gsub(msg, "/", "_") 263 | end 264 | if string.match(msg, "=") then 265 | msg = string.gsub(msg, "=", "") 266 | end 267 | return msg 268 | end 269 | 270 | -- 创建一个新的缓冲区并显示 qflist 的内容 271 | local function qflist_to_buf() 272 | -- 获取当前的 qflist 273 | local qflist = vim.fn.getqflist() 274 | 275 | -- 创建一个新的缓冲区 276 | local buf = vim.api.nvim_create_buf(true, false) 277 | 278 | -- 将 qflist 的内容写入缓冲区 279 | local lines = {} 280 | for _, item in ipairs(qflist) do 281 | local text = item.text or "" 282 | table.insert(lines, text) 283 | end 284 | 285 | vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) 286 | 287 | -- 打开新窗口并显示缓冲区 288 | vim.api.nvim_command("sbuffer " .. buf) 289 | end 290 | 291 | M.setup = function() 292 | vim.api.nvim_create_user_command("CamelCase", function(o) 293 | M.camel_case_start(o.range, o.line1, o.line2) 294 | end, { range = 0, nargs = 0 }) 295 | 296 | vim.api.nvim_create_user_command("QFlistToBuf", function(_) 297 | qflist_to_buf() 298 | end, { range = 0, nargs = 0 }) 299 | end 300 | 301 | return M 302 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /lua/kide/gpt/chat.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local gpt_provide = require("kide.gpt.provide") 3 | 4 | ---@class kide.gpt.Chat 5 | ---@field icon string 6 | ---@field title string 7 | ---@field client gpt.Client 8 | ---@field type string 9 | ---@field chatwin? number 10 | ---@field chatbuf? number 11 | ---@field codebuf? number 12 | ---@field chatclosed? boolean 13 | ---@field cursormoved? boolean 14 | ---@field chatruning? boolean 15 | ---@field winleave? boolean 16 | ---@field callback function 17 | ---@field chat_last string[] 18 | ---@field system_prompt string 19 | ---@field user_title string 20 | ---@field system_title string 21 | local Chat = {} 22 | Chat.__index = Chat 23 | 24 | function M.new(opts) 25 | local self = setmetatable({}, Chat) 26 | self.icon = opts.icon 27 | self.title = opts.title 28 | self.type = opts.type 29 | self.chatwin = nil 30 | self.chatbuf = nil 31 | self.codebuf = nil 32 | self.chatclosed = true 33 | self.cursormoved = false 34 | self.chatruning = false 35 | self.winleave = false 36 | self.callback = opts.callback 37 | self.chat_last = {} 38 | self.system_prompt = opts.system_prompt 39 | self.user_title = opts.user_title or M.chat_config.user_title 40 | self.system_title = opts.system_title or M.chat_config.system_title 41 | return self 42 | end 43 | 44 | M.chat_config = { 45 | user_title = " :", 46 | system_title = " :", 47 | system_prompt = "You are a general AI assistant.\n\n" 48 | .. "The user provided the additional info about how they would like you to respond:\n\n" 49 | .. "- If you're unsure don't guess and say you don't know instead.\n" 50 | .. "- Ask question if you need clarification to provide better answer.\n" 51 | .. "- Think deeply and carefully from first principles step by step.\n" 52 | .. "- Zoom out first to see the big picture and then zoom in to details.\n" 53 | .. "- Use Socratic method to improve your thinking and coding skills.\n" 54 | .. "- Don't elide any code from your output if the answer requires coding.\n" 55 | .. "- Take a deep breath; You've got this!\n" 56 | .. "- All non-code text responses must be written in the Chinese language indicated.", 57 | } 58 | 59 | local function disable_start() 60 | vim.cmd("TSBufDisable highlight") 61 | vim.cmd("RenderMarkdown disable") 62 | end 63 | 64 | local function enable_done() 65 | vim.cmd("TSBufEnable highlight") 66 | vim.cmd("RenderMarkdown enable") 67 | vim.cmd("normal! G$") 68 | end 69 | 70 | function Chat:request() 71 | if self.chatruning then 72 | vim.api.nvim_put({ "", self.user_title, "" }, "c", true, true) 73 | self.chatruning = false 74 | self.client:close() 75 | enable_done() 76 | return 77 | end 78 | self.chatruning = true 79 | ---@diagnostic disable-next-line: param-type-mismatch 80 | local list = vim.api.nvim_buf_get_lines(self.chatbuf, 0, -1, false) 81 | local messages = { 82 | { 83 | content = "", 84 | role = "system", 85 | }, 86 | } 87 | 88 | messages[1].content = self.system_prompt 89 | -- 1 user, 2 assistant 90 | local flag = 0 91 | local chat_msg = "" 92 | local chat_count = 1 93 | for _, v in ipairs(list) do 94 | if vim.startswith(v, self.system_title) then 95 | flag = 2 96 | chat_msg = "" 97 | chat_count = chat_count + 1 98 | elseif vim.startswith(v, self.user_title) then 99 | chat_msg = "" 100 | flag = 1 101 | chat_count = chat_count + 1 102 | else 103 | chat_msg = chat_msg .. "\n" .. v 104 | messages[chat_count] = { 105 | content = chat_msg, 106 | role = flag == 1 and "user" or "assistant", 107 | } 108 | end 109 | end 110 | -- 跳转到最后一行 111 | vim.cmd("normal! G$") 112 | disable_start() 113 | vim.api.nvim_put({ "", self.system_title, "" }, "l", true, true) 114 | 115 | self.client:request(messages, self.callback(self)) 116 | end 117 | 118 | ---@param state kide.gpt.Chat 119 | ---@return function 120 | local gpt_chat_callback = function(state) 121 | ---@param opt gpt.Event 122 | return function(opt) 123 | local data = opt.data 124 | local done = opt.done 125 | if opt.usage then 126 | require("kide").gpt_stl( 127 | state.chatbuf, 128 | state.icon, 129 | state.title, 130 | require("kide.gpt.toole").usage_str(state.client.model, opt.usage) 131 | ) 132 | end 133 | if state.chatclosed or state.chatruning == false then 134 | state.client:close() 135 | enable_done() 136 | return 137 | end 138 | if opt.exit == 1 then 139 | vim.notify("AI respond Error: " .. opt.data, vim.log.levels.WARN) 140 | enable_done() 141 | return 142 | end 143 | if state.winleave then 144 | -- 防止回答问题时光标已经移动走了 145 | vim.api.nvim_set_current_win(state.chatwin) 146 | state.winleave = false 147 | end 148 | if state.cursormoved then 149 | -- 防止光标移动打乱回答顺序, 总是移动到最后一行 150 | vim.cmd("normal! G$") 151 | state.cursormoved = false 152 | end 153 | if done then 154 | vim.api.nvim_put({ "", "", state.user_title, "" }, "c", true, true) 155 | state.chatruning = false 156 | state.chat_last = vim.api.nvim_buf_get_lines(state.chatbuf, 0, -1, true) 157 | enable_done() 158 | return 159 | end 160 | if state.chatbuf and vim.api.nvim_buf_is_valid(state.chatbuf) then 161 | if data and data:match("\n") then 162 | local ln = vim.split(data, "\n") 163 | vim.api.nvim_put(ln, "c", true, true) 164 | else 165 | vim.api.nvim_put({ data }, "c", true, true) 166 | end 167 | end 168 | end 169 | end 170 | 171 | ---@param state kide.gpt.Chat 172 | local gpt_reasoner_callback = function(state) 173 | local reasoning = 0 174 | ---@param opt gpt.Event 175 | return function(opt) 176 | if opt.usage then 177 | require("kide").gpt_stl( 178 | state.chatbuf, 179 | state.icon, 180 | state.title, 181 | require("kide.gpt.toole").usage_str(state.client.model, opt.usage) 182 | ) 183 | end 184 | local data 185 | if opt.reasoning and opt.reasoning ~= vim.NIL then 186 | data = opt.reasoning 187 | if reasoning == 0 then 188 | reasoning = 1 189 | end 190 | elseif opt.data and opt.data ~= vim.NIL then 191 | if reasoning == 2 then 192 | reasoning = 3 193 | end 194 | data = opt.data 195 | end 196 | local done = opt.done 197 | if state.chatclosed or state.chatruning == false then 198 | state.client:close() 199 | enable_done() 200 | return 201 | end 202 | if opt.exit == 1 then 203 | vim.notify("AI respond Error: " .. opt.data, vim.log.levels.WARN) 204 | enable_done() 205 | return 206 | end 207 | if state.winleave then 208 | -- 防止回答问题时光标已经移动走了 209 | vim.api.nvim_set_current_win(state.chatwin) 210 | state.winleave = false 211 | end 212 | if state.cursormoved then 213 | -- 防止光标移动打乱回答顺序, 总是移动到最后一行 214 | vim.cmd("normal! G$") 215 | state.cursormoved = false 216 | end 217 | if done then 218 | vim.api.nvim_put({ "", "", state.user_title, "" }, "c", true, true) 219 | state.chatruning = false 220 | state.chat_last = vim.api.nvim_buf_get_lines(state.chatbuf, 0, -1, true) 221 | enable_done() 222 | return 223 | end 224 | if state.chatbuf and vim.api.nvim_buf_is_valid(state.chatbuf) then 225 | data = data or "" 226 | if reasoning == 1 then 227 | reasoning = 2 228 | vim.api.nvim_put({ "", "```text", "" }, "c", true, true) 229 | end 230 | if reasoning == 3 then 231 | reasoning = 4 232 | vim.api.nvim_put({ "", "```", "", "---", "" }, "c", true, true) 233 | end 234 | if data:match("\n") then 235 | local ln = vim.split(data, "\n") 236 | vim.api.nvim_put(ln, "c", true, true) 237 | else 238 | vim.api.nvim_put({ data }, "c", true, true) 239 | end 240 | end 241 | end 242 | end 243 | 244 | function Chat:close_gpt_win() 245 | if self.chatwin then 246 | pcall(vim.api.nvim_win_close, self.chatwin, true) 247 | self.chatwin = nil 248 | self.chatbuf = nil 249 | self.codebuf = nil 250 | self.chatclosed = true 251 | self.cursormoved = false 252 | self.chatruning = false 253 | self.winleave = false 254 | self.client:close() 255 | end 256 | end 257 | 258 | function Chat:create_gpt_win() 259 | self.client = gpt_provide.new_client(self.type) 260 | self.codebuf = vim.api.nvim_get_current_buf() 261 | vim.cmd("belowright new") 262 | self.chatwin = vim.api.nvim_get_current_win() 263 | self.chatbuf = vim.api.nvim_get_current_buf() 264 | vim.bo[self.chatbuf].buftype = "nofile" 265 | vim.bo[self.chatbuf].bufhidden = "wipe" 266 | vim.bo[self.chatbuf].buflisted = false 267 | vim.bo[self.chatbuf].swapfile = false 268 | vim.bo[self.chatbuf].filetype = "markdown" 269 | vim.api.nvim_put({ self.user_title, "" }, "c", true, true) 270 | self.chatclosed = false 271 | 272 | vim.keymap.set("n", "q", function() 273 | self.chatclosed = true 274 | self:close_gpt_win() 275 | end, { noremap = true, silent = true, buffer = self.chatbuf }) 276 | vim.keymap.set("n", "", function() 277 | self:request() 278 | end, { noremap = true, silent = true, buffer = self.chatbuf }) 279 | vim.keymap.set("i", "", function() 280 | vim.cmd("stopinsert") 281 | self:request() 282 | end, { noremap = true, silent = true, buffer = self.chatbuf }) 283 | 284 | vim.api.nvim_buf_create_user_command(self.chatbuf, "GptSend", function() 285 | self:request() 286 | end, { desc = "Gpt Send" }) 287 | 288 | vim.api.nvim_create_autocmd("BufWipeout", { 289 | buffer = self.chatbuf, 290 | callback = function() 291 | self:close_gpt_win() 292 | end, 293 | }) 294 | 295 | vim.api.nvim_create_autocmd("WinClosed", { 296 | buffer = self.chatbuf, 297 | callback = function() 298 | self:close_gpt_win() 299 | end, 300 | }) 301 | vim.api.nvim_create_autocmd("WinLeave", { 302 | buffer = self.chatbuf, 303 | callback = function() 304 | self.winleave = true 305 | end, 306 | }) 307 | 308 | vim.api.nvim_create_autocmd("CursorMoved", { 309 | buffer = self.chatbuf, 310 | callback = function() 311 | self.cursormoved = true 312 | end, 313 | }) 314 | require("kide").gpt_stl(self.chatbuf, self.icon, self.title) 315 | end 316 | 317 | function Chat:code_question(selection) 318 | if not selection then 319 | return 320 | end 321 | local qs 322 | ---@diagnostic disable-next-line: param-type-mismatch 323 | if vim.api.nvim_buf_is_valid(self.codebuf) then 324 | local filetype = vim.bo[self.codebuf].filetype or "text" 325 | local filename = require("kide.stl").format_uri(vim.uri_from_bufnr(self.codebuf)) 326 | qs = { 327 | "请解释`" .. filename .. "`文件中的这段代码", 328 | "```" .. filetype, 329 | } 330 | else 331 | qs = { 332 | "请解释这段代码", 333 | "```", 334 | } 335 | end 336 | vim.list_extend(qs, selection) 337 | table.insert(qs, "```") 338 | table.insert(qs, "") 339 | vim.api.nvim_put(qs, "c", true, true) 340 | end 341 | 342 | function Chat:question(question) 343 | vim.api.nvim_put({ question, "" }, "c", true, true) 344 | end 345 | 346 | ---@param param kai.gpt.ChatParam 347 | function Chat:diagnostics(param) 348 | local diagnostics = param.diagnostics 349 | if not diagnostics then 350 | return 351 | end 352 | local need_code = not param.code 353 | local qs = { 354 | "请解释以下诊断信息并给出修复方案:", 355 | } 356 | local filetype = vim.bo[self.codebuf].filetype or "text" 357 | for _, diagnostic in ipairs(diagnostics) do 358 | local code = diagnostic.code or "Unknown Code" 359 | local severity = diagnostic.severity == 1 and "ERROR" or diagnostic.severity == 2 and "WARN" or "INFO" 360 | table.insert(qs, "## " .. severity .. ": " .. code) 361 | if need_code then 362 | local lines = vim.api.nvim_buf_get_lines(self.codebuf, diagnostic.lnum, diagnostic.end_lnum + 1, false) 363 | if #lines > 0 then 364 | table.insert(qs, "- Code Snippet") 365 | table.insert(qs, "```" .. filetype) 366 | for _, line in ipairs(lines) do 367 | table.insert(qs, line) 368 | end 369 | table.insert(qs, "```") 370 | end 371 | end 372 | 373 | table.insert(qs, "- Source: " .. (diagnostic.source or "Unknown Source")) 374 | local message = diagnostic.message or "No message provided" 375 | table.insert(qs, "- Diagnostic Message") 376 | table.insert(qs, "```text") 377 | local lines = vim.split(message, "\n") 378 | for _, line in ipairs(lines) do 379 | table.insert(qs, line) 380 | end 381 | table.insert(qs, "```") 382 | end 383 | table.insert(qs, "") 384 | vim.api.nvim_put(qs, "c", true, true) 385 | end 386 | 387 | M.chat = M.new({ 388 | icon = "󰭻", 389 | title = "GptChat", 390 | callback = gpt_chat_callback, 391 | type = "chat", 392 | system_prompt = M.chat_config.system_prompt, 393 | }) 394 | 395 | M.reasoner = M.new({ 396 | icon = "󰍦", 397 | title = "GptReasoner", 398 | callback = gpt_reasoner_callback, 399 | type = "reasoner", 400 | }) 401 | 402 | M.linux = M.new({ 403 | icon = "󰭻", 404 | title = "GptLinux", 405 | callback = gpt_chat_callback, 406 | type = "chat", 407 | system_prompt = "你是一个 Linux 内核专家。\n\n" 408 | .. "帮助我阅读 Linux 内核源代码:\n\n" 409 | .. "- 你需要详细地解释我提供的每行代码。\n" 410 | .. "- 如果你不确定,请不要猜测,而是说你不知道。\n" 411 | .. "- 所有非代码文本回答必须用中文。", 412 | }) 413 | 414 | M.lsp = M.new({ 415 | icon = "󰭻", 416 | title = "GptLsp", 417 | callback = gpt_chat_callback, 418 | type = "chat", 419 | system_prompt = "你是一个编程专家。\n\n" 420 | .. "帮助我解决代码编译问题:\n\n" 421 | .. "- 你需要详细地解释我提供的编译问题。\n" 422 | .. "- 如果你不确定,请不要猜测,而是说你不知道。\n" 423 | .. "- 所有非代码文本回答必须用中文。", 424 | }) 425 | 426 | ---@class kai.gpt.ChatParam 427 | ---@field code? string[] 428 | ---@field question? string 429 | ---@field diagnostics? vim.Diagnostic[] 430 | ---@field last? boolean 431 | ---@field gpt? kide.gpt.Chat 432 | 433 | ---@param gpt kide.gpt.Chat 434 | function M.toggle(param, gpt) 435 | param = param or {} 436 | if not gpt.client then 437 | gpt.client = gpt_provide.new_client(gpt.type) 438 | end 439 | if gpt.chatwin then 440 | gpt:close_gpt_win() 441 | else 442 | gpt:create_gpt_win() 443 | if param.last then 444 | gpt:gpt_last() 445 | return 446 | end 447 | if param.code then 448 | gpt:code_question(param.code) 449 | end 450 | if param.diagnostics then 451 | gpt:diagnostics(param) 452 | end 453 | if param.question then 454 | gpt:question(param.question) 455 | end 456 | end 457 | end 458 | 459 | ---@param param kai.gpt.ChatParam 460 | M.toggle_gpt = function(param) 461 | param = param or {} 462 | local gpt = param.gpt 463 | if not gpt then 464 | gpt = M.chat 465 | end 466 | M.toggle(param, gpt) 467 | end 468 | 469 | function Chat:gpt_last(buf) 470 | if self.chat_last and #self.chat_last > 0 then 471 | vim.api.nvim_buf_set_lines(buf or self.chatbuf, 0, -1, true, self.chat_last) 472 | vim.cmd("normal! G$") 473 | end 474 | end 475 | 476 | return M 477 | -------------------------------------------------------------------------------- /lua/plugins.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | "nvim-treesitter/nvim-treesitter", 4 | lazy = false, 5 | config = function() 6 | require("nvim-treesitter.configs").setup({ 7 | ensure_installed = { "lua", "luadoc", "printf", "vim", "vimdoc" }, 8 | highlight = { 9 | enable = true, 10 | disable = function(_, buf) 11 | local max_filesize = 1024 * 1024 -- 1MB 12 | local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf)) 13 | if ok and stats and stats.size > max_filesize then 14 | return true 15 | end 16 | end, 17 | additional_vim_regex_highlighting = false, 18 | }, 19 | indent = { enable = true }, 20 | }) 21 | end, 22 | }, 23 | { 24 | "nvim-lua/plenary.nvim", 25 | lazy = true, 26 | }, 27 | { 28 | "nvim-tree/nvim-web-devicons", 29 | lazy = true, 30 | }, 31 | 32 | { 33 | "stevearc/conform.nvim", 34 | lazy = false, 35 | opts = { 36 | formatters_by_ft = { 37 | lua = { "stylua" }, 38 | css = { "prettier" }, 39 | html = { "prettier" }, 40 | json = { "jq" }, 41 | json5 = { "prettier" }, 42 | markdown = { "prettier" }, 43 | sql = { "sql_formatter" }, 44 | python = { "black" }, 45 | bash = { "shfmt" }, 46 | sh = { "shfmt" }, 47 | toml = { "taplo" }, 48 | }, 49 | }, 50 | }, 51 | { 52 | "lewis6991/gitsigns.nvim", 53 | lazy = false, 54 | opts = { 55 | signs = { 56 | delete = { text = "󰍵" }, 57 | changedelete = { text = "" }, 58 | }, 59 | }, 60 | }, 61 | { 62 | "saghen/blink.cmp", 63 | lazy = false, -- lazy loading handled internally 64 | dependencies = "rafamadriz/friendly-snippets", 65 | version = "*", 66 | ---@module 'blink.cmp' 67 | ---@type blink.cmp.Config 68 | opts = { 69 | keymap = { 70 | preset = "default", 71 | [""] = { "show", "show_documentation", "hide_documentation" }, 72 | [""] = { "hide", "fallback" }, 73 | [""] = { "accept", "fallback" }, 74 | 75 | [""] = { "snippet_forward", "fallback" }, 76 | [""] = { "snippet_backward", "fallback" }, 77 | 78 | [""] = { "select_prev", "fallback" }, 79 | [""] = { "select_next", "fallback" }, 80 | [""] = { "select_prev", "fallback" }, 81 | [""] = { "select_next", "fallback" }, 82 | 83 | [""] = { "scroll_documentation_up", "fallback" }, 84 | [""] = { "scroll_documentation_down", "fallback" }, 85 | }, 86 | appearance = { 87 | use_nvim_cmp_as_default = false, 88 | nerd_font_variant = "mono", 89 | }, 90 | sources = { 91 | default = { 92 | "lsp", 93 | "path", 94 | "snippets", 95 | "buffer", 96 | "dadbod", 97 | -- "daprepl", 98 | }, 99 | providers = { 100 | dadbod = { name = "Dadbod", module = "vim_dadbod_completion.blink" }, 101 | -- daprepl = { name = "DapRepl", module = "kide.cmp.dap" }, 102 | }, 103 | }, 104 | cmdline = { 105 | keymap = { 106 | preset = "enter", 107 | [""] = { 108 | "show", 109 | "select_next", 110 | "fallback", 111 | }, 112 | [""] = { "select_prev", "fallback" }, 113 | }, 114 | sources = function() 115 | local type = vim.fn.getcmdtype() 116 | -- Search forward and backward 117 | if type == "/" or type == "?" then 118 | return { "buffer" } 119 | end 120 | -- Commands 121 | if type == ":" or type == "@" then 122 | return { "cmdline" } 123 | end 124 | return {} 125 | end, 126 | }, 127 | completion = { 128 | menu = { 129 | auto_show = function(ctx) 130 | return ctx.mode ~= "cmdline" 131 | end, 132 | border = "rounded", 133 | draw = { 134 | components = { 135 | kind_icon = { 136 | ellipsis = false, 137 | text = function(ctx) 138 | return require("kide.lspkind").symbol_map[ctx.kind].icon 139 | end, 140 | highlight = function(ctx) 141 | return require("kide.lspkind").symbol_map[ctx.kind].hl 142 | end, 143 | }, 144 | }, 145 | }, 146 | }, 147 | documentation = { 148 | auto_show = true, 149 | auto_show_delay_ms = 100, 150 | update_delay_ms = 50, 151 | window = { 152 | min_width = 10, 153 | max_width = 60, 154 | max_height = 20, 155 | border = "rounded", 156 | }, 157 | }, 158 | }, 159 | }, 160 | opts_extend = { "sources.default" }, 161 | config = function(_, opts) 162 | require("blink.cmp").setup(opts) 163 | end, 164 | }, 165 | { 166 | "windwp/nvim-autopairs", 167 | event = "InsertEnter", 168 | config = true, 169 | }, 170 | { 171 | "mfussenegger/nvim-lint", 172 | lazy = true, 173 | }, 174 | -- java 175 | { 176 | "mfussenegger/nvim-jdtls", 177 | lazy = true, 178 | }, 179 | { 180 | "JavaHello/spring-boot.nvim", 181 | enabled = vim.g.enable_spring_boot == true, 182 | lazy = true, 183 | dependencies = { 184 | "mfussenegger/nvim-jdtls", 185 | }, 186 | config = false, 187 | }, 188 | { 189 | "JavaHello/java-deps.nvim", 190 | lazy = true, 191 | config = function() 192 | require("java-deps").setup({}) 193 | end, 194 | }, 195 | { 196 | "https://gitlab.com/schrieveslaach/sonarlint.nvim.git", 197 | lazy = false, 198 | enabled = vim.env["SONARLINT_ENABLE"] == "Y", 199 | config = function() 200 | require("kide.lsp.sonarlint").setup() 201 | end, 202 | }, 203 | { 204 | "JavaHello/microprofile.nvim", 205 | enabled = vim.g.enable_quarkus == true, 206 | lazy = true, 207 | config = function() 208 | require("microprofile").setup({ 209 | ls_path = vim.env["NVIM_MICROPROFILE_LS_PATH"], 210 | jdt_extensions_path = vim.env["NVIM_MICROPROFILE_JDT_EXTENSIONS_PATH"], 211 | }) 212 | end, 213 | }, 214 | { 215 | "JavaHello/quarkus.nvim", 216 | enabled = vim.g.enable_quarkus == true, 217 | ft = { "java", "yaml", "jproperties", "html" }, 218 | dependencies = { 219 | "JavaHello/microprofile.nvim", 220 | "mfussenegger/nvim-jdtls", 221 | }, 222 | config = function() 223 | require("quarkus").setup({ 224 | ls_path = vim.env["NVIM_QUARKUS_LS_PATH"], 225 | jdt_extensions_path = vim.env["NVIM_QUARKUS_JDT_EXTENSIONS_PATH"], 226 | microprofile_ext_path = vim.env["NVIM_QUARKUS_MICROPROFILE_EXT_PATH"], 227 | }) 228 | end, 229 | }, 230 | { 231 | "aklt/plantuml-syntax", 232 | ft = "plantuml", 233 | }, 234 | 235 | -- dap 236 | { 237 | "mfussenegger/nvim-dap", 238 | lazy = true, 239 | dependencies = { "theHamsta/nvim-dap-virtual-text" }, 240 | config = function() 241 | local dap = require("dap") 242 | dap.defaults.fallback.focus_terminal = true 243 | require("nvim-dap-virtual-text").setup({}) 244 | -- dap.listeners.after.event_initialized["dapui_config"] = function() 245 | -- dap.repl.open() 246 | -- end 247 | end, 248 | }, 249 | { 250 | "theHamsta/nvim-dap-virtual-text", 251 | lazy = true, 252 | config = false, 253 | }, 254 | 255 | -- python 256 | { 257 | "mfussenegger/nvim-dap-python", 258 | lazy = true, 259 | dependencies = { "mfussenegger/nvim-dap" }, 260 | config = false, 261 | }, 262 | -- Git 263 | { 264 | "tpope/vim-fugitive", 265 | cmd = { "G", "Git" }, 266 | }, 267 | { 268 | "sindrets/diffview.nvim", 269 | cmd = { 270 | "DiffviewClose", 271 | "DiffviewFileHistory", 272 | "DiffviewFocusFiles", 273 | "DiffviewLog", 274 | "DiffviewOpen", 275 | "DiffviewRefresh", 276 | "DiffviewToggleFiles", 277 | }, 278 | opts = { 279 | keymaps = { 280 | view = { 281 | ["q"] = function() 282 | vim.cmd("tabclose") 283 | end, 284 | }, 285 | file_panel = { 286 | ["q"] = function() 287 | vim.cmd("tabclose") 288 | end, 289 | }, 290 | file_history_panel = { 291 | ["q"] = function() 292 | vim.cmd("tabclose") 293 | end, 294 | }, 295 | }, 296 | }, 297 | }, 298 | 299 | -- Note 300 | { 301 | "zk-org/zk-nvim", 302 | cmd = { 303 | "ZkIndex", 304 | "ZkNew", 305 | "ZkNotes", 306 | }, 307 | config = function() 308 | require("zk").setup({ 309 | picker = "select", 310 | lsp = { 311 | config = { 312 | cmd = { "zk", "lsp" }, 313 | name = "zk", 314 | }, 315 | auto_attach = { 316 | enabled = true, 317 | filetypes = { "markdown" }, 318 | }, 319 | }, 320 | }) 321 | end, 322 | }, 323 | 324 | -- 大纲插件 325 | { 326 | "hedyhli/outline.nvim", 327 | cmd = { 328 | "Outline", 329 | }, 330 | opts = { 331 | symbols = { 332 | icon_fetcher = function(k) 333 | return require("kide.icons")[k] 334 | end, 335 | }, 336 | providers = { 337 | lsp = { 338 | blacklist_clients = { "spring-boot" }, 339 | }, 340 | }, 341 | }, 342 | }, 343 | 344 | -- mackdown 预览插件 345 | { 346 | "iamcco/markdown-preview.nvim", 347 | ft = "markdown", 348 | build = "cd app && yarn install", 349 | init = function() 350 | vim.g.mkdp_page_title = "${name}" 351 | end, 352 | config = function() end, 353 | }, 354 | 355 | -- databases 356 | { 357 | "tpope/vim-dadbod", 358 | lazy = true, 359 | }, 360 | { 361 | "kristijanhusak/vim-dadbod-ui", 362 | dependencies = { 363 | { "tpope/vim-dadbod", lazy = true }, 364 | { 365 | "kristijanhusak/vim-dadbod-completion", 366 | dependencies = { "tpope/vim-dadbod" }, 367 | ft = { "sql", "mysql", "plsql" }, 368 | lazy = true, 369 | }, 370 | }, 371 | cmd = { 372 | "DBUI", 373 | "DBUIToggle", 374 | }, 375 | init = function() 376 | vim.g.db_ui_use_nerd_fonts = 1 377 | end, 378 | }, 379 | 380 | -- bqf 381 | { 382 | "kevinhwang91/nvim-bqf", 383 | ft = "qf", 384 | config = function() 385 | require("bqf").setup({ 386 | preview = { 387 | auto_preview = true, 388 | should_preview_cb = function(pbufnr, _) 389 | local fname = vim.fn.bufname(pbufnr) 390 | if vim.startswith(fname, "jdt://") then 391 | -- 未加载时不预览 392 | return vim.fn.bufloaded(pbufnr) == 1 393 | end 394 | return true 395 | end, 396 | }, 397 | filter = { 398 | fzf = { 399 | extra_opts = { "--bind", "ctrl-o:toggle-all", "--delimiter", "│" }, 400 | }, 401 | }, 402 | }) 403 | end, 404 | }, 405 | { 406 | "NStefan002/screenkey.nvim", 407 | cmd = { 408 | "Screenkey", 409 | }, 410 | version = "*", 411 | }, 412 | -- ASCII 图 413 | { 414 | "jbyuki/venn.nvim", 415 | lazy = true, 416 | cmd = { "VBox" }, 417 | }, 418 | { 419 | "windwp/nvim-ts-autotag", 420 | ft = { "html" }, 421 | config = function() 422 | require("nvim-ts-autotag").setup({}) 423 | end, 424 | }, 425 | 426 | { 427 | "MeanderingProgrammer/render-markdown.nvim", 428 | dependencies = { "nvim-treesitter/nvim-treesitter" }, 429 | ft = { "markdown" }, 430 | opts = {}, 431 | }, 432 | { 433 | "HakonHarnes/img-clip.nvim", 434 | cmd = { "PasteImage" }, 435 | opts = {}, 436 | }, 437 | { 438 | "folke/snacks.nvim", 439 | priority = 1000, 440 | lazy = false, 441 | ---@type snacks.Config 442 | opts = { 443 | styles = { 444 | input = { 445 | relative = "cursor", 446 | row = 1, 447 | col = 0, 448 | keys = { 449 | i_esc = { "", { "cmp_close", "cancel" }, mode = "i", expr = true }, 450 | }, 451 | }, 452 | }, 453 | bigfile = { enabled = true }, 454 | -- dashboard = { enabled = true }, 455 | explorer = { enabled = true }, 456 | indent = { 457 | enabled = true, 458 | filter = function(buf) 459 | -- return not vim.g.snacks_indent 460 | -- and not vim.b[buf].snacks_indent 461 | -- and vim.bo[buf].buftype == "" 462 | local ft = vim.bo[buf].filetype 463 | if 464 | ft == "snacks_picker_preview" 465 | or ft == "snacks_picker_list" 466 | or ft == "snacks_picker_input" 467 | or ft == "Outline" 468 | or ft == "JavaProjects" 469 | or ft == "text" 470 | or ft == "" 471 | or ft == "lazy" 472 | or ft == "help" 473 | or ft == "markdown" 474 | then 475 | return false 476 | end 477 | return true 478 | end, 479 | }, 480 | input = { enabled = true }, 481 | picker = { 482 | enabled = true, 483 | layout = { 484 | cycle = false, 485 | preset = "dropdown", 486 | }, 487 | layouts = { 488 | dropdown = { 489 | layout = { 490 | backdrop = false, 491 | width = 0.8, 492 | min_width = 80, 493 | height = 0.8, 494 | min_height = 30, 495 | box = "vertical", 496 | border = "rounded", 497 | title = "{title} {live} {flags}", 498 | title_pos = "center", 499 | { win = "input", height = 1, border = "bottom" }, 500 | { win = "list", border = "none" }, 501 | { win = "preview", height = 0.6, border = "top" }, 502 | }, 503 | }, 504 | }, 505 | formatters = { 506 | file = { 507 | truncate = 80, 508 | }, 509 | }, 510 | sources = { 511 | explorer = { 512 | auto_close = true, 513 | layout = { 514 | layout = { 515 | backdrop = false, 516 | width = 0.8, 517 | min_width = 120, 518 | height = 0.8, 519 | border = "rounded", 520 | box = "vertical", 521 | { win = "list", border = "none" }, 522 | { 523 | win = "input", 524 | height = 1, 525 | border = "none", 526 | }, 527 | }, 528 | }, 529 | win = { 530 | list = { 531 | keys = { 532 | ["s"] = "explorer_open", -- open with system application 533 | ["o"] = "confirm", 534 | }, 535 | }, 536 | }, 537 | }, 538 | }, 539 | }, 540 | notifier = { enabled = false }, 541 | quickfile = { enabled = true }, 542 | scope = { enabled = true }, 543 | -- scroll = { enabled = true }, 544 | -- statuscolumn = { enabled = true }, 545 | words = { enabled = true }, 546 | image = { 547 | enabled = true, 548 | }, 549 | }, 550 | }, 551 | } 552 | -------------------------------------------------------------------------------- /lua/mappings.lua: -------------------------------------------------------------------------------- 1 | -- add yours here 2 | 3 | local map = vim.keymap.set 4 | local command = vim.api.nvim_create_user_command 5 | 6 | map("n", "", function() 7 | require("kide.term").toggle() 8 | vim.cmd("startinsert") 9 | end, { desc = "toggle term" }) 10 | map("t", "", require("kide.term").toggle, { desc = "toggle term" }) 11 | map("i", "", function() 12 | vim.cmd("stopinsert") 13 | require("kide.term").toggle() 14 | end, { desc = "toggle term" }) 15 | map("v", "", function() 16 | vim.api.nvim_feedkeys("\027", "xt", false) 17 | local text = require("kide.tools").get_visual_selection() 18 | require("kide.term").toggle() 19 | vim.defer_fn(function() 20 | require("kide.term").send_line(text[1]) 21 | end, 500) 22 | end, { desc = "toggle term" }) 23 | 24 | map("n", "gb", require("gitsigns").blame_line, { desc = "gitsigns blame line" }) 25 | map("n", "", "noh", { desc = "Clear Highlight" }) 26 | 27 | map("n", "", "res +5", { desc = "Resize +5" }) 28 | map("n", "", "res -5", { desc = "Resize -5" }) 29 | map("n", "", "res -5", { desc = "Resize -5" }) 30 | map("n", "", "res +5", { desc = "Resize +5" }) 31 | map("n", "", "vertical resize+5", { desc = "Vertical Resize +5" }) 32 | map("n", "", "vertical resize-5", { desc = "Vertical Resize -5" }) 33 | map("n", "", "vertical resize-5", { desc = "Vertical Resize -5" }) 34 | map("n", "", "vertical resize+5", { desc = "Vertical Resize +5" }) 35 | 36 | vim.keymap.set({ "t", "i" }, "", "h") 37 | vim.keymap.set({ "t", "i" }, "", "j") 38 | vim.keymap.set({ "t", "i" }, "", "k") 39 | vim.keymap.set({ "t", "i" }, "", "l") 40 | vim.keymap.set({ "n" }, "", "h") 41 | vim.keymap.set({ "n" }, "", "j") 42 | vim.keymap.set({ "n" }, "", "k") 43 | vim.keymap.set({ "n" }, "", "l") 44 | -- terminal 45 | map("t", "", "", { desc = "terminal escape terminal mode" }) 46 | 47 | -- dap 48 | map("n", "", function() 49 | require("dap").continue() 50 | end, { 51 | desc = "Dap continue", 52 | }) 53 | map("n", "", function() 54 | require("dap").step_over() 55 | end, { 56 | desc = "Dap step_over", 57 | }) 58 | map("n", "", function() 59 | require("dap").step_into() 60 | end, { 61 | desc = "Dap step_into", 62 | }) 63 | map("n", "", function() 64 | require("dap").step_out() 65 | end, { 66 | desc = "Dap step_out", 67 | }) 68 | map("n", "db", function() 69 | require("dap").toggle_breakpoint() 70 | end, { desc = "Dap toggle breakpoint" }) 71 | map("n", "dB", function() 72 | require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) 73 | end, { desc = "Dap breakpoint condition" }) 74 | map("n", "dl", function() 75 | require("dap").run_last() 76 | end, { 77 | desc = "Dap run last", 78 | }) 79 | map("n", "lp", function() 80 | require("dap").set_breakpoint(nil, nil, vim.fn.input("Log point message: ")) 81 | end, { 82 | desc = "Dap set_breakpoint", 83 | }) 84 | map("n", "dr", function() 85 | require("dap").repl.open() 86 | end, { 87 | desc = "Dap repl open", 88 | }) 89 | map({ "n", "v" }, "dh", function() 90 | require("dap.ui.widgets").hover() 91 | end, { 92 | desc = "Dap hover", 93 | }) 94 | map({ "n", "v" }, "dp", function() 95 | require("dap.ui.widgets").preview() 96 | end, { 97 | desc = "Dap preview", 98 | }) 99 | map("n", "df", function() 100 | local widgets = require("dap.ui.widgets") 101 | widgets.centered_float(widgets.frames) 102 | end, { 103 | desc = "Dap centered_float frames", 104 | }) 105 | map("n", "dv", function() 106 | local widgets = require("dap.ui.widgets") 107 | widgets.centered_float(widgets.scopes) 108 | end, { 109 | desc = "Dap centered_float scopes", 110 | }) 111 | 112 | map("n", "e", function() 113 | Snacks.explorer.open({}) 114 | end, { desc = "files", silent = true, noremap = true }) 115 | 116 | -- outline 117 | map("n", "o", "Outline", { desc = "Symbols Outline" }) 118 | 119 | -- task 120 | command("TaskRun", function() 121 | require("kide.term").input_run(false) 122 | end, { desc = "Task Run" }) 123 | 124 | command("TaskRunLast", function() 125 | require("kide.term").input_run(true) 126 | end, { desc = "Restart Last Task" }) 127 | 128 | map("n", "", function() 129 | require("conform").format({ lsp_fallback = true }) 130 | end, { desc = "format file" }) 131 | map("v", "", function() 132 | vim.api.nvim_feedkeys("\027", "xt", false) 133 | local start_pos = vim.api.nvim_buf_get_mark(0, "<") 134 | local end_pos = vim.api.nvim_buf_get_mark(0, ">") 135 | require("conform").format({ 136 | range = { 137 | start = start_pos, 138 | ["end"] = end_pos, 139 | }, 140 | lsp_fallback = true, 141 | }) 142 | end, { desc = "format range", silent = true, noremap = true }) 143 | 144 | -- Git 145 | map("n", "]c", function() 146 | local gs = require("gitsigns") 147 | if vim.wo.diff then 148 | return "]c" 149 | end 150 | vim.schedule(function() 151 | gs.next_hunk() 152 | end) 153 | return "" 154 | end, { expr = true, desc = "Git Next Hunk" }) 155 | 156 | map("n", "[c", function() 157 | local gs = require("gitsigns") 158 | if vim.wo.diff then 159 | return "[c" 160 | end 161 | vim.schedule(function() 162 | gs.prev_hunk() 163 | end) 164 | return "" 165 | end, { expr = true, desc = "Git Prev Hunk" }) 166 | 167 | map("n", "[e", function() 168 | vim.diagnostic.jump({ count = -1, severity = vim.diagnostic.severity.ERROR, float = true }) 169 | end, { desc = "Jump to the previous diagnostic error" }) 170 | map("n", "]e", function() 171 | vim.diagnostic.jump({ count = 1, severity = vim.diagnostic.severity.ERROR, float = true }) 172 | end, { desc = "Jump to the next diagnostic error" }) 173 | map("n", "go", vim.diagnostic.open_float, { desc = "Open float Diagnostics" }) 174 | 175 | -- quickfix next/prev 176 | -- map("n", "]q", "cnext", { desc = "Quickfix Next" }) 177 | -- map("n", "[q", "cprev", { desc = "Quickfix Prev" }) 178 | 179 | -- local list next/prev 180 | -- map("n", "]l", "lnext", { desc = "Location List Next" }) 181 | -- map("n", "[l", "lprev", { desc = "Location List Prev" }) 182 | 183 | command("InlayHint", function() 184 | vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({})) 185 | end, { desc = "LSP Inlay Hint" }) 186 | command("CodeLens", function() 187 | vim.lsp.codelens.refresh() 188 | end, { desc = "LSP CodeLens" }) 189 | command("CodeLensClear", function() 190 | vim.lsp.codelens.clear() 191 | end, { desc = "LSP CodeLens" }) 192 | 193 | command("LspDocumentSymbols", function(_) 194 | vim.lsp.buf.document_symbol() 195 | end, { 196 | desc = "Lsp Document Symbols", 197 | nargs = 0, 198 | range = true, 199 | }) 200 | 201 | command("LspWorkspaceSymbols", function(opts) 202 | if opts.range > 0 then 203 | local text = require("kide.tools").get_visual_selection() 204 | vim.lsp.buf.workspace_symbol(text[1]) 205 | else 206 | vim.lsp.buf.workspace_symbol(opts.args) 207 | end 208 | end, { 209 | desc = "Lsp Workspace Symbols", 210 | nargs = "?", 211 | range = true, 212 | }) 213 | 214 | local severity_key = { 215 | "ERROR", 216 | "WARN", 217 | "INFO", 218 | "HINT", 219 | } 220 | command("DiagnosticsWorkspace", function(opts) 221 | local level = opts.args 222 | if level == nil or level == "" then 223 | vim.diagnostic.setqflist() 224 | else 225 | vim.diagnostic.setqflist({ severity = level }) 226 | end 227 | end, { 228 | desc = "Diagnostics Workspace", 229 | nargs = "?", 230 | complete = function(al, _, _) 231 | return vim.tbl_filter(function(item) 232 | return vim.startswith(item, al) 233 | end, severity_key) 234 | end, 235 | }) 236 | command("DiagnosticsDocument", function(opts) 237 | local level = opts.args 238 | if level == nil or level == "" then 239 | vim.diagnostic.setloclist() 240 | else 241 | vim.diagnostic.setloclist({ severity = level }) 242 | end 243 | end, { 244 | desc = "Diagnostics Document", 245 | nargs = "?", 246 | complete = function(al, _, _) 247 | return vim.tbl_filter(function(item) 248 | return vim.startswith(item, al) 249 | end, severity_key) 250 | end, 251 | }) 252 | 253 | -- find files 254 | if vim.fn.executable("fd") == 1 then 255 | command("Fd", function(opt) 256 | vim.fn.setqflist({}, " ", { lines = vim.fn.systemlist("fd --type file " .. opt.args), efm = "%f" }) 257 | vim.cmd("botright copen") 258 | end, { 259 | desc = "find files", 260 | nargs = "?", 261 | }) 262 | end 263 | if vim.fn.executable("find") == 1 then 264 | command("Find", function(opt) 265 | vim.fn.setqflist({}, " ", { lines = vim.fn.systemlist("find . -type f -iname '" .. opt.args .. "'"), efm = "%f" }) 266 | vim.cmd("botright copen") 267 | end, { 268 | desc = "find files", 269 | nargs = 1, 270 | }) 271 | end 272 | command("CloseOtherBufs", function(_) 273 | local bufs = vim.api.nvim_list_bufs() 274 | local cur = vim.api.nvim_get_current_buf() 275 | for _, v in ipairs(bufs) do 276 | if vim.bo[v].buflisted and cur ~= v then 277 | local ok = pcall(vim.api.nvim_buf_delete, v, { force = false, unload = false }) 278 | if not ok then 279 | vim.cmd("b " .. v) 280 | return 281 | end 282 | end 283 | end 284 | end, { 285 | desc = "find files", 286 | nargs = 0, 287 | }) 288 | 289 | map("n", "fq", function() 290 | Snacks.picker.qflist() 291 | end, { desc = "Quickfix" }) 292 | 293 | map("n", "fb", function() 294 | Snacks.picker.buffers() 295 | end, { desc = "Find buffer" }) 296 | map("n", "ff", function() 297 | Snacks.picker.files() 298 | end, { desc = "Find files" }) 299 | 300 | map("n", "fd", function() 301 | Snacks.picker.diagnostics() 302 | end, { desc = "Find diagnostics" }) 303 | 304 | map("v", "ff", function() 305 | vim.api.nvim_feedkeys("\027", "xt", false) 306 | local text = require("kide.tools").get_visual_selection() 307 | local param = text[1] 308 | Snacks.picker.files({ search = param }) 309 | end, { desc = "find files", silent = true, noremap = true }) 310 | 311 | map("v", "fw", function() 312 | vim.api.nvim_feedkeys("\027", "xt", false) 313 | local text = require("kide.tools").get_visual_selection() 314 | local param = text[1] 315 | Snacks.picker.grep({ search = param }) 316 | end, { desc = "live grep", silent = true, noremap = true }) 317 | map("n", "fw", function() 318 | Snacks.picker.grep() 319 | end, { desc = "live grep", silent = true, noremap = true }) 320 | 321 | if vim.base64 then 322 | command("Base64Encode", function(opt) 323 | local text 324 | if opt.range > 0 then 325 | text = require("kide.tools").get_visual_selection() 326 | text = table.concat(text, "\n") 327 | else 328 | text = opt.args 329 | end 330 | vim.notify(vim.base64.encode(text), vim.log.levels.INFO) 331 | end, { 332 | desc = "base64 encode", 333 | nargs = "?", 334 | range = true, 335 | }) 336 | command("Base64Decode", function(opt) 337 | local text 338 | if opt.range > 0 then 339 | text = require("kide.tools").get_visual_selection() 340 | text = table.concat(text, "\n") 341 | else 342 | text = opt.args 343 | end 344 | text = require("kide.tools").base64_url_safe_to_std(text) 345 | vim.notify(vim.base64.decode(text), vim.log.levels.INFO) 346 | end, { 347 | desc = "base64 decode", 348 | nargs = "?", 349 | range = true, 350 | }) 351 | end 352 | 353 | local function creat_trans_command(name, from, to) 354 | command(name, function(opt) 355 | local text 356 | if opt.range > 0 then 357 | text = require("kide.tools").get_visual_selection() 358 | text = table.concat(text, "\n") 359 | else 360 | text = opt.args 361 | end 362 | require("kide.gpt.translate").translate_float({ text = text, from = from, to = to }) 363 | end, { 364 | desc = "translate", 365 | nargs = "?", 366 | range = true, 367 | }) 368 | end 369 | 370 | creat_trans_command("TransAutoZh", "auto", "中文") 371 | map("v", "tc", function() 372 | vim.api.nvim_feedkeys("\027", "xt", false) 373 | local text = require("kide.tools").get_visual_selection() 374 | require("kide.gpt.translate").translate_float({ text = table.concat(text, "\n"), from = "auto", to = "中文" }) 375 | end, {}) 376 | creat_trans_command("TransEnZh", "英语", "中文") 377 | creat_trans_command("TransZhEn", "中文", "英语") 378 | creat_trans_command("TransIdZh", "印尼语", "中文") 379 | 380 | command("GptChat", function(opt) 381 | local q 382 | local code 383 | if opt.range > 0 then 384 | code = require("kide.tools").get_visual_selection() 385 | end 386 | if opt.args and opt.args ~= "" then 387 | q = opt.args 388 | end 389 | require("kide.gpt.chat").toggle_gpt({ 390 | code = code, 391 | question = q, 392 | }) 393 | end, { 394 | desc = "GptChat", 395 | nargs = "*", 396 | range = true, 397 | }) 398 | command("GptLast", function(opt) 399 | require("kide.gpt.chat").toggle_gpt({ 400 | last = true, 401 | }) 402 | end, { 403 | desc = "Gpt", 404 | nargs = "*", 405 | range = true, 406 | }) 407 | 408 | command("Gpt", function(opt) 409 | local args = opt.args 410 | local code 411 | if opt.range > 0 then 412 | code = require("kide.tools").get_visual_selection() 413 | end 414 | if args and args ~= "" then 415 | if args == "linux" then 416 | require("kide.gpt.chat").toggle_gpt({ 417 | gpt = require("kide.gpt.chat").linux, 418 | code = code, 419 | }) 420 | elseif args == "lsp" then 421 | local cursor = vim.api.nvim_win_get_cursor(0) 422 | local diagnostics = vim.diagnostic.get(0, { 423 | lnum = cursor[1] - 1, 424 | }) 425 | if #diagnostics > 0 then 426 | require("kide.gpt.chat").toggle_gpt({ 427 | gpt = require("kide.gpt.chat").lsp, 428 | code = code, 429 | diagnostics = diagnostics, 430 | }) 431 | else 432 | vim.notify("没有诊断信息", vim.log.levels.INFO) 433 | end 434 | else 435 | vim.notify("没有指定助手类型: " .. args, vim.log.levels.WARN) 436 | end 437 | else 438 | vim.notify("没有指定助手类型", vim.log.levels.WARN) 439 | end 440 | end, { 441 | desc = "Gpt Assistant", 442 | nargs = 1, 443 | range = true, 444 | complete = function() 445 | return { "linux", "lsp" } 446 | end, 447 | }) 448 | 449 | command("GptReasoner", function(opt) 450 | local q 451 | local code 452 | if opt.range > 0 then 453 | code = require("kide.tools").get_visual_selection() 454 | end 455 | if opt.args and opt.args ~= "" then 456 | q = opt.args 457 | end 458 | require("kide.gpt.chat").toggle_gpt({ 459 | gpt = require("kide.gpt.chat").reasoner, 460 | code = code, 461 | question = q, 462 | }) 463 | end, { 464 | desc = "Gpt", 465 | nargs = "*", 466 | range = true, 467 | }) 468 | 469 | command("GptProvider", function(opt) 470 | if opt.args and opt.args ~= "" then 471 | require("kide.gpt.provide").select_provide(opt.args) 472 | else 473 | vim.ui.select(require("kide.gpt.provide").provide_keys(), { 474 | prompt = "Select GPT Provides:", 475 | format_item = function(item) 476 | return item 477 | end, 478 | }, function(c) 479 | require("kide.gpt.provide").select_provide(c) 480 | end) 481 | end 482 | end, { 483 | desc = "GptProvider", 484 | nargs = "?", 485 | range = false, 486 | complete = function() 487 | return require("kide.gpt.provide").provide_keys() 488 | end, 489 | }) 490 | 491 | command("GptModels", function(_) 492 | vim.ui.select(require("kide.gpt.provide").models(), { 493 | prompt = "Select GPT Models:", 494 | format_item = function(item) 495 | return item 496 | end, 497 | }, function(c) 498 | require("kide.gpt.provide").select_model(c) 499 | end) 500 | end, { 501 | desc = "GptModels", 502 | nargs = 0, 503 | range = false, 504 | }) 505 | 506 | command("LspInfo", function(_) 507 | require("kide.lspui").open_info() 508 | end, { 509 | desc = "Lsp info", 510 | nargs = 0, 511 | range = false, 512 | }) 513 | 514 | command("NotificationHistory", function(_) 515 | Snacks.notifier.show_history() 516 | end, { 517 | desc = "Notification History", 518 | nargs = 0, 519 | range = false, 520 | }) 521 | 522 | command("LspLog", function(_) 523 | vim.cmd("tabedit " .. vim.lsp.log.get_filename()) 524 | vim.cmd("normal! G") 525 | end, { 526 | desc = "Lsp log", 527 | nargs = 0, 528 | range = false, 529 | }) 530 | 531 | command("Go", function(opt) 532 | local cmd = { "go" } 533 | if opt.args and opt.args ~= "" then 534 | vim.list_extend(cmd, vim.split(opt.args, " ")) 535 | end 536 | require("kide.term").toggle(cmd) 537 | end, { 538 | desc = "Go cmd", 539 | nargs = "*", 540 | range = false, 541 | complete = "file", 542 | }) 543 | command("ImageHover", function() 544 | Snacks.image.hover() 545 | end, { 546 | desc = "Image Hover", 547 | nargs = 0, 548 | range = false, 549 | }) 550 | 551 | if vim.fn.executable("cargo-owlsp") == 1 then 552 | map("n", "", require("kide.lsp.rustowl").rustowl_cursor, { noremap = true, silent = true }) 553 | end 554 | 555 | command("Codex", function() 556 | require("kide.codex").codex() 557 | end, { 558 | desc = "Codex cmd", 559 | nargs = 0, 560 | range = false, 561 | }) 562 | 563 | map({ "i", "n", "t" }, "", function() 564 | require("kide.codex").codex() 565 | end, { desc = "Codex cmd" }) 566 | 567 | require("kide.tools").setup() 568 | require("kide.tools.maven").setup() 569 | require("kide.tools.plantuml").setup() 570 | require("kide.tools.mermaid").setup() 571 | require("kide.tools.curl").setup() 572 | require("kide.gpt.commit").setup() 573 | require("kide.gpt.code").setup() 574 | -------------------------------------------------------------------------------- /lua/kide/lsp/jdtls.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local env = { 3 | HOME = vim.env["HOME"], 4 | JAVA_HOME = vim.env["JAVA_HOME"], 5 | JDTLS_RUN_JAVA = vim.env["JDTLS_RUN_JAVA"], 6 | JDTLS_HOME = vim.env["JDTLS_HOME"], 7 | JDTLS_WORKSPACE = vim.env["JDTLS_WORKSPACE"], 8 | JOL_JAR = vim.env["JOL_JAR"], 9 | } 10 | local vscode = require("kide.tools.vscode") 11 | local mason, _ = pcall(require, "mason-registry") 12 | -- local jdtls_path = vscode.find_one("/redhat.java-*/server") 13 | local function get_jdtls_path() 14 | local jdtls_path = env.JDTLS_HOME or vscode.find_one("/redhat.java-*/server") 15 | 16 | if not jdtls_path then 17 | if mason and require("mason-registry").has_package("jdtls") then 18 | jdtls_path = require("mason-registry").get_package("jdtls"):get_install_path() 19 | end 20 | end 21 | return jdtls_path 22 | end 23 | 24 | local jdtls_path = get_jdtls_path() 25 | if not jdtls_path then 26 | return M 27 | end 28 | 29 | local utils = require("kide.tools") 30 | local maven = require("kide.tools.maven") 31 | 32 | local jdtls_java = (function() 33 | local jdtls_run_java = env.JDTLS_RUN_JAVA 34 | if jdtls_run_java then 35 | return jdtls_run_java 36 | end 37 | local java_home = env.JAVA_HOME 38 | if java_home then 39 | return java_home .. "/bin/java" 40 | end 41 | return "java" 42 | end)() 43 | 44 | local function get_java_ver_home(v, dv) 45 | return vim.env["JAVA_" .. v .. "_HOME"] or dv 46 | end 47 | local function get_java_ver_sources(v, dv) 48 | return vim.env["JAVA_" .. v .. "_SOURCES"] or dv 49 | end 50 | 51 | local function get_jdtls_workspace() 52 | return env.JDTLS_WORKSPACE or env.HOME .. "/.jdtls-workspace/" 53 | end 54 | 55 | local function get_jol_jar() 56 | return env.JOL_JAR or "/opt/software/java/jol-cli-0.17-full.jar" 57 | end 58 | 59 | -- see https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request 60 | local ExecutionEnvironment = { 61 | J2SE_1_5 = "J2SE-1.5", 62 | JavaSE_1_6 = "JavaSE-1.6", 63 | JavaSE_1_7 = "JavaSE-1.7", 64 | JavaSE_1_8 = "JavaSE-1.8", 65 | JavaSE_9 = "JavaSE-9", 66 | JavaSE_10 = "JavaSE-10", 67 | JavaSE_11 = "JavaSE-11", 68 | JavaSE_12 = "JavaSE-12", 69 | JavaSE_13 = "JavaSE-13", 70 | JavaSE_14 = "JavaSE-14", 71 | JavaSE_15 = "JavaSE-15", 72 | JavaSE_16 = "JavaSE-16", 73 | JavaSE_17 = "JavaSE-17", 74 | JavaSE_18 = "JavaSE-18", 75 | JavaSE_19 = "JavaSE-19", 76 | JAVASE_20 = "JavaSE-20", 77 | JAVASE_21 = "JavaSE-21", 78 | JAVASE_22 = "JavaSE-22", 79 | JAVASE_23 = "JavaSE-23", 80 | JAVASE_24 = "JavaSE-24", 81 | } 82 | 83 | local function fglob(path) 84 | if path == "" then 85 | return nil 86 | end 87 | return path 88 | end 89 | 90 | local runtimes = (function() 91 | local result = {} 92 | for _, value in pairs(ExecutionEnvironment) do 93 | local version = vim.fn.split(value, "-")[2] 94 | if string.match(version, "%.") then 95 | version = vim.split(version, "%.")[2] 96 | end 97 | local java_home = get_java_ver_home(version) 98 | local default_jdk = false 99 | if java_home then 100 | local java_sources = get_java_ver_sources( 101 | version, 102 | fglob(vim.fn.glob(java_home .. "/src.zip")) or fglob(vim.fn.glob(java_home .. "/lib/src.zip")) 103 | ) 104 | if ExecutionEnvironment.JavaSE_17 == value then 105 | default_jdk = true 106 | end 107 | table.insert(result, { 108 | name = value, 109 | path = java_home, 110 | sources = java_sources, 111 | default = default_jdk, 112 | }) 113 | end 114 | end 115 | if #result == 0 then 116 | vim.notify("Please config Java runtimes (JAVA_17_HOME...)") 117 | end 118 | return result 119 | end)() 120 | 121 | -- local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ":p:h:t") 122 | local root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew" }) 123 | local rwdir = root_dir or vim.fn.getcwd() 124 | local workspace_dir = get_jdtls_workspace() .. require("kide.tools").base64_url_safe(rwdir) 125 | 126 | local function jdtls_launcher() 127 | local jdtls_config = nil 128 | if utils.is_mac then 129 | jdtls_config = "/config_mac" 130 | elseif utils.is_linux then 131 | jdtls_config = "/config_linux" 132 | elseif utils.is_win then 133 | jdtls_config = "/config_win" 134 | else 135 | vim.notify("jdtls: unknown os", vim.log.levels.ERROR) 136 | return nil 137 | end 138 | local lombok_jar = vscode.get_lombok_jar() 139 | local cmd = { 140 | jdtls_java, 141 | "-Declipse.application=org.eclipse.jdt.ls.core.id1", 142 | "-Dosgi.bundles.defaultStartLevel=4", 143 | "-Declipse.product=org.eclipse.jdt.ls.core.product", 144 | "-Dosgi.checkConfiguration=true", 145 | "-Dosgi.sharedConfiguration.area=" .. vim.fn.glob(jdtls_path .. jdtls_config), 146 | "-Dosgi.sharedConfiguration.area.readOnly=true", 147 | "-Dosgi.configuration.cascaded=true", 148 | "-Dlog.protocol=true", 149 | "-Dlog.level=ALL", 150 | "-Xmx4g", 151 | "-XX:+UseZGC", 152 | -- "-XX:+UseTransparentHugePages", 153 | -- "-XX:+AlwaysPreTouch", 154 | "--enable-native-access=ALL-UNNAMED", 155 | "--add-modules=ALL-SYSTEM", 156 | "--add-opens", 157 | "java.base/java.util=ALL-UNNAMED", 158 | "--add-opens", 159 | "java.base/java.lang=ALL-UNNAMED", 160 | "--add-opens", 161 | "java.base/sun.nio.fs=ALL-UNNAMED", 162 | } 163 | if lombok_jar ~= nil then 164 | table.insert(cmd, "-javaagent:" .. lombok_jar) 165 | end 166 | table.insert(cmd, "-jar") 167 | table.insert(cmd, vim.fn.glob(jdtls_path .. "/plugins/org.eclipse.equinox.launcher_*.jar")) 168 | table.insert(cmd, "-data") 169 | table.insert(cmd, workspace_dir) 170 | return cmd 171 | end 172 | 173 | local bundles = {} 174 | -- This bundles definition is the same as in the previous section (java-debug installation) 175 | 176 | local vscode_java_debug_path = (function() 177 | local p = vim.env["JDTLS_JAVA_DEBUG_PATH"] 178 | p = p or vscode.find_one("/vscjava.vscode-java-debug-*/server") 179 | if p then 180 | return p 181 | end 182 | if mason and require("mason-registry").has_package("java-debug-adapter") then 183 | return require("mason-registry").get_package("java-debug-adapter"):get_install_path() .. "/extension/server" 184 | end 185 | end)() 186 | if vscode_java_debug_path then 187 | vim.list_extend( 188 | bundles, 189 | vim.split(vim.fn.glob(vscode_java_debug_path .. "/com.microsoft.java.debug.plugin-*.jar"), "\n") 190 | ) 191 | end 192 | 193 | -- /opt/software/lsp/java/vscode-java-test/server 194 | -- vim.list_extend(bundles, vim.split(vim.fn.glob("/opt/software/lsp/java/vscode-java-test/server/*.jar"), "\n")); 195 | local vscode_java_test_path = (function() 196 | local p = vim.env["JDTLS_JAVA_TEST_PATH"] 197 | p = p or vscode.find_one("/vscjava.vscode-java-test-*/server") 198 | if p then 199 | return p 200 | end 201 | if mason and require("mason-registry").has_package("java-test") then 202 | return require("mason-registry").get_package("java-test"):get_install_path() .. "/extension/server" 203 | end 204 | end)() 205 | if vscode_java_test_path then 206 | for _, bundle in ipairs(vim.split(vim.fn.glob(vscode_java_test_path .. "/*.jar"), "\n")) do 207 | if 208 | not vim.endswith(bundle, "com.microsoft.java.test.runner-jar-with-dependencies.jar") 209 | and not vim.endswith(bundle, "jacocoagent.jar") 210 | then 211 | table.insert(bundles, bundle) 212 | end 213 | end 214 | end 215 | 216 | -- /opt/software/lsp/java/vscode-java-decompiler/server/ 217 | local java_decoompiler_path = (function() 218 | local p = vim.env["JDTLS_JAVA_DECOMPILER_PATH"] 219 | p = p or vscode.find_one("/dgileadi.java-decompiler-*/server") 220 | if p then 221 | return p 222 | end 223 | end)() 224 | if java_decoompiler_path then 225 | vim.list_extend(bundles, vim.split(vim.fn.glob(java_decoompiler_path .. "/*.jar"), "\n")) 226 | end 227 | 228 | -- /opt/software/lsp/java/vscode-java-dependency/jdtls.ext/ 229 | -- vim.list_extend(bundles, vim.split(vim.fn.glob("/opt/software/lsp/java/vscode-java-dependency/jdtls.ext/com.microsoft.jdtls.ext.core/target/com.microsoft.jdtls.ext.core-*.jar"), "\n")); 230 | -- /opt/software/lsp/java/vscode-java-dependency/server/ 231 | local java_dependency_path = (function() 232 | local p = vim.env["JDTLS_JAVA_DEPENDENCY_PATH"] 233 | p = p or vscode.find_one("/vscjava.vscode-java-dependency-*/server") 234 | if p then 235 | return p 236 | end 237 | end)() 238 | if java_dependency_path then 239 | vim.list_extend(bundles, vim.split(vim.fn.glob(java_dependency_path .. "/*.jar"), "\n")) 240 | end 241 | 242 | local vscode_pde_path = vscode.find_one("/yaozheng.vscode-pde-*/server") 243 | if vscode_pde_path and "Y" == vim.env["VSCODE_PDE_ENABLE"] then 244 | vim.list_extend(bundles, vim.split(vim.fn.glob(vscode_pde_path .. "/*.jar"), "\n")) 245 | end 246 | 247 | -- or "https://raw.githubusercontent.com/redhat-developer/vscode-java/refs/heads/main/formatters/eclipse-formatter.xml" 248 | local function fmt_config() 249 | local fmt_path = vim.uv.cwd() .. "/eclipse-formatter.xml" 250 | local has_fmt = vim.uv.fs_stat(fmt_path) 251 | if has_fmt then 252 | return { 253 | url = fmt_path, 254 | profile = "Eclipse", 255 | } 256 | end 257 | return {} 258 | end 259 | 260 | -- vim.notify("SETUP: " .. vim.api.nvim_buf_get_name(vim.api.nvim_get_current_buf()), vim.log.levels.INFO) 261 | -- See `:help vim.lsp.start_client` for an overview of the supported `config` options. 262 | M.config = { 263 | -- The command that starts the language server 264 | -- See: https://github.com/eclipse/eclipse.jdt.ls#running-from-the-command-line 265 | cmd = jdtls_launcher(), 266 | filetypes = { "java" }, 267 | root_dir = root_dir, 268 | 269 | -- Here you can configure eclipse.jdt.ls specific settings 270 | -- See https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request 271 | -- for a list of options 272 | settings = { 273 | java = { 274 | format = { 275 | settings = fmt_config(), 276 | }, 277 | autobuild = { enabled = false }, 278 | maxConcurrentBuilds = 8, 279 | home = env.JAVA_HOME, 280 | project = { 281 | encoding = "UTF-8", 282 | }, 283 | foldingRange = { enabled = true }, 284 | selectionRange = { enabled = true }, 285 | import = { 286 | gradle = { enabled = true }, 287 | maven = { enabled = true }, 288 | exclusions = { 289 | "**/node_modules/**", 290 | "**/.metadata/**", 291 | "**/archetype-resources/**", 292 | "**/META-INF/maven/**", 293 | "**/.git/**", 294 | }, 295 | }, 296 | inlayhints = { 297 | parameterNames = { enabled = "ALL" }, 298 | }, 299 | referenceCodeLens = { enabled = true }, 300 | implementationsCodeLens = { enabled = true }, 301 | templates = { 302 | typeComment = { 303 | "/**", 304 | " * ${type_name}.", 305 | " *", 306 | " * @author ${user}", 307 | " */", 308 | }, 309 | }, 310 | eclipse = { 311 | downloadSources = true, 312 | }, 313 | maven = { 314 | downloadSources = true, 315 | updateSnapshots = true, 316 | }, 317 | signatureHelp = { 318 | enabled = true, 319 | description = { 320 | enabled = true, 321 | }, 322 | }, 323 | contentProvider = { preferred = "fernflower" }, 324 | completion = { 325 | favoriteStaticMembers = { 326 | "org.junit.Assert.*", 327 | "org.junit.Assume.*", 328 | "org.junit.jupiter.api.Assertions.*", 329 | "org.junit.jupiter.api.Assumptions.*", 330 | "org.junit.jupiter.api.DynamicContainer.*", 331 | "org.junit.jupiter.api.DynamicTest.*", 332 | "org.assertj.core.api.Assertions.assertThat", 333 | "org.assertj.core.api.Assertions.assertThatThrownBy", 334 | "org.assertj.core.api.Assertions.assertThatExceptionOfType", 335 | "org.assertj.core.api.Assertions.catchThrowable", 336 | "java.util.Objects.requireNonNull", 337 | "java.util.Objects.requireNonNullElse", 338 | "org.mockito.Mockito.*", 339 | }, 340 | filteredTypes = { 341 | "com.sun.*", 342 | "io.micrometer.shaded.*", 343 | "java.awt.*", 344 | "org.graalvm.*", 345 | "jdk.*", 346 | "sun.*", 347 | }, 348 | importOrder = { 349 | "java", 350 | "javax", 351 | "org", 352 | "com", 353 | }, 354 | }, 355 | sources = { 356 | organizeImports = { 357 | starThreshold = 9999, 358 | staticStarThreshold = 9999, 359 | }, 360 | }, 361 | saveActions = { 362 | organizeImports = true, 363 | }, 364 | configuration = { 365 | maven = { 366 | userSettings = maven.get_maven_settings(), 367 | globalSettings = maven.get_maven_settings(), 368 | }, 369 | runtimes = runtimes, 370 | }, 371 | }, 372 | }, 373 | 374 | -- Language server `initializationOptions` 375 | -- You need to extend the `bundles` with paths to jar files 376 | -- if you want to use additional eclipse.jdt.ls plugins. 377 | -- 378 | -- See https://github.com/mfussenegger/nvim-jdtls#java-debug-installation 379 | -- 380 | -- If you don't plan on using the debugger or other eclipse.jdt.ls plugins you can remove this 381 | -- init_options = { 382 | -- bundles = { 383 | -- vim.fn.glob("/opt/software/lsp/java/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-0.35.0.jar") 384 | -- }, 385 | -- workspace = workspace_dir 386 | -- }, 387 | } 388 | M.config.commands = {} 389 | M.config.commands["_java.reloadBundles.command"] = function() 390 | return {} 391 | end 392 | 393 | local jdtls = require("jdtls") 394 | jdtls.jol_path = get_jol_jar() 395 | 396 | -- local extendedClientCapabilities = jdtls.extendedClientCapabilities 397 | -- extendedClientCapabilities.resolveAdditionalTextEditsSupport = true 398 | -- extendedClientCapabilities.progressReportProvider = false 399 | 400 | M.config["init_options"] = { 401 | bundles = bundles, 402 | extendedClientCapabilities = require("jdtls.capabilities"), 403 | } 404 | 405 | M.async_profiler_home = vim.env["ASYNC_PROFILER_HOME"] 406 | local function get_async_profiler_ddl() 407 | if M.async_profiler_home then 408 | if utils.is_mac then 409 | return vim.fn.glob(M.async_profiler_home .. "/build/lib/libasyncProfiler.dylib") 410 | elseif utils.is_linux then 411 | return vim.fn.glob(M.async_profiler_home .. "/build/lib/libasyncProfiler.so") 412 | else 413 | return vim.fn.glob(M.async_profiler_home .. "/build/lib/libasyncProfiler.dll") 414 | end 415 | end 416 | end 417 | local function get_async_profiler_cov() 418 | if M.async_profiler_home then 419 | for _, value in ipairs(vim.split(vim.fn.glob(M.async_profiler_home .. "/target/jfr-converter-*.jar"), "\n")) do 420 | if not (vim.endswith(value, "-javadoc.jar") or vim.endswith(value, "-sources.jar")) then 421 | return value 422 | end 423 | end 424 | end 425 | end 426 | 427 | -- see https://github.com/mfussenegger/dotfiles/blob/master/vim/.config/nvim/ftplugin/java.lua 428 | local function test_with_profile(test_fn) 429 | return function() 430 | local choices = { 431 | "cpu,alloc=2m,lock=10ms", 432 | "cpu", 433 | "alloc", 434 | "wall", 435 | "context-switches", 436 | "cycles", 437 | "instructions", 438 | "cache-misses", 439 | } 440 | local select_opts = { 441 | format_item = tostring, 442 | } 443 | vim.ui.select(choices, select_opts, function(choice) 444 | if not choice then 445 | return 446 | end 447 | local async_profiler_so = get_async_profiler_ddl() 448 | local event = "event=" .. choice 449 | local vmArgs = "-ea -agentpath:" .. async_profiler_so .. "=start," 450 | vmArgs = vmArgs .. event .. ",file=" .. utils.tmpdir_file("profile.jfr") 451 | test_fn({ 452 | config_overrides = { 453 | vmArgs = vmArgs, 454 | noDebug = true, 455 | }, 456 | after_test = function() 457 | local result = vim 458 | .system({ 459 | "java", 460 | "-jar", 461 | get_async_profiler_cov(), 462 | utils.tmpdir_file("profile.jfr"), 463 | utils.tmpdir_file("profile.html"), 464 | }) 465 | :wait() 466 | if result.code == 0 then 467 | utils.open_fn(utils.tmpdir_file("profile.html")) 468 | else 469 | vim.notify("Async Profiler conversion failed: " .. result.stderr, vim.log.levels.ERROR) 470 | end 471 | end, 472 | }) 473 | end) 474 | end 475 | end 476 | 477 | M.config.flags = { 478 | debounce_text_changes = 150, 479 | } 480 | M.config.handlers = {} 481 | M.config.handlers["language/status"] = function(err, msg) 482 | -- 使用 progress 查看状态 483 | -- print("jdtls " .. s.type .. ": " .. s.message) 484 | -- ServiceReady 不能用来判断是否完全启动 485 | -- if "ServiceReady" == s.type then 486 | -- require("jdtls.dap").setup_dap_main_class_configs({ verbose = true }) 487 | -- end 488 | end 489 | 490 | local me = require("kide.melspconfig") 491 | M.config.capabilities = me.capabilities() 492 | M.config.on_init = me.on_init 493 | 494 | M.config.on_attach = function(client, buffer) 495 | local function desc_opts(desc) 496 | return { silent = true, buffer = buffer, desc = desc } 497 | end 498 | 499 | local function with_compile(fn) 500 | return function() 501 | if vim.bo.modified then 502 | vim.cmd("w") 503 | end 504 | client.request_sync("java/buildWorkspace", false, 5000, buffer) 505 | fn() 506 | end 507 | end 508 | vim.keymap.set("n", "dl", with_compile(require("dap").run_last), desc_opts("Run last")) 509 | vim.keymap.set("n", "dc", with_compile(jdtls.test_class), desc_opts("Test class")) 510 | vim.keymap.set("n", "dm", with_compile(jdtls.test_nearest_method), desc_opts("Test method")) 511 | vim.keymap.set("n", "ds", with_compile(jdtls.pick_test), desc_opts("Select test")) 512 | vim.keymap.set("n", "crv", jdtls.extract_variable, desc_opts("Extract variable")) 513 | vim.keymap.set("v", "crm", [[lua require('jdtls').extract_method(true)]], desc_opts("Extract method")) 514 | vim.keymap.set("n", "crc", jdtls.extract_constant, desc_opts("Extract constant")) 515 | 516 | if M.async_profiler_home then 517 | vim.keymap.set( 518 | "n", 519 | "dM", 520 | with_compile(test_with_profile(jdtls.test_nearest_method)), 521 | desc_opts("Test method with profiling") 522 | ) 523 | end 524 | 525 | local create_command = vim.api.nvim_buf_create_user_command 526 | create_command(buffer, "OR", require("jdtls").organize_imports, { 527 | nargs = 0, 528 | }) 529 | 530 | create_command(buffer, "JavaProjects", require("java-deps").toggle_outline, { 531 | nargs = 0, 532 | }) 533 | create_command(buffer, "JdtExtendedSymbols", require("jdtls").extended_symbols, { 534 | nargs = 0, 535 | }) 536 | 537 | create_command( 538 | buffer, 539 | "JdtRun", 540 | with_compile(function() 541 | local main_config_opts = { 542 | verbose = false, 543 | on_ready = require("dap")["continue"], 544 | } 545 | require("jdtls.dap").setup_dap_main_class_configs(main_config_opts) 546 | end), 547 | { 548 | nargs = 0, 549 | } 550 | ) 551 | create_command(buffer, "JdtTestGenerate", require("jdtls.tests").generate, { nargs = 0 }) 552 | create_command(buffer, "JdtTestGoto", require("jdtls.tests").goto_subjects, { nargs = 0 }) 553 | 554 | create_command(buffer, "Jol", function(o) 555 | -- externals: Show object externals: objects reachable from a given instance 556 | -- footprint: Show the footprint of all objects reachable from a sample instance 557 | -- internals: Show object internals: field layout, default contents, object header 558 | -- internals-estimates: Same as 'internals', but simulate class layout in different VM modes 559 | jdtls.jol(o.args) 560 | end, { 561 | nargs = 1, 562 | complete = function() 563 | return { 564 | "externals", 565 | "footprint", 566 | "internals", 567 | "internals-estimates", 568 | } 569 | end, 570 | }) 571 | me.on_attach(client, buffer) 572 | end 573 | 574 | return M 575 | --------------------------------------------------------------------------------