summaryrefslogtreecommitdiff
path: root/lua/lvim
diff options
context:
space:
mode:
authorkylo252 <[email protected]>2021-10-10 21:07:41 +0200
committerGitHub <[email protected]>2021-10-10 21:07:41 +0200
commit52b74557415eb757ad4b7481b0aec8a3f98dd58d (patch)
tree9a05ec71a46c99fbdf8df0043be652b528c7c04e /lua/lvim
parente2c85df440564a62fd804555747b1652a6844a5e (diff)
feat: add an independent lvim namespace (#1699)
Diffstat (limited to 'lua/lvim')
-rw-r--r--lua/lvim/bootstrap.lua196
-rw-r--r--lua/lvim/config/defaults.lua32
-rw-r--r--lua/lvim/config/init.lua202
-rw-r--r--lua/lvim/config/settings.lua76
-rw-r--r--lua/lvim/core/autocmds.lua129
-rw-r--r--lua/lvim/core/autopairs.lua81
-rw-r--r--lua/lvim/core/bufferline.lua25
-rw-r--r--lua/lvim/core/builtins/init.lua28
-rw-r--r--lua/lvim/core/cmp.lua265
-rw-r--r--lua/lvim/core/commands.lua25
-rw-r--r--lua/lvim/core/comment.lua31
-rw-r--r--lua/lvim/core/dap.lua76
-rw-r--r--lua/lvim/core/dashboard.lua112
-rw-r--r--lua/lvim/core/gitsigns.lua64
-rw-r--r--lua/lvim/core/info.lua174
-rw-r--r--lua/lvim/core/log.lua60
-rw-r--r--lua/lvim/core/lualine/colors.lua16
-rw-r--r--lua/lvim/core/lualine/components.lua154
-rw-r--r--lua/lvim/core/lualine/conditions.lua17
-rw-r--r--lua/lvim/core/lualine/init.lua47
-rw-r--r--lua/lvim/core/lualine/styles.lua137
-rw-r--r--lua/lvim/core/lualine/utils.lua27
-rw-r--r--lua/lvim/core/nvimtree.lua141
-rw-r--r--lua/lvim/core/project.lua51
-rw-r--r--lua/lvim/core/telescope.lua193
-rw-r--r--lua/lvim/core/terminal.lua114
-rw-r--r--lua/lvim/core/treesitter.lua81
-rw-r--r--lua/lvim/core/which-key.lua270
-rw-r--r--lua/lvim/impatient.lua360
-rw-r--r--lua/lvim/impatient/profile.lua145
-rw-r--r--lua/lvim/interface/popup.lua62
-rw-r--r--lua/lvim/interface/text.lua95
-rw-r--r--lua/lvim/keymappings.lua180
-rw-r--r--lua/lvim/lsp/config.lua45
-rw-r--r--lua/lvim/lsp/handlers.lua169
-rw-r--r--lua/lvim/lsp/init.lua165
-rw-r--r--lua/lvim/lsp/manager.lua86
-rw-r--r--lua/lvim/lsp/null-ls/formatters.lua66
-rw-r--r--lua/lvim/lsp/null-ls/init.lua32
-rw-r--r--lua/lvim/lsp/null-ls/linters.lua66
-rw-r--r--lua/lvim/lsp/null-ls/services.lua63
-rw-r--r--lua/lvim/lsp/peek.lua152
-rw-r--r--lua/lvim/lsp/providers/jsonls.lua197
-rw-r--r--lua/lvim/lsp/providers/sumneko_lua.lua19
-rw-r--r--lua/lvim/lsp/providers/vuels.lua26
-rw-r--r--lua/lvim/lsp/providers/yamlls.lua30
-rw-r--r--lua/lvim/lsp/templates.lua98
-rw-r--r--lua/lvim/lsp/utils.lua62
-rw-r--r--lua/lvim/lualine/themes/onedarker.lua35
-rw-r--r--lua/lvim/plugin-loader.lua63
-rw-r--r--lua/lvim/plugins.lua180
-rw-r--r--lua/lvim/utils/ft.lua47
-rw-r--r--lua/lvim/utils/hooks.lua35
-rw-r--r--lua/lvim/utils/init.lua205
-rw-r--r--lua/lvim/utils/table.lua24
55 files changed, 5531 insertions, 0 deletions
diff --git a/lua/lvim/bootstrap.lua b/lua/lvim/bootstrap.lua
new file mode 100644
index 00000000..46c044ae
--- /dev/null
+++ b/lua/lvim/bootstrap.lua
@@ -0,0 +1,196 @@
+local M = {}
+
+package.loaded["lvim.utils.hooks"] = nil
+local _, hooks = pcall(require, "lvim.utils.hooks")
+
+---Join path segments that were passed as input
+---@return string
+function _G.join_paths(...)
+ local uv = vim.loop
+ local path_sep = uv.os_uname().version:match "Windows" and "\\" or "/"
+ local result = table.concat({ ... }, path_sep)
+ return result
+end
+
+---Get the full path to `$LUNARVIM_RUNTIME_DIR`
+---@return string
+function _G.get_runtime_dir()
+ local lvim_runtime_dir = os.getenv "LUNARVIM_RUNTIME_DIR"
+ if not lvim_runtime_dir then
+ -- when nvim is used directly
+ return vim.fn.stdpath "config"
+ end
+ return lvim_runtime_dir
+end
+
+---Get the full path to `$LUNARVIM_CONFIG_DIR`
+---@return string
+function _G.get_config_dir()
+ local lvim_config_dir = os.getenv "LUNARVIM_CONFIG_DIR"
+ if not lvim_config_dir then
+ return vim.fn.stdpath "config"
+ end
+ return lvim_config_dir
+end
+
+---Get the full path to `$LUNARVIM_CACHE_DIR`
+---@return string
+function _G.get_cache_dir()
+ local lvim_cache_dir = os.getenv "LUNARVIM_CACHE_DIR"
+ if not lvim_cache_dir then
+ return vim.fn.stdpath "cache"
+ end
+ return lvim_cache_dir
+end
+
+---Get the full path to the currently installed lunarvim repo
+---@return string
+local function get_install_path()
+ local lvim_runtime_dir = os.getenv "LUNARVIM_RUNTIME_DIR"
+ if not lvim_runtime_dir then
+ -- when nvim is used directly
+ return vim.fn.stdpath "config"
+ end
+ return join_paths(lvim_runtime_dir, "lvim")
+end
+
+---Get currently installed version of LunarVim
+---@param type string can be "short"
+---@return string
+function _G.get_version(type)
+ type = type or ""
+ local lvim_full_ver = vim.fn.system("git -C " .. get_install_path() .. " describe --tags")
+
+ if string.match(lvim_full_ver, "%d") == nil then
+ return nil
+ end
+ if type == "short" then
+ return vim.fn.split(lvim_full_ver, "-")[1]
+ else
+ return string.sub(lvim_full_ver, 1, #lvim_full_ver - 1)
+ end
+end
+
+---Initialize the `&runtimepath` variables and prepare for startup
+---@return table
+function M:init()
+ self.runtime_dir = get_runtime_dir()
+ self.config_dir = get_config_dir()
+ self.cache_path = get_cache_dir()
+ self.install_path = get_install_path()
+
+ self.pack_dir = join_paths(self.runtime_dir, "site", "pack")
+ self.packer_install_dir = join_paths(self.runtime_dir, "site", "pack", "packer", "start", "packer.nvim")
+ self.packer_cache_path = join_paths(self.config_dir, "plugin", "packer_compiled.lua")
+
+ if os.getenv "LUNARVIM_RUNTIME_DIR" then
+ vim.opt.rtp:remove(join_paths(vim.fn.stdpath "data", "site"))
+ vim.opt.rtp:remove(join_paths(vim.fn.stdpath "data", "site", "after"))
+ vim.opt.rtp:prepend(join_paths(self.runtime_dir, "site"))
+ vim.opt.rtp:append(join_paths(self.runtime_dir, "site", "after"))
+
+ vim.opt.rtp:remove(vim.fn.stdpath "config")
+ vim.opt.rtp:remove(join_paths(vim.fn.stdpath "config", "after"))
+ vim.opt.rtp:prepend(self.config_dir)
+ vim.opt.rtp:append(join_paths(self.config_dir, "after"))
+ -- TODO: we need something like this: vim.opt.packpath = vim.opt.rtp
+
+ vim.cmd [[let &packpath = &runtimepath]]
+ vim.cmd("set spellfile=" .. join_paths(self.config_dir, "spell", "en.utf-8.add"))
+ end
+
+ vim.fn.mkdir(get_cache_dir(), "p")
+
+ -- FIXME: currently unreliable in unit-tests
+ if not os.getenv "LVIM_TEST_ENV" then
+ require("lvim.impatient").setup {
+ path = vim.fn.stdpath "cache" .. "/lvim_cache",
+ enable_profiling = true,
+ }
+ end
+
+ require("lvim.config"):init {
+ config_dir = self.config_dir,
+ }
+ local config = require "lvim.config"
+ config:init {
+ user_config = join_paths(self.config_dir, "config.lua"),
+ }
+
+ require("lvim.plugin-loader"):init {
+ package_root = self.pack_dir,
+ install_path = self.packer_install_dir,
+ }
+
+ return self
+end
+
+---Update LunarVim
+---pulls the latest changes from github and, resets the startup cache
+function M:update()
+ hooks.run_pre_update()
+ M:update_repo()
+ hooks.run_post_update()
+end
+
+local function git_cmd(subcmd)
+ local Job = require "plenary.job"
+ local Log = require "lvim.core.log"
+ local args = { "-C", get_install_path() }
+ vim.list_extend(args, subcmd)
+
+ local stderr = {}
+ local stdout, ret = Job
+ :new({
+ command = "git",
+ args = args,
+ cwd = get_install_path(),
+ on_stderr = function(_, data)
+ table.insert(stderr, data)
+ end,
+ })
+ :sync()
+
+ if not vim.tbl_isempty(stderr) then
+ Log:debug(stderr)
+ end
+
+ if not vim.tbl_isempty(stdout) then
+ Log:debug(stdout)
+ end
+
+ return ret
+end
+
+---pulls the latest changes from github
+function M:update_repo()
+ local Log = require "lvim.core.log"
+ local sub_commands = {
+ fetch = { "fetch" },
+ diff = { "diff", "--quiet", "@{upstream}" },
+ merge = { "merge", "--ff-only", "--progress" },
+ }
+ Log:info "Checking for updates"
+
+ local ret = git_cmd(sub_commands.fetch)
+ if ret ~= 0 then
+ Log:error "Update failed! Check the log for further information"
+ return
+ end
+
+ ret = git_cmd(sub_commands.diff)
+
+ if ret == 0 then
+ Log:info "LunarVim is already up-to-date"
+ return
+ end
+
+ ret = git_cmd(sub_commands.merge)
+
+ if ret ~= 0 then
+ Log:error "Update failed! Please pull the changes manually instead."
+ return
+ end
+end
+
+return M
diff --git a/lua/lvim/config/defaults.lua b/lua/lvim/config/defaults.lua
new file mode 100644
index 00000000..ffbb2e1b
--- /dev/null
+++ b/lua/lvim/config/defaults.lua
@@ -0,0 +1,32 @@
+return {
+ leader = "space",
+ colorscheme = "onedarker",
+ line_wrap_cursor_movement = true,
+ transparent_window = false,
+ format_on_save = true,
+ keys = {},
+
+ builtin = {},
+
+ log = {
+ ---@usage can be { "trace", "debug", "info", "warn", "error", "fatal" },
+ level = "warn",
+ viewer = {
+ ---@usage this will fallback on "less +F" if not found
+ cmd = "lnav",
+ layout_config = {
+ ---@usage direction = 'vertical' | 'horizontal' | 'window' | 'float',
+ direction = "horizontal",
+ open_mapping = "",
+ size = 40,
+ float_opts = {},
+ },
+ },
+ },
+ plugins = {
+ -- use config.lua for this not put here
+ },
+
+ autocommands = {},
+ lang = {},
+}
diff --git a/lua/lvim/config/init.lua b/lua/lvim/config/init.lua
new file mode 100644
index 00000000..d7877f1e
--- /dev/null
+++ b/lua/lvim/config/init.lua
@@ -0,0 +1,202 @@
+local utils = require "lvim.utils"
+local Log = require "lvim.core.log"
+
+local M = {}
+
+local user_config_dir = get_config_dir()
+local user_config_file = utils.join_paths(user_config_dir, "config.lua")
+
+-- Fallback config.lua to lv-config.lua
+if not utils.is_file(user_config_file) then
+ local lv_config = utils.join_paths(user_config_dir, "lv-config.lua")
+ Log:warn(string.format("[%s] not found, falling back to [%s]", user_config_file, lv_config))
+ user_config_file = lv_config
+end
+
+function M:get_user_config_path()
+ return user_config_file
+end
+
+--- Initialize lvim default configuration
+-- Define lvim global variable
+function M:init()
+ if vim.tbl_isempty(lvim or {}) then
+ lvim = require "lvim.config.defaults"
+ local home_dir = vim.loop.os_homedir()
+ lvim.vsnip_dir = utils.join_paths(home_dir, ".config", "snippets")
+ lvim.database = { save_location = utils.join_paths(home_dir, ".config", "lunarvim_db"), auto_execute = 1 }
+ end
+
+ local builtins = require "lvim.core.builtins"
+ builtins.config { user_config_file = user_config_file }
+
+ local settings = require "lvim.config.settings"
+ settings.load_options()
+
+ local lvim_lsp_config = require "lvim.lsp.config"
+ lvim.lsp = vim.deepcopy(lvim_lsp_config)
+
+ local supported_languages = {
+ "asm",
+ "bash",
+ "beancount",
+ "bibtex",
+ "bicep",
+ "c",
+ "c_sharp",
+ "clojure",
+ "cmake",
+ "comment",
+ "commonlisp",
+ "cpp",
+ "crystal",
+ "cs",
+ "css",
+ "cuda",
+ "d",
+ "dart",
+ "dockerfile",
+ "dot",
+ "elixir",
+ "elm",
+ "emmet",
+ "erlang",
+ "fennel",
+ "fish",
+ "fortran",
+ "gdscript",
+ "glimmer",
+ "go",
+ "gomod",
+ "graphql",
+ "haskell",
+ "hcl",
+ "heex",
+ "html",
+ "java",
+ "javascript",
+ "javascriptreact",
+ "jsdoc",
+ "json",
+ "json5",
+ "jsonc",
+ "julia",
+ "kotlin",
+ "latex",
+ "ledger",
+ "less",
+ "lua",
+ "markdown",
+ "nginx",
+ "nix",
+ "ocaml",
+ "ocaml_interface",
+ "perl",
+ "php",
+ "pioasm",
+ "ps1",
+ "puppet",
+ "python",
+ "ql",
+ "query",
+ "r",
+ "regex",
+ "rst",
+ "ruby",
+ "rust",
+ "scala",
+ "scss",
+ "sh",
+ "solidity",
+ "sparql",
+ "sql",
+ "supercollider",
+ "surface",
+ "svelte",
+ "swift",
+ "tailwindcss",
+ "terraform",
+ "tex",
+ "tlaplus",
+ "toml",
+ "tsx",
+ "turtle",
+ "typescript",
+ "typescriptreact",
+ "verilog",
+ "vim",
+ "vue",
+ "yaml",
+ "yang",
+ "zig",
+ }
+
+ require("lvim.lsp.manager").init_defaults(supported_languages)
+end
+
+local function deprecation_notice()
+ local in_headless = #vim.api.nvim_list_uis() == 0
+ if in_headless then
+ return
+ end
+
+ for lang, entry in pairs(lvim.lang) do
+ local deprecated_config = entry["lvim.lsp"] or {}
+ if not vim.tbl_isempty(deprecated_config) then
+ local msg = string.format(
+ "Deprecation notice: [lvim.lang.%s.lsp] setting is no longer supported. See https://github.com/LunarVim/LunarVim#breaking-changes",
+ lang
+ )
+ vim.schedule(function()
+ vim.notify(msg, vim.log.levels.WARN)
+ end)
+ end
+ end
+end
+
+--- Override the configuration with a user provided one
+-- @param config_path The path to the configuration overrides
+function M:load(config_path)
+ config_path = config_path or self.get_user_config_path()
+ local ok, _ = pcall(dofile, config_path)
+ if not ok then
+ Log:warn("Invalid configuration: " .. config_path)
+ end
+
+ deprecation_notice()
+
+ local autocmds = require "lvim.core.autocmds"
+ autocmds.define_augroups(lvim.autocommands)
+
+ local settings = require "lvim.config.settings"
+ settings.load_commands()
+end
+
+--- Override the configuration with a user provided one
+-- @param config_path The path to the configuration overrides
+function M:reload()
+ local lvim_modules = {}
+ for module, _ in pairs(package.loaded) do
+ if module:match "lvim" then
+ package.loaded.module = nil
+ table.insert(lvim_modules, module)
+ end
+ end
+
+ M:init()
+ M:load()
+
+ require("lvim.keymappings").setup() -- this should be done before loading the plugins
+ local plugins = require "lvim.plugins"
+ utils.toggle_autoformat()
+ local plugin_loader = require "lvim.plugin-loader"
+ plugin_loader:cache_reset()
+ plugin_loader:load { plugins, lvim.plugins }
+ vim.cmd ":PackerInstall"
+ vim.cmd ":PackerCompile"
+ -- vim.cmd ":PackerClean"
+ require("lvim.lsp").setup()
+ Log:info "Reloaded configuration"
+end
+
+return M
diff --git a/lua/lvim/config/settings.lua b/lua/lvim/config/settings.lua
new file mode 100644
index 00000000..b86e1a18
--- /dev/null
+++ b/lua/lvim/config/settings.lua
@@ -0,0 +1,76 @@
+local M = {}
+local utils = require "lvim.utils"
+M.load_options = function()
+ local default_options = {
+ backup = false, -- creates a backup file
+ clipboard = "unnamedplus", -- allows neovim to access the system clipboard
+ cmdheight = 2, -- more space in the neovim command line for displaying messages
+ colorcolumn = "99999", -- fixes indentline for now
+ completeopt = { "menuone", "noselect" },
+ conceallevel = 0, -- so that `` is visible in markdown files
+ fileencoding = "utf-8", -- the encoding written to a file
+ foldmethod = "manual", -- folding, set to "expr" for treesitter based folding
+ foldexpr = "", -- set to "nvim_treesitter#foldexpr()" for treesitter based folding
+ guifont = "monospace:h17", -- the font used in graphical neovim applications
+ hidden = true, -- required to keep multiple buffers and open multiple buffers
+ hlsearch = true, -- highlight all matches on previous search pattern
+ ignorecase = true, -- ignore case in search patterns
+ mouse = "a", -- allow the mouse to be used in neovim
+ pumheight = 10, -- pop up menu height
+ showmode = false, -- we don't need to see things like -- INSERT -- anymore
+ showtabline = 2, -- always show tabs
+ smartcase = true, -- smart case
+ smartindent = true, -- make indenting smarter again
+ splitbelow = true, -- force all horizontal splits to go below current window
+ splitright = true, -- force all vertical splits to go to the right of current window
+ swapfile = false, -- creates a swapfile
+ termguicolors = true, -- set term gui colors (most terminals support this)
+ timeoutlen = 100, -- time to wait for a mapped sequence to complete (in milliseconds)
+ title = true, -- set the title of window to the value of the titlestring
+ -- opt.titlestring = "%<%F%=%l/%L - nvim" -- what the title of the window will be set to
+ undodir = utils.join_paths(get_cache_dir(), "undo"), -- set an undo directory
+ undofile = true, -- enable persistent undo
+ updatetime = 300, -- faster completion
+ writebackup = false, -- if a file is being edited by another program (or was written to file while editing with another program), it is not allowed to be edited
+ expandtab = true, -- convert tabs to spaces
+ shiftwidth = 2, -- the number of spaces inserted for each indentation
+ tabstop = 2, -- insert 2 spaces for a tab
+ cursorline = true, -- highlight the current line
+ number = true, -- set numbered lines
+ relativenumber = false, -- set relative numbered lines
+ numberwidth = 4, -- set number column width to 2 {default 4}
+ signcolumn = "yes", -- always show the sign column, otherwise it would shift the text each time
+ wrap = false, -- display lines as one long line
+ spell = false,
+ spelllang = "en",
+ scrolloff = 8, -- is one of my fav
+ sidescrolloff = 8,
+ }
+
+ --- SETTINGS ---
+
+ vim.opt.shortmess:append "c"
+
+ for k, v in pairs(default_options) do
+ vim.opt[k] = v
+ end
+end
+
+M.load_commands = function()
+ local cmd = vim.cmd
+ if lvim.line_wrap_cursor_movement then
+ cmd "set whichwrap+=<,>,[,],h,l"
+ end
+
+ if lvim.transparent_window then
+ cmd "au ColorScheme * hi Normal ctermbg=none guibg=none"
+ cmd "au ColorScheme * hi SignColumn ctermbg=none guibg=none"
+ cmd "au ColorScheme * hi NormalNC ctermbg=none guibg=none"
+ cmd "au ColorScheme * hi MsgArea ctermbg=none guibg=none"
+ cmd "au ColorScheme * hi TelescopeBorder ctermbg=none guibg=none"
+ cmd "au ColorScheme * hi NvimTreeNormal ctermbg=none guibg=none"
+ cmd "let &fcs='eob: '"
+ end
+end
+
+return M
diff --git a/lua/lvim/core/autocmds.lua b/lua/lvim/core/autocmds.lua
new file mode 100644
index 00000000..4f29f0e1
--- /dev/null
+++ b/lua/lvim/core/autocmds.lua
@@ -0,0 +1,129 @@
+local autocommands = {}
+local user_config_file = require("lvim.config"):get_user_config_path()
+
+lvim.autocommands = {
+ _general_settings = {
+ {
+ "Filetype",
+ "*",
+ "lua require('lvim.utils.ft').do_filetype(vim.fn.expand(\"<amatch>\"))",
+ },
+ {
+ "FileType",
+ "qf",
+ "nnoremap <silent> <buffer> q :q<CR>",
+ },
+ {
+ "FileType",
+ "lsp-installer",
+ "nnoremap <silent> <buffer> q :q<CR>",
+ },
+ {
+ "TextYankPost",
+ "*",
+ "lua require('vim.highlight').on_yank({higroup = 'Search', timeout = 200})",
+ },
+ {
+ "BufWinEnter",
+ "*",
+ "setlocal formatoptions-=c formatoptions-=r formatoptions-=o",
+ },
+ {
+ "BufWinEnter",
+ "dashboard",
+ "setlocal cursorline signcolumn=yes cursorcolumn number",
+ },
+ {
+ "BufRead",
+ "*",
+ "setlocal formatoptions-=c formatoptions-=r formatoptions-=o",
+ },
+ {
+ "BufNewFile",
+ "*",
+ "setlocal formatoptions-=c formatoptions-=r formatoptions-=o",
+ },
+ { "BufWritePost", user_config_file, "lua require('lvim.config'):reload()" },
+ {
+ "FileType",
+ "qf",
+ "set nobuflisted",
+ },
+ -- { "VimLeavePre", "*", "set title set titleold=" },
+ },
+ _filetypechanges = {
+ { "BufWinEnter", ".tf", "setlocal filetype=terraform" },
+ { "BufRead", "*.tf", "setlocal filetype=terraform" },
+ { "BufNewFile", "*.tf", "setlocal filetype=terraform" },
+ { "BufWinEnter", ".zsh", "setlocal filetype=sh" },
+ { "BufRead", "*.zsh", "setlocal filetype=sh" },
+ { "BufNewFile", "*.zsh", "setlocal filetype=sh" },
+ },
+ -- _solidity = {
+ -- {'BufWinEnter', '.sol', 'setlocal filetype=solidity'}, {'BufRead', '*.sol', 'setlocal filetype=solidity'},
+ -- {'BufNewFile', '*.sol', 'setlocal filetype=solidity'}
+ -- },
+ -- _gemini = {
+ -- {'BufWinEnter', '.gmi', 'setlocal filetype=markdown'}, {'BufRead', '*.gmi', 'setlocal filetype=markdown'},
+ -- {'BufNewFile', '*.gmi', 'setlocal filetype=markdown'}
+ -- },
+ _git = {
+ { "FileType", "gitcommit", "setlocal wrap" },
+ { "FileType", "gitcommit", "setlocal spell" },
+ },
+ _markdown = {
+ { "FileType", "markdown", "setlocal wrap" },
+ { "FileType", "markdown", "setlocal spell" },
+ },
+ _buffer_bindings = {
+ { "FileType", "floaterm", "nnoremap <silent> <buffer> q :q<CR>" },
+ },
+ _auto_resize = {
+ -- will cause split windows to be resized evenly if main window is resized
+ { "VimResized", "*", "wincmd =" },
+ },
+ _packer_compile = {
+ -- will run PackerCompile after writing plugins.lua
+ { "BufWritePost", "plugins.lua", "PackerCompile" },
+ },
+ _general_lsp = {
+ { "FileType", "lspinfo", "nnoremap <silent> <buffer> q :q<CR>" },
+ },
+
+ -- _fterm_lazygit = {
+ -- -- will cause esc key to exit lazy git
+ -- {"TermEnter", "*", "call LazyGitNativation()"}
+ -- },
+ -- _mode_switching = {
+ -- -- will switch between absolute and relative line numbers depending on mode
+ -- {'InsertEnter', '*', 'if &relativenumber | let g:ms_relativenumberoff = 1 | setlocal number norelativenumber | endif'},
+ -- {'InsertLeave', '*', 'if exists("g:ms_relativenumberoff") | setlocal relativenumber | endif'},
+ -- {'InsertEnter', '*', 'if &cursorline | let g:ms_cursorlineoff = 1 | setlocal nocursorline | endif'},
+ -- {'InsertLeave', '*', 'if exists("g:ms_cursorlineoff") | setlocal cursorline | endif'},
+ -- },
+ custom_groups = {},
+}
+
+function autocommands.define_augroups(definitions) -- {{{1
+ -- Create autocommand groups based on the passed definitions
+ --
+ -- The key will be the name of the group, and each definition
+ -- within the group should have:
+ -- 1. Trigger
+ -- 2. Pattern
+ -- 3. Text
+ -- just like how they would normally be defined from Vim itself
+ for group_name, definition in pairs(definitions) do
+ vim.cmd("augroup " .. group_name)
+ vim.cmd "autocmd!"
+
+ for _, def in pairs(definition) do
+ local command = table.concat(vim.tbl_flatten { "autocmd", def }, " ")
+ vim.cmd(command)
+ end
+
+ vim.cmd "augroup END"
+ end
+end
+
+return autocommands
diff --git a/lua/lvim/core/autopairs.lua b/lua/lvim/core/autopairs.lua
new file mode 100644
index 00000000..eb080fb1
--- /dev/null
+++ b/lua/lvim/core/autopairs.lua
@@ -0,0 +1,81 @@
+local M = {}
+
+function M.config()
+ lvim.builtin.autopairs = {
+ active = true,
+ on_config_done = nil,
+ ---@usage auto insert after select function or method item
+ map_complete = true,
+ ---@usage -- modifies the function or method delimiter by filetypes
+ map_char = {
+ all = "(",
+ tex = "{",
+ },
+ ---@usage check treesitter
+ check_ts = true,
+ ts_config = {
+ lua = { "string" },
+ javascript = { "template_string" },
+ java = false,
+ },
+ }
+end
+
+M.setup = function()
+ local autopairs = require "nvim-autopairs"
+ local Rule = require "nvim-autopairs.rule"
+ local cond = require "nvim-autopairs.conds"
+
+ autopairs.setup {
+ check_ts = lvim.builtin.autopairs.check_ts,
+ ts_config = lvim.builtin.autopairs.ts_config,
+ }
+
+ -- vim.g.completion_confirm_key = ""
+
+ autopairs.add_rule(Rule("$$", "$$", "tex"))
+ autopairs.add_rules {
+ Rule("$", "$", { "tex", "latex" }) -- don't add a pair if the next character is %
+ :with_pair(cond.not_after_regex_check "%%") -- don't add a pair if the previous character is xxx
+ :with_pair(cond.not_before_regex_check("xxx", 3)) -- don't move right when repeat character
+ :with_move(cond.none()) -- don't delete if the next character is xx
+ :with_del(cond.not_after_regex_check "xx") -- disable add newline when press <cr>
+ :with_cr(cond.none()),
+ }
+ autopairs.add_rules {
+ Rule("$$", "$$", "tex"):with_pair(function(opts)
+ print(vim.inspect(opts))
+ if opts.line == "aa $$" then
+ -- don't add pair on that line
+ return false
+ end
+ end),
+ }
+
+ if package.loaded["cmp"] then
+ require("nvim-autopairs.completion.cmp").setup {
+ map_cr = false,
+ map_complete = lvim.builtin.autopairs.map_complete,
+ map_char = lvim.builtin.autopairs.map_char,
+ }
+ -- we map CR explicitly in cmp.lua but we still need to setup the autopairs CR keymap
+ vim.api.nvim_set_keymap("i", "<CR>", "v:lua.MPairs.autopairs_cr()", { expr = true, noremap = true })
+ end
+
+ require("nvim-treesitter.configs").setup { autopairs = { enable = true } }
+
+ local ts_conds = require "nvim-autopairs.ts-conds"
+
+ -- TODO: can these rules be safely added from "config.lua" ?
+ -- press % => %% is only inside comment or string
+ autopairs.add_rules {
+ Rule("%", "%", "lua"):with_pair(ts_conds.is_ts_node { "string", "comment" }),
+ Rule("$", "$", "lua"):with_pair(ts_conds.is_not_ts_node { "function" }),
+ }
+
+ if lvim.builtin.autopairs.on_config_done then
+ lvim.builtin.autopairs.on_config_done(autopairs)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/bufferline.lua b/lua/lvim/core/bufferline.lua
new file mode 100644
index 00000000..ae6542d1
--- /dev/null
+++ b/lua/lvim/core/bufferline.lua
@@ -0,0 +1,25 @@
+local M = {}
+
+M.config = function()
+ lvim.builtin.bufferline = {
+ active = true,
+ on_config_done = nil,
+ keymap = {
+ normal_mode = {
+ ["<S-l>"] = ":BufferNext<CR>",
+ ["<S-h>"] = ":BufferPrevious<CR>",
+ },
+ },
+ }
+end
+
+M.setup = function()
+ local keymap = require "lvim.keymappings"
+ keymap.append_to_defaults(lvim.builtin.bufferline.keymap)
+
+ if lvim.builtin.bufferline.on_config_done then
+ lvim.builtin.bufferline.on_config_done()
+ end
+end
+
+return M
diff --git a/lua/lvim/core/builtins/init.lua b/lua/lvim/core/builtins/init.lua
new file mode 100644
index 00000000..8f83072e
--- /dev/null
+++ b/lua/lvim/core/builtins/init.lua
@@ -0,0 +1,28 @@
+local M = {}
+
+local builtins = {
+ "lvim.keymappings",
+ "lvim.core.which-key",
+ "lvim.core.gitsigns",
+ "lvim.core.cmp",
+ "lvim.core.dashboard",
+ "lvim.core.dap",
+ "lvim.core.terminal",
+ "lvim.core.telescope",
+ "lvim.core.treesitter",
+ "lvim.core.nvimtree",
+ "lvim.core.project",
+ "lvim.core.bufferline",
+ "lvim.core.autopairs",
+ "lvim.core.comment",
+ "lvim.core.lualine",
+}
+
+function M.config(config)
+ for _, builtin_path in ipairs(builtins) do
+ local builtin = require(builtin_path)
+ builtin.config(config)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/cmp.lua b/lua/lvim/core/cmp.lua
new file mode 100644
index 00000000..ad06a360
--- /dev/null
+++ b/lua/lvim/core/cmp.lua
@@ -0,0 +1,265 @@
+local M = {}
+
+local check_backspace = function()
+ local col = vim.fn.col "." - 1
+ return col == 0 or vim.fn.getline("."):sub(col, col):match "%s"
+end
+
+local function T(str)
+ return vim.api.nvim_replace_termcodes(str, true, true, true)
+end
+
+local is_emmet_active = function()
+ local clients = vim.lsp.buf_get_clients()
+
+ for _, client in pairs(clients) do
+ if client.name == "emmet_ls" then
+ return true
+ end
+ end
+ return false
+end
+
+M.config = function()
+ local status_cmp_ok, cmp = pcall(require, "cmp")
+ if not status_cmp_ok then
+ return
+ end
+ local status_luasnip_ok, luasnip = pcall(require, "luasnip")
+ if not status_luasnip_ok then
+ return
+ end
+ local win_get_cursor = vim.api.nvim_win_get_cursor
+ local get_current_buf = vim.api.nvim_get_current_buf
+
+ local function inside_snippet()
+ -- for outdated versions of luasnip
+ if not luasnip.session.current_nodes then
+ return false
+ end
+
+ local node = luasnip.session.current_nodes[get_current_buf()]
+ if not node then
+ return false
+ end
+
+ local snip_begin_pos, snip_end_pos = node.parent.snippet.mark:pos_begin_end()
+ local pos = win_get_cursor(0)
+ pos[1] = pos[1] - 1 -- LuaSnip is 0-based not 1-based like nvim for rows
+ return pos[1] >= snip_begin_pos[1] and pos[1] <= snip_end_pos[1]
+ end
+
+ ---sets the current buffer's luasnip to the one nearest the cursor
+ ---@return boolean true if a node is found, false otherwise
+ local function seek_luasnip_cursor_node()
+ -- for outdated versions of luasnip
+ if not luasnip.session.current_nodes then
+ return false
+ end
+
+ local pos = win_get_cursor(0)
+ pos[1] = pos[1] - 1
+ local node = luasnip.session.current_nodes[get_current_buf()]
+ if not node then
+ return false
+ end
+
+ local snippet = node.parent.snippet
+ local exit_node = snippet.insert_nodes[0]
+
+ -- exit early if we're past the exit node
+ if exit_node then
+ local exit_pos_end = exit_node.mark:pos_end()
+ if (pos[1] > exit_pos_end[1]) or (pos[1] == exit_pos_end[1] and pos[2] > exit_pos_end[2]) then
+ snippet:remove_from_jumplist()
+ luasnip.session.current_nodes[get_current_buf()] = nil
+
+ return false
+ end
+ end
+
+ node = snippet.inner_first:jump_into(1, true)
+ while node ~= nil and node.next ~= nil and node ~= snippet do
+ local n_next = node.next
+ local next_pos = n_next and n_next.mark:pos_begin()
+ local candidate = n_next ~= snippet and next_pos and (pos[1] < next_pos[1])
+ or (pos[1] == next_pos[1] and pos[2] < next_pos[2])
+
+ -- Past unmarked exit node, exit early
+ if n_next == nil or n_next == snippet.next then
+ snippet:remove_from_jumplist()
+ luasnip.session.current_nodes[get_current_buf()] = nil
+
+ return false
+ end
+
+ if candidate then
+ luasnip.session.current_nodes[get_current_buf()] = node
+ return true
+ end
+
+ local ok
+ ok, node = pcall(node.jump_from, node, 1, true) -- no_move until last stop
+ if not ok then
+ snippet:remove_from_jumplist()
+ luasnip.session.current_nodes[get_current_buf()] = nil
+
+ return false
+ end
+ end
+
+ -- No candidate, but have an exit node
+ if exit_node then
+ -- to jump to the exit node, seek to snippet
+ luasnip.session.current_nodes[get_current_buf()] = snippet
+ return true
+ end
+
+ -- No exit node, exit from snippet
+ snippet:remove_from_jumplist()
+ luasnip.session.current_nodes[get_current_buf()] = nil
+ return false
+ end
+
+ lvim.builtin.cmp = {
+ confirm_opts = {
+ behavior = cmp.ConfirmBehavior.Replace,
+ select = false,
+ },
+ experimental = {
+ ghost_text = true,
+ native_menu = false,
+ },
+ formatting = {
+ kind_icons = {
+ Class = " ",
+ Color = " ",
+ Constant = "ﲀ ",
+ Constructor = " ",
+ Enum = "練",
+ EnumMember = " ",
+ Event = " ",
+ Field = " ",
+ File = "",
+ Folder = " ",
+ Function = " ",
+ Interface = "ﰮ ",
+ Keyword = " ",
+ Method = " ",
+ Module = " ",
+ Operator = "",
+ Property = " ",
+ Reference = " ",
+ Snippet = " ",
+ Struct = " ",
+ Text = " ",
+ TypeParameter = " ",
+ Unit = "塞",
+ Value = " ",
+ Variable = " ",
+ },
+ source_names = {
+ nvim_lsp = "(LSP)",
+ emoji = "(Emoji)",
+ path = "(Path)",
+ calc = "(Calc)",
+ cmp_tabnine = "(Tabnine)",
+ vsnip = "(Snippet)",
+ luasnip = "(Snippet)",
+ buffer = "(Buffer)",
+ },
+ duplicates = {
+ buffer = 1,
+ path = 1,
+ nvim_lsp = 0,
+ luasnip = 1,
+ },
+ duplicates_default = 0,
+ format = function(entry, vim_item)
+ vim_item.kind = lvim.builtin.cmp.formatting.kind_icons[vim_item.kind]
+ vim_item.menu = lvim.builtin.cmp.formatting.source_names[entry.source.name]
+ vim_item.dup = lvim.builtin.cmp.formatting.duplicates[entry.source.name]
+ or lvim.builtin.cmp.formatting.duplicates_default
+ return vim_item
+ end,
+ },
+ snippet = {
+ expand = function(args)
+ require("luasnip").lsp_expand(args.body)
+ end,
+ },
+ documentation = {
+ border = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" },
+ },
+ sources = {
+ { name = "nvim_lsp" },
+ { name = "path" },
+ { name = "luasnip" },
+ { name = "cmp_tabnine" },
+ { name = "nvim_lua" },
+ { name = "buffer" },
+ { name = "calc" },
+ { name = "emoji" },
+ { name = "treesitter" },
+ { name = "crates" },
+ },
+ mapping = {
+ ["<C-d>"] = cmp.mapping.scroll_docs(-4),
+ ["<C-f>"] = cmp.mapping.scroll_docs(4),
+ -- TODO: potentially fix emmet nonsense
+ ["<Tab>"] = cmp.mapping(function()
+ if cmp.visible() then
+ cmp.select_next_item()
+ elseif luasnip.expandable() then
+ luasnip.expand()
+ elseif inside_snippet() and seek_luasnip_cursor_node() and luasnip.jumpable() then
+ luasnip.jump(1)
+ elseif check_backspace() then
+ vim.fn.feedkeys(T "<Tab>", "n")
+ elseif is_emmet_active() then
+ return vim.fn["cmp#complete"]()
+ else
+ vim.fn.feedkeys(T "<Tab>", "n")
+ end
+ end, {
+ "i",
+ "s",
+ }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ if cmp.visible() then
+ cmp.select_prev_item()
+ elseif inside_snippet() and luasnip.jumpable(-1) then
+ luasnip.jump(-1)
+ else
+ fallback()
+ end
+ end, {
+ "i",
+ "s",
+ }),
+
+ ["<C-Space>"] = cmp.mapping.complete(),
+ ["<C-e>"] = cmp.mapping.close(),
+ ["<CR>"] = cmp.mapping(function(fallback)
+ if cmp.visible() and cmp.confirm(lvim.builtin.cmp.confirm_opts) then
+ return
+ end
+
+ if inside_snippet() and seek_luasnip_cursor_node() and luasnip.jumpable() then
+ if not luasnip.jump(1) then
+ fallback()
+ end
+ else
+ fallback()
+ end
+ end),
+ },
+ }
+end
+
+M.setup = function()
+ require("luasnip/loaders/from_vscode").lazy_load()
+ require("cmp").setup(lvim.builtin.cmp)
+end
+
+return M
diff --git a/lua/lvim/core/commands.lua b/lua/lvim/core/commands.lua
new file mode 100644
index 00000000..b750f12b
--- /dev/null
+++ b/lua/lvim/core/commands.lua
@@ -0,0 +1,25 @@
+local M = {}
+
+M.defaults = {
+ [[
+ function! QuickFixToggle()
+ if empty(filter(getwininfo(), 'v:val.quickfix'))
+ copen
+ else
+ cclose
+ endif
+ endfunction
+ ]],
+ -- :LvimInfo
+ [[ command! LvimInfo lua require('lvim.core.info').toggle_popup(vim.bo.filetype) ]],
+ [[ command! LvimCacheReset lua require('lvim.utils.hooks').reset_cache() ]],
+ [[ command! LvimUpdate lua require('lvim.bootstrap').update() ]],
+}
+
+M.load = function(commands)
+ for _, command in ipairs(commands) do
+ vim.cmd(command)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/comment.lua b/lua/lvim/core/comment.lua
new file mode 100644
index 00000000..b98410ab
--- /dev/null
+++ b/lua/lvim/core/comment.lua
@@ -0,0 +1,31 @@
+local M = {}
+
+function M.config()
+ lvim.builtin.comment = {
+ active = true,
+ on_config_done = nil,
+ -- Linters prefer comment and line to have a space in between markers
+ marker_padding = true,
+ -- should comment out empty or whitespace only lines
+ comment_empty = false,
+ -- Should key mappings be created
+ create_mappings = true,
+ -- Normal mode mapping left hand side
+ line_mapping = "gcc",
+ -- Visual/Operator mapping left hand side
+ operator_mapping = "gc",
+ -- Hook function to call before commenting takes place
+ hook = nil,
+ }
+end
+
+function M.setup()
+ local nvim_comment = require "nvim_comment"
+
+ nvim_comment.setup(lvim.builtin.comment)
+ if lvim.builtin.comment.on_config_done then
+ lvim.builtin.comment.on_config_done(nvim_comment)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/dap.lua b/lua/lvim/core/dap.lua
new file mode 100644
index 00000000..d9b59641
--- /dev/null
+++ b/lua/lvim/core/dap.lua
@@ -0,0 +1,76 @@
+local M = {}
+
+M.config = function()
+ lvim.builtin.dap = {
+ active = false,
+ on_config_done = nil,
+ breakpoint = {
+ text = "",
+ texthl = "LspDiagnosticsSignError",
+ linehl = "",
+ numhl = "",
+ },
+ breakpoint_rejected = {
+ text = "",
+ texthl = "LspDiagnosticsSignHint",
+ linehl = "",
+ numhl = "",
+ },
+ stopped = {
+ text = "",
+ texthl = "LspDiagnosticsSignInformation",
+ linehl = "DiagnosticUnderlineInfo",
+ numhl = "LspDiagnosticsSignInformation",
+ },
+ }
+end
+
+M.setup = function()
+ local dap = require "dap"
+
+ vim.fn.sign_define("DapBreakpoint", lvim.builtin.dap.breakpoint)
+ vim.fn.sign_define("DapBreakpointRejected", lvim.builtin.dap.breakpoint_rejected)
+ vim.fn.sign_define("DapStopped", lvim.builtin.dap.stopped)
+
+ dap.defaults.fallback.terminal_win_cmd = "50vsplit new"
+
+ lvim.builtin.which_key.mappings["d"] = {
+ name = "Debug",
+ t = { "<cmd>lua require'dap'.toggle_breakpoint()<cr>", "Toggle Breakpoint" },
+ b = { "<cmd>lua require'dap'.step_back()<cr>", "Step Back" },
+ c = { "<cmd>lua require'dap'.continue()<cr>", "Continue" },
+ C = { "<cmd>lua require'dap'.run_to_cursor()<cr>", "Run To Cursor" },
+ d = { "<cmd>lua require'dap'.disconnect()<cr>", "Disconnect" },
+ g = { "<cmd>lua require'dap'.session()<cr>", "Get Session" },
+ i = { "<cmd>lua require'dap'.step_into()<cr>", "Step Into" },
+ o = { "<cmd>lua require'dap'.step_over()<cr>", "Step Over" },
+ u = { "<cmd>lua require'dap'.step_out()<cr>", "Step Out" },
+ p = { "<cmd>lua require'dap'.pause.toggle()<cr>", "Pause" },
+ r = { "<cmd>lua require'dap'.repl.toggle()<cr>", "Toggle Repl" },
+ s = { "<cmd>lua require'dap'.continue()<cr>", "Start" },
+ q = { "<cmd>lua require'dap'.close()<cr>", "Quit" },
+ }
+
+ if lvim.builtin.dap.on_config_done then
+ lvim.builtin.dap.on_config_done(dap)
+ end
+end
+
+-- TODO put this up there ^^^ call in ftplugin
+
+-- M.dap = function()
+-- if lvim.plugin.dap.active then
+-- local dap_install = require "dap-install"
+-- dap_install.config("python_dbg", {})
+-- end
+-- end
+--
+-- M.dap = function()
+-- -- gem install readapt ruby-debug-ide
+-- if lvim.plugin.dap.active then
+-- local dap_install = require "dap-install"
+-- dap_install.config("ruby_vsc_dbg", {})
+-- end
+-- end
+
+return M
diff --git a/lua/lvim/core/dashboard.lua b/lua/lvim/core/dashboard.lua
new file mode 100644
index 00000000..38d9d226
--- /dev/null
+++ b/lua/lvim/core/dashboard.lua
@@ -0,0 +1,112 @@
+local M = {}
+local utils = require "lvim.utils"
+
+M.config = function(config)
+ lvim.builtin.dashboard = {
+ active = false,
+ on_config_done = nil,
+ search_handler = "telescope",
+ disable_at_vim_enter = 0,
+ session_directory = utils.join_paths(get_cache_dir(), "sessions"),
+ custom_header = {
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣶⣾⠿⠿⠟⠛⠛⠛⠛⠿⠿⣿⣷⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ " ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⡿⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⡿⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠒⠂⠉⠉⠉⠉⢩⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⢰⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠠⡀⠀⠀⢀⣾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢀⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡧⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠈⠁⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣇⠀⠀⠀⠀⠀⠀⠉⠢⠤⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⠈⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠒⠤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⡇⠀⠀⢀⣣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠒⠢⠤⠄⣀⣀⠀⠀⠀⢠⣿⡟⠀⠀⠀⣺⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ "⠀⣿⠇⠀⠀⠀⠀⠀⣤⡄⠀⠀⢠⣤⡄⠀⢨⣭⣠⣤⣤⣤⡀⠀⠀⢀⣤⣤⣤⣤⡄⠀⠀⠀⣤⣄⣤⣤⣤⠀⠀⣿⣯⠉⠉⣿⡟⠀⠈⢩⣭⣤⣤⠀⠀⠀⠀⣠⣤⣤⣤⣄⣤⣤",
+ "⢠⣿⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⣸⣿⠁⠀⣿⣿⠉⠀⠈⣿⡇⠀⠀⠛⠋⠀⠀⢹⣿⠀⠀⠀⣿⠏⠀⠸⠿⠃⠀⣿⣿⠀⣰⡟⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⣿⡟⢸⣿⡇⢀⣿",
+ "⣸⡇⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⣿⡟⠀⢠⣿⡇⠀⠀⢰⣿⡇⠀⣰⣾⠟⠛⠛⣻⡇⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⢻⣿⢰⣿⠀⠀⠀⠀⠀⠀⣾⡇⠀⠀⠀⢸⣿⠇⢸⣿⠀⢸⡏",
+ "⣿⣧⣤⣤⣤⡄⠀⠘⣿⣤⣤⡤⣿⠇⠀⢸⣿⠁⠀⠀⣼⣿⠀⠀⢿⣿⣤⣤⠔⣿⠃⠀⠀⣾⡇⠀⠀⠀⠀⠀⠀⢸⣿⣿⠋⠀⠀⠀⢠⣤⣤⣿⣥⣤⡄⠀⣼⣿⠀⣸⡏⠀⣿⠃",
+ "⠉⠉⠉⠉⠉⠁⠀⠀⠈⠉⠉⠀⠉⠀⠀⠈⠉⠀⠀⠀⠉⠉⠀⠀⠀⠉⠉⠁⠈⠉⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠁⠀⠉⠁⠀⠉⠁⠀⠉⠀",
+ },
+
+ custom_section = {
+ a = {
+ description = { " Find File " },
+ command = "Telescope find_files",
+ },
+ b = {
+ description = { " Recent Projects " },
+ command = "Telescope projects",
+ },
+ c = {
+ description = { " Recently Used Files" },
+ command = "Telescope oldfiles",
+ },
+ d = {
+ description = { " Find Word " },
+ command = "Telescope live_grep",
+ },
+ e = {
+ description = { " Configuration " },
+ command = ":e " .. config.user_config_file,
+ },
+ },
+
+ footer = { "lunarvim.org" },
+ }
+end
+
+M.setup = function()
+ vim.g.dashboard_disable_at_vimenter = lvim.builtin.dashboard.disable_at_vim_enter
+
+ vim.g.dashboard_custom_header = lvim.builtin.dashboard.custom_header
+
+ vim.g.dashboard_default_executive = lvim.builtin.dashboard.search_handler
+
+ vim.g.dashboard_custom_section = lvim.builtin.dashboard.custom_section
+
+ lvim.builtin.which_key.mappings[";"] = { "<cmd>Dashboard<CR>", "Dashboard" }
+
+ vim.g.dashboard_session_directory = lvim.builtin.dashboard.session_directory
+
+ local lvim_site = "lunarvim.org"
+ local lvim_version = get_version "short"
+ local num_plugins_loaded = #vim.fn.globpath(get_runtime_dir() .. "/site/pack/packer/start", "*", 0, 1)
+
+ local footer = {
+ "LunarVim loaded " .. num_plugins_loaded .. " plugins ",
+ "",
+ lvim_site,
+ }
+
+ if lvim_version then
+ table.insert(footer, 2, "")
+ table.insert(footer, 3, "v" .. lvim_version)
+ end
+
+ local text = require "lvim.interface.text"
+ vim.g.dashboard_custom_footer = text.align_center({ width = 0 }, footer, 0.49) -- Use 0.49 as  counts for 2 characters
+
+ require("lvim.core.autocmds").define_augroups {
+ _dashboard = {
+ -- seems to be nobuflisted that makes my stuff disappear will do more testing
+ {
+ "FileType",
+ "dashboard",
+ "setlocal nocursorline noswapfile synmaxcol& signcolumn=no norelativenumber nocursorcolumn nospell nolist nonumber bufhidden=wipe colorcolumn= foldcolumn=0 matchpairs= ",
+ },
+ {
+ "FileType",
+ "dashboard",
+ "set showtabline=0 | autocmd BufLeave <buffer> set showtabline=" .. vim.opt.showtabline._value,
+ },
+ { "FileType", "dashboard", "nnoremap <silent> <buffer> q :q<CR>" },
+ },
+ }
+
+ if lvim.builtin.dashboard.on_config_done then
+ lvim.builtin.dashboard.on_config_done()
+ end
+end
+
+return M
diff --git a/lua/lvim/core/gitsigns.lua b/lua/lvim/core/gitsigns.lua
new file mode 100644
index 00000000..cc6387dc
--- /dev/null
+++ b/lua/lvim/core/gitsigns.lua
@@ -0,0 +1,64 @@
+local M = {}
+
+M.config = function()
+ lvim.builtin.gitsigns = {
+ active = true,
+ on_config_done = nil,
+ opts = {
+ signs = {
+ add = {
+ hl = "GitSignsAdd",
+ text = "▎",
+ numhl = "GitSignsAddNr",
+ linehl = "GitSignsAddLn",
+ },
+ change = {
+ hl = "GitSignsChange",
+ text = "▎",
+ numhl = "GitSignsChangeNr",
+ linehl = "GitSignsChangeLn",
+ },
+ delete = {
+ hl = "GitSignsDelete",
+ text = "契",
+ numhl = "GitSignsDeleteNr",
+ linehl = "GitSignsDeleteLn",
+ },
+ topdelete = {
+ hl = "GitSignsDelete",
+ text = "契",
+ numhl = "GitSignsDeleteNr",
+ linehl = "GitSignsDeleteLn",
+ },
+ changedelete = {
+ hl = "GitSignsChange",
+ text = "▎",
+ numhl = "GitSignsChangeNr",
+ linehl = "GitSignsChangeLn",
+ },
+ },
+ numhl = false,
+ linehl = false,
+ keymaps = {
+ -- Default keymap options
+ noremap = true,
+ buffer = true,
+ },
+ watch_gitdir = { interval = 1000 },
+ sign_priority = 6,
+ update_debounce = 200,
+ status_formatter = nil, -- Use default
+ },
+ }
+end
+
+M.setup = function()
+ local gitsigns = require "gitsigns"
+
+ gitsigns.setup(lvim.builtin.gitsigns.opts)
+ if lvim.builtin.gitsigns.on_config_done then
+ lvim.builtin.gitsigns.on_config_done(gitsigns)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/info.lua b/lua/lvim/core/info.lua
new file mode 100644
index 00000000..7fdb665b
--- /dev/null
+++ b/lua/lvim/core/info.lua
@@ -0,0 +1,174 @@
+local M = {
+ banner = {
+ "",
+ [[ __ _ ___ ]],
+ [[ / / __ ______ ____ _____| | / (_)___ ___ ]],
+ [[ / / / / / / __ \/ __ `/ ___/ | / / / __ `__ \]],
+ [[ / /___/ /_/ / / / / /_/ / / | |/ / / / / / / /]],
+ [[/_____/\__,_/_/ /_/\__,_/_/ |___/_/_/ /_/ /_/ ]],
+ },
+}
+
+local fmt = string.format
+local text = require "lvim.interface.text"
+local lsp_utils = require "lvim.lsp.utils"
+local user_config_file = require("lvim.config"):get_user_config_path()
+
+local function str_list(list)
+ return fmt("[ %s ]", table.concat(list, ", "))
+end
+
+local function get_formatter_suggestion_msg(ft)
+ local null_formatters = require "lvim.lsp.null-ls.formatters"
+ local supported_formatters = null_formatters.list_available(ft)
+ local section = {
+ " HINT ",
+ "",
+ fmt("* List of supported formatters: %s", str_list(supported_formatters)),
+ }
+
+ if not vim.tbl_isempty(supported_formatters) then
+ vim.list_extend(section, {
+ "* Configured formatter needs to be installed and executable.",
+ fmt("* Enable installed formatter(s) with following config in %s", user_config_file),
+ "",
+ fmt(" lvim.lang.%s.formatters = { { exe = '%s' } }", ft, table.concat(supported_formatters, "│")),
+ })
+ end
+
+ return section
+end
+
+local function get_linter_suggestion_msg(ft)
+ local null_linters = require "lvim.lsp.null-ls.linters"
+ local supported_linters = null_linters.list_available(ft)
+ local section = {
+ " HINT ",
+ "",
+ fmt("* List of supported linters: %s", str_list(supported_linters)),
+ }
+
+ if not vim.tbl_isempty(supported_linters) then
+ vim.list_extend(section, {
+ "* Configured linter needs to be installed and executable.",
+ fmt("* Enable installed linter(s) with following config in %s", user_config_file),
+ "",
+ fmt(" lvim.lang.%s.linters = { { exe = '%s' } }", ft, table.concat(supported_linters, "│")),
+ })
+ end
+
+ return section
+end
+
+local function tbl_set_highlight(terms, highlight_group)
+ for _, v in pairs(terms) do
+ vim.cmd('let m=matchadd("' .. highlight_group .. '", "' .. v .. "[ ,│']\")")
+ end
+end
+
+local function make_client_info(client)
+ local client_enabled_caps = lsp_utils.get_client_capabilities(client.id)
+ local name = client.name
+ local id = client.id
+ local document_formatting = client.resolved_capabilities.document_formatting
+ local client_info = {
+ fmt("* Name: %s", name),
+ fmt("* Id: %s", tostring(id)),
+ fmt("* Supports formatting: %s", tostring(document_formatting)),
+ }
+ if not vim.tbl_isempty(client_enabled_caps) then
+ local caps_text = "* Capabilities list: "
+ local caps_text_len = caps_text:len()
+ local enabled_caps = text.format_table(client_enabled_caps, 3, " | ")
+ enabled_caps = text.shift_right(enabled_caps, caps_text_len)
+ enabled_caps[1] = fmt("%s%s", caps_text, enabled_caps[1]:sub(caps_text_len + 1))
+ vim.list_extend(client_info, enabled_caps)
+ end
+
+ return client_info
+end
+
+function M.toggle_popup(ft)
+ local clients = lsp_utils.get_active_clients_by_ft(ft)
+ local client_names = {}
+
+ local header = {
+ fmt("Detected filetype: %s", ft),
+ fmt("Treesitter active: %s", tostring(next(vim.treesitter.highlighter.active) ~= nil)),
+ }
+
+ local lsp_info = {
+ "Language Server Protocol (LSP) info",
+ fmt "* Associated server(s):",
+ }
+
+ for _, client in pairs(clients) do
+ vim.list_extend(lsp_info, make_client_info(client))
+ table.insert(client_names, client.name)
+ end
+
+ local null_formatters = require "lvim.lsp.null-ls.formatters"
+ local null_linters = require "lvim.lsp.null-ls.linters"
+ local registered_formatters = null_formatters.list_supported_names(ft)
+ local registered_linters = null_linters.list_supported_names(ft)
+ local registered_providers = {}
+ vim.list_extend(registered_providers, registered_formatters)
+ vim.list_extend(registered_providers, registered_linters)
+ local registered_count = vim.tbl_count(registered_providers)
+ local null_ls_info = {
+ "Formatters and linters",
+ fmt(
+ "* Configured providers: %s%s",
+ table.concat(registered_providers, "  , "),
+ registered_count > 0 and "  " or ""
+ ),
+ }
+
+ local content_provider = function(popup)
+ local content = {}
+
+ for _, section in ipairs {
+ M.banner,
+ { "" },
+ { "" },
+ header,
+ { "" },
+ lsp_info,
+ { "" },
+ null_ls_info,
+ { "" },
+ { "" },
+ get_formatter_suggestion_msg(ft),
+ { "" },
+ { "" },
+ get_linter_suggestion_msg(ft),
+ } do
+ vim.list_extend(content, section)
+ end
+
+ return text.align_left(popup, content, 0.5)
+ end
+
+ local function set_syntax_hl()
+ vim.cmd [[highlight LvimInfoIdentifier gui=bold]]
+ vim.cmd [[highlight link LvimInfoHeader Type]]
+ vim.cmd [[let m=matchadd("LvimInfoHeader", "Language Server Protocol (LSP) info")]]
+ vim.cmd [[let m=matchadd("LvimInfoHeader", "Formatters and linters")]]
+ vim.cmd('let m=matchadd("LvimInfoIdentifier", " ' .. ft .. '$")')
+ vim.cmd 'let m=matchadd("string", "true")'
+ vim.cmd 'let m=matchadd("error", "false")'
+ tbl_set_highlight(registered_providers, "LvimInfoIdentifier")
+ -- tbl_set_highlight(require("lvim.lsp.null-ls.formatters").list_available(ft), "LvimInfoIdentifier")
+ -- tbl_set_highlight(require("lvim.lsp.null-ls.linters").list_available(ft), "LvimInfoIdentifier")
+ end
+
+ local Popup = require("lvim.interface.popup"):new {
+ win_opts = { number = false },
+ buf_opts = { modifiable = false, filetype = "lspinfo" },
+ }
+ Popup:display(content_provider)
+ set_syntax_hl()
+
+ return Popup
+end
+return M
diff --git a/lua/lvim/core/log.lua b/lua/lvim/core/log.lua
new file mode 100644
index 00000000..fca1fcb4
--- /dev/null
+++ b/lua/lvim/core/log.lua
@@ -0,0 +1,60 @@
+local Log = {}
+
+--- Adds a log entry using Plenary.log
+---@param msg any
+---@param level string [same as vim.log.log_levels]
+function Log:add_entry(msg, level)
+ assert(type(level) == "string")
+ if self.__handle then
+ -- plenary uses lower-case log levels
+ self.__handle[level:lower()](msg)
+ return
+ end
+ local status_ok, plenary = pcall(require, "plenary")
+ if status_ok then
+ local default_opts = { plugin = "lunarvim", level = lvim.log.level }
+ local handle = plenary.log.new(default_opts)
+ handle[level:lower()](msg)
+ self.__handle = handle
+ end
+ -- don't do anything if plenary is not available
+end
+
+---Retrieves the path of the logfile
+---@return string path of the logfile
+function Log:get_path()
+ return string.format("%s/%s.log", vim.fn.stdpath "cache", "lunarvim")
+end
+
+---Add a log entry at TRACE level
+---@param msg any
+function Log:trace(msg)
+ self:add_entry(msg, "TRACE")
+end
+
+---Add a log entry at DEBUG level
+---@param msg any
+function Log:debug(msg)
+ self:add_entry(msg, "DEBUG")
+end
+
+---Add a log entry at INFO level
+---@param msg any
+function Log:info(msg)
+ self:add_entry(msg, "INFO")
+end
+
+---Add a log entry at WARN level
+---@param msg any
+function Log:warn(msg)
+ self:add_entry(msg, "WARN")
+end
+
+---Add a log entry at ERROR level
+---@param msg any
+function Log:error(msg)
+ self:add_entry(msg, "ERROR")
+end
+
+setmetatable({}, Log)
+return Log
diff --git a/lua/lvim/core/lualine/colors.lua b/lua/lvim/core/lualine/colors.lua
new file mode 100644
index 00000000..4984cd1f
--- /dev/null
+++ b/lua/lvim/core/lualine/colors.lua
@@ -0,0 +1,16 @@
+local colors = {
+ bg = "#202328",
+ fg = "#bbc2cf",
+ yellow = "#ECBE7B",
+ cyan = "#008080",
+ darkblue = "#081633",
+ green = "#98be65",
+ orange = "#FF8800",
+ violet = "#a9a1e1",
+ magenta = "#c678dd",
+ purple = "#c678dd",
+ blue = "#51afef",
+ red = "#ec5f67",
+}
+
+return colors
diff --git a/lua/lvim/core/lualine/components.lua b/lua/lvim/core/lualine/components.lua
new file mode 100644
index 00000000..5c0fb84b
--- /dev/null
+++ b/lua/lvim/core/lualine/components.lua
@@ -0,0 +1,154 @@
+local conditions = require "lvim.core.lualine.conditions"
+local colors = require "lvim.core.lualine.colors"
+
+local function diff_source()
+ local gitsigns = vim.b.gitsigns_status_dict
+ if gitsigns then
+ return {
+ added = gitsigns.added,
+ modified = gitsigns.changed,
+ removed = gitsigns.removed,
+ }
+ end
+end
+
+return {
+ mode = {
+ function()
+ return " "
+ end,
+ padding = { left = 0, right = 0 },
+ color = {},
+ cond = nil,
+ },
+ branch = {
+ "b:gitsigns_head",
+ icon = " ",
+ color = { gui = "bold" },
+ cond = conditions.hide_in_width,
+ },
+ filename = {
+ "filename",
+ color = {},
+ cond = nil,
+ },
+ diff = {
+ "diff",
+ source = diff_source,
+ symbols = { added = "  ", modified = "柳", removed = " " },
+ diff_color = {
+ added = { fg = colors.green },
+ modified = { fg = colors.yellow },
+ removed = { fg = colors.red },
+ },
+ color = {},
+ cond = nil,
+ },
+ python_env = {
+ function()
+ local utils = require "lvim.core.lualine.utils"
+ if vim.bo.filetype == "python" then
+ local venv = os.getenv "CONDA_DEFAULT_ENV"
+ if venv then
+ return string.format("  (%s)", utils.env_cleanup(venv))
+ end
+ venv = os.getenv "VIRTUAL_ENV"
+ if venv then
+ return string.format("  (%s)", utils.env_cleanup(venv))
+ end
+ return ""
+ end
+ return ""
+ end,
+ color = { fg = colors.green },
+ cond = conditions.hide_in_width,
+ },
+ diagnostics = {
+ "diagnostics",
+ sources = { "nvim_lsp" },
+ symbols = { error = " ", warn = " ", info = " ", hint = " " },
+ color = {},
+ cond = conditions.hide_in_width,
+ },
+ treesitter = {
+ function()
+ local b = vim.api.nvim_get_current_buf()
+ if next(vim.treesitter.highlighter.active[b]) then
+ return "  "
+ end
+ return ""
+ end,
+ color = { fg = colors.green },
+ cond = conditions.hide_in_width,
+ },
+ lsp = {
+ function(msg)
+ msg = msg or "LS Inactive"
+ local buf_clients = vim.lsp.buf_get_clients()
+ if next(buf_clients) == nil then
+ -- TODO: clean up this if statement
+ if type(msg) == "boolean" or #msg == 0 then
+ return "LS Inactive"
+ end
+ return msg
+ end
+ local buf_ft = vim.bo.filetype
+ local buf_client_names = {}
+
+ -- add client
+ for _, client in pairs(buf_clients) do
+ if client.name ~= "null-ls" then
+ table.insert(buf_client_names, client.name)
+ end
+ end
+
+ -- add formatter
+ local formatters = require "lvim.lsp.null-ls.formatters"
+ local supported_formatters = formatters.list_supported_names(buf_ft)
+ vim.list_extend(buf_client_names, supported_formatters)
+
+ -- add linter
+ local linters = require "lvim.lsp.null-ls.linters"
+ local supported_linters = linters.list_supported_names(buf_ft)
+ vim.list_extend(buf_client_names, supported_linters)
+
+ return table.concat(buf_client_names, ", ")
+ end,
+ icon = " ",
+ color = { gui = "bold" },
+ cond = conditions.hide_in_width,
+ },
+ location = { "location", cond = conditions.hide_in_width, color = {} },
+ progress = { "progress", cond = conditions.hide_in_width, color = {} },
+ spaces = {
+ function()
+ local label = "Spaces: "
+ if not vim.api.nvim_buf_get_option(0, "expandtab") then
+ label = "Tab size: "
+ end
+ return label .. vim.api.nvim_buf_get_option(0, "shiftwidth") .. " "
+ end,
+ cond = conditions.hide_in_width,
+ color = {},
+ },
+ encoding = {
+ "o:encoding",
+ fmt = string.upper,
+ color = {},
+ cond = conditions.hide_in_width,
+ },
+ filetype = { "filetype", cond = conditions.hide_in_width, color = {} },
+ scrollbar = {
+ function()
+ local current_line = vim.fn.line "."
+ local total_lines = vim.fn.line "$"
+ local chars = { "__", "▁▁", "▂▂", "▃▃", "▄▄", "▅▅", "▆▆", "▇▇", "██" }
+ local line_ratio = current_line / total_lines
+ local index = math.ceil(line_ratio * #chars)
+ return chars[index]
+ end,
+ padding = { left = 0, right = 0 },
+ color = { fg = colors.yellow, bg = colors.bg },
+ cond = nil,
+ },
+}
diff --git a/lua/lvim/core/lualine/conditions.lua b/lua/lvim/core/lualine/conditions.lua
new file mode 100644
index 00000000..3ee4fbb8
--- /dev/null
+++ b/lua/lvim/core/lualine/conditions.lua
@@ -0,0 +1,17 @@
+local window_width_limit = 80
+
+local conditions = {
+ buffer_not_empty = function()
+ return vim.fn.empty(vim.fn.expand "%:t") ~= 1
+ end,
+ hide_in_width = function()
+ return vim.fn.winwidth(0) > window_width_limit
+ end,
+ -- check_git_workspace = function()
+ -- local filepath = vim.fn.expand "%:p:h"
+ -- local gitdir = vim.fn.finddir(".git", filepath .. ";")
+ -- return gitdir and #gitdir > 0 and #gitdir < #filepath
+ -- end,
+}
+
+return conditions
diff --git a/lua/lvim/core/lualine/init.lua b/lua/lvim/core/lualine/init.lua
new file mode 100644
index 00000000..c5d024c2
--- /dev/null
+++ b/lua/lvim/core/lualine/init.lua
@@ -0,0 +1,47 @@
+local M = {}
+M.config = function()
+ lvim.builtin.lualine = {
+ active = true,
+ style = "lvim",
+ options = {
+ icons_enabled = nil,
+ component_separators = nil,
+ section_separators = nil,
+ theme = nil,
+ disabled_filetypes = nil,
+ },
+ sections = {
+ lualine_a = nil,
+ lualine_b = nil,
+ lualine_c = nil,
+ lualine_x = nil,
+ lualine_y = nil,
+ lualine_z = nil,
+ },
+ inactive_sections = {
+ lualine_a = nil,
+ lualine_b = nil,
+ lualine_c = nil,
+ lualine_x = nil,
+ lualine_y = nil,
+ lualine_z = nil,
+ },
+ tabline = nil,
+ extensions = nil,
+ on_config_done = nil,
+ }
+end
+
+M.setup = function()
+ require("lvim.core.lualine.styles").update()
+ require("lvim.core.lualine.utils").validate_theme()
+
+ local lualine = require "lualine"
+ lualine.setup(lvim.builtin.lualine)
+
+ if lvim.builtin.lualine.on_config_done then
+ lvim.builtin.lualine.on_config_done(lualine)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/lualine/styles.lua b/lua/lvim/core/lualine/styles.lua
new file mode 100644
index 00000000..0843aead
--- /dev/null
+++ b/lua/lvim/core/lualine/styles.lua
@@ -0,0 +1,137 @@
+local M = {}
+local components = require "lvim.core.lualine.components"
+
+local styles = {
+ lvim = nil,
+ default = nil,
+ none = nil,
+}
+
+styles.none = {
+ style = "none",
+ options = {
+ icons_enabled = true,
+ component_separators = { left = "", right = "" },
+ section_separators = { left = "", right = "" },
+ disabled_filetypes = {},
+ },
+ sections = {
+ lualine_a = {},
+ lualine_b = {},
+ lualine_c = {},
+ lualine_x = {},
+ lualine_y = {},
+ lualine_z = {},
+ },
+ inactive_sections = {
+ lualine_a = {},
+ lualine_b = {},
+ lualine_c = {},
+ lualine_x = {},
+ lualine_y = {},
+ lualine_z = {},
+ },
+ tabline = {},
+ extensions = {},
+}
+
+styles.default = {
+ style = "default",
+ options = {
+ icons_enabled = true,
+ component_separators = { left = "", right = "" },
+ section_separators = { left = "", right = "" },
+ disabled_filetypes = {},
+ },
+ sections = {
+ lualine_a = { "mode" },
+ lualine_b = { "branch" },
+ lualine_c = { "filename" },
+ lualine_x = { "encoding", "fileformat", "filetype" },
+ lualine_y = { "progress" },
+ lualine_z = { "location" },
+ },
+ inactive_sections = {
+ lualine_a = {},
+ lualine_b = {},
+ lualine_c = { "filename" },
+ lualine_x = { "location" },
+ lualine_y = {},
+ lualine_z = {},
+ },
+ tabline = {},
+ extensions = {},
+}
+
+styles.lvim = {
+ style = "lvim",
+ options = {
+ icons_enabled = true,
+ component_separators = { left = "", right = "" },
+ section_separators = { left = "", right = "" },
+ disabled_filetypes = { "dashboard", "NvimTree", "Outline" },
+ },
+ sections = {
+ lualine_a = {
+ components.mode,
+ },
+ lualine_b = {
+ components.branch,
+ components.filename,
+ },
+ lualine_c = {
+ components.diff,
+ components.python_env,
+ },
+ lualine_x = {
+ components.diagnostics,
+ components.treesitter,
+ components.lsp,
+ components.filetype,
+ },
+ lualine_y = {},
+ lualine_z = {
+ components.scrollbar,
+ },
+ },
+ inactive_sections = {
+ lualine_a = {
+ "filename",
+ },
+ lualine_b = {},
+ lualine_c = {},
+ lualine_x = {},
+ lualine_y = {},
+ lualine_z = {},
+ },
+ tabline = {},
+ extensions = { "nvim-tree" },
+}
+
+function M.get_style(style)
+ local style_keys = vim.tbl_keys(styles)
+ if not vim.tbl_contains(style_keys, style) then
+ local Log = require "lvim.core.log"
+ Log:error(
+ "Invalid lualine style",
+ string.format('"%s"', style),
+ "options are: ",
+ string.format('"%s"', table.concat(style_keys, '", "'))
+ )
+ Log:debug '"lvim" style is applied.'
+ style = "lvim"
+ end
+
+ return vim.deepcopy(styles[style])
+end
+
+function M.update()
+ local style = M.get_style(lvim.builtin.lualine.style)
+ if lvim.builtin.lualine.options.theme == nil then
+ lvim.builtin.lualine.options.theme = lvim.colorscheme
+ end
+
+ lvim.builtin.lualine = vim.tbl_deep_extend("keep", lvim.builtin.lualine, style)
+end
+
+return M
diff --git a/lua/lvim/core/lualine/utils.lua b/lua/lvim/core/lualine/utils.lua
new file mode 100644
index 00000000..cf80a99e
--- /dev/null
+++ b/lua/lvim/core/lualine/utils.lua
@@ -0,0 +1,27 @@
+local M = {}
+
+function M.validate_theme()
+ local theme = lvim.builtin.lualine.options.theme
+ if type(theme) == "table" then
+ return
+ end
+
+ local lualine_loader = require "lualine.utils.loader"
+ local ok = pcall(lualine_loader.load_theme, theme)
+ if not ok then
+ lvim.builtin.lualine.options.theme = "auto"
+ end
+end
+
+function M.env_cleanup(venv)
+ if string.find(venv, "/") then
+ local final_venv = venv
+ for w in venv:gmatch "([^/]+)" do
+ final_venv = w
+ end
+ venv = final_venv
+ end
+ return venv
+end
+
+return M
diff --git a/lua/lvim/core/nvimtree.lua b/lua/lvim/core/nvimtree.lua
new file mode 100644
index 00000000..d9e6fb5d
--- /dev/null
+++ b/lua/lvim/core/nvimtree.lua
@@ -0,0 +1,141 @@
+local M = {}
+local Log = require "lvim.core.log"
+
+function M.config()
+ lvim.builtin.nvimtree = {
+ active = true,
+ on_config_done = nil,
+ setup = {
+ open_on_setup = false,
+ auto_close = true,
+ open_on_tab = false,
+ update_focused_file = {
+ enable = true,
+ },
+ diagnostics = {
+ enable = true,
+ icons = {
+ hint = "",
+ info = "",
+ warning = "",
+ error = "",
+ },
+ },
+ view = {
+ width = 30,
+ side = "left",
+ auto_resize = false,
+ mappings = {
+ custom_only = false,
+ },
+ },
+ },
+ show_icons = {
+ git = 1,
+ folders = 1,
+ files = 1,
+ folder_arrows = 1,
+ tree_width = 30,
+ },
+ ignore = { ".git", "node_modules", ".cache" },
+ quit_on_open = 0,
+ hide_dotfiles = 1,
+ git_hl = 1,
+ root_folder_modifier = ":t",
+ allow_resize = 1,
+ auto_ignore_ft = { "startify", "dashboard" },
+ icons = {
+ default = "",
+ symlink = "",
+ git = {
+ unstaged = "",
+ staged = "S",
+ unmerged = "",
+ renamed = "➜",
+ deleted = "",
+ untracked = "U",
+ ignored = "◌",
+ },
+ folder = {
+ default = "",
+ open = "",
+ empty = "",
+ empty_open = "",
+ symlink = "",
+ },
+ },
+ }
+end
+
+function M.setup()
+ local status_ok, nvim_tree_config = pcall(require, "nvim-tree.config")
+ if not status_ok then
+ Log:error "Failed to load nvim-tree.config"
+ return
+ end
+ local g = vim.g
+
+ for opt, val in pairs(lvim.builtin.nvimtree) do
+ g["nvim_tree_" .. opt] = val
+ end
+
+ -- Implicitly update nvim-tree when project module is active
+ if lvim.builtin.project.active then
+ lvim.builtin.nvimtree.respect_buf_cwd = 1
+ lvim.builtin.nvimtree.setup.update_cwd = true
+ lvim.builtin.nvimtree.setup.disable_netrw = false
+ lvim.builtin.nvimtree.setup.hijack_netrw = false
+ vim.g.netrw_banner = false
+ end
+
+ local tree_cb = nvim_tree_config.nvim_tree_callback
+
+ if not lvim.builtin.nvimtree.setup.view.mappings.list then
+ lvim.builtin.nvimtree.setup.view.mappings.list = {
+ { key = { "l", "<CR>", "o" }, cb = tree_cb "edit" },
+ { key = "h", cb = tree_cb "close_node" },
+ { key = "v", cb = tree_cb "vsplit" },
+ }
+ end
+
+ lvim.builtin.which_key.mappings["e"] = { "<cmd>NvimTreeToggle<CR>", "Explorer" }
+
+ local tree_view = require "nvim-tree.view"
+
+ -- Add nvim_tree open callback
+ local open = tree_view.open
+ tree_view.open = function()
+ M.on_open()
+ open()
+ end
+
+ vim.cmd "au WinClosed * lua require('lvim.core.nvimtree').on_close()"
+
+ if lvim.builtin.nvimtree.on_config_done then
+ lvim.builtin.nvimtree.on_config_done(nvim_tree_config)
+ end
+ require("nvim-tree").setup(lvim.builtin.nvimtree.setup)
+end
+
+function M.on_open()
+ if package.loaded["bufferline.state"] and lvim.builtin.nvimtree.setup.view.side == "left" then
+ require("bufferline.state").set_offset(lvim.builtin.nvimtree.setup.view.width + 1, "")
+ end
+end
+
+function M.on_close()
+ local buf = tonumber(vim.fn.expand "<abuf>")
+ local ft = vim.api.nvim_buf_get_option(buf, "filetype")
+ if ft == "NvimTree" and package.loaded["bufferline.state"] then
+ require("bufferline.state").set_offset(0)
+ end
+end
+
+function M.change_tree_dir(dir)
+ local lib_status_ok, lib = pcall(require, "nvim-tree.lib")
+ if lib_status_ok then
+ lib.change_dir(dir)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/project.lua b/lua/lvim/core/project.lua
new file mode 100644
index 00000000..e7527440
--- /dev/null
+++ b/lua/lvim/core/project.lua
@@ -0,0 +1,51 @@
+local M = {}
+
+function M.config()
+ lvim.builtin.project = {
+ ---@usage set to false to disable project.nvim.
+ --- This is on by default since it's currently the expected behavior.
+ active = true,
+
+ on_config_done = nil,
+
+ ---@usage set to true to disable setting the current-woriking directory
+ --- Manual mode doesn't automatically change your root directory, so you have
+ --- the option to manually do so using `:ProjectRoot` command.
+ manual_mode = false,
+
+ ---@usage Methods of detecting the root directory
+ --- Allowed values: **"lsp"** uses the native neovim lsp
+ --- **"pattern"** uses vim-rooter like glob pattern matching. Here
+ --- order matters: if one is not detected, the other is used as fallback. You
+ --- can also delete or rearangne the detection methods.
+ detection_methods = { "lsp", "pattern" },
+
+ ---@usage patterns used to detect root dir, when **"pattern"** is in detection_methods
+ patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json" },
+
+ ---@ Show hidden files in telescope when searching for files in a project
+ show_hidden = false,
+
+ ---@usage When set to false, you will get a message when project.nvim changes your directory.
+ -- When set to false, you will get a message when project.nvim changes your directory.
+ silent_chdir = true,
+
+ ---@usage list of lsp client names to ignore when using **lsp** detection. eg: { "efm", ... }
+ ignore_lsp = {},
+
+ ---@type string
+ ---@usage path to store the project history for use in telescope
+ datapath = get_cache_dir(),
+ }
+end
+
+function M.setup()
+ local project = require "project_nvim"
+
+ project.setup(lvim.builtin.project)
+ if lvim.builtin.project.on_config_done then
+ lvim.builtin.project.on_config_done(project)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/telescope.lua b/lua/lvim/core/telescope.lua
new file mode 100644
index 00000000..35f6b4a2
--- /dev/null
+++ b/lua/lvim/core/telescope.lua
@@ -0,0 +1,193 @@
+local M = {}
+
+function M.config()
+ -- Define this minimal config so that it's available if telescope is not yet available.
+ lvim.builtin.telescope = {
+ ---@usage disable telescope completely [not recommeded]
+ active = true,
+ on_config_done = nil,
+ }
+
+ local status_ok, actions = pcall(require, "telescope.actions")
+ if not status_ok then
+ return
+ end
+
+ lvim.builtin.telescope = vim.tbl_extend("force", lvim.builtin.telescope, {
+ defaults = {
+ prompt_prefix = " ",
+ selection_caret = " ",
+ entry_prefix = " ",
+ initial_mode = "insert",
+ selection_strategy = "reset",
+ sorting_strategy = "descending",
+ layout_strategy = "horizontal",
+ layout_config = {
+ width = 0.75,
+ preview_cutoff = 120,
+ horizontal = { mirror = false },
+ vertical = { mirror = false },
+ },
+ file_sorter = require("telescope.sorters").get_fzy_sorter,
+ file_ignore_patterns = {},
+ generic_sorter = require("telescope.sorters").get_generic_fuzzy_sorter,
+ path_display = { shorten = 5 },
+ winblend = 0,
+ border = {},
+ borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
+ color_devicons = true,
+ use_less = true,
+ set_env = { ["COLORTERM"] = "truecolor" }, -- default = nil,
+ file_previewer = require("telescope.previewers").vim_buffer_cat.new,
+ grep_previewer = require("telescope.previewers").vim_buffer_vimgrep.new,
+ qflist_previewer = require("telescope.previewers").vim_buffer_qflist.new,
+
+ -- Developer configurations: Not meant for general override
+ -- buffer_previewer_maker = require("telescope.previewers").buffer_previewer_maker,
+ mappings = {
+ i = {
+ ["<C-n>"] = actions.move_selection_next,
+ ["<C-p>"] = actions.move_selection_previous,
+ ["<C-c>"] = actions.close,
+ ["<C-j>"] = actions.cycle_history_next,
+ ["<C-k>"] = actions.cycle_history_prev,
+ ["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
+ ["<CR>"] = actions.select_default + actions.center,
+ -- To disable a keymap, put [map] = false
+ -- So, to not map "<C-n>", just put
+ -- ["<c-t>"] = trouble.open_with_trouble,
+ -- ["<c-x>"] = false,
+ -- ["<esc>"] = actions.close,
+ -- Otherwise, just set the mapping to the function that you want it to be.
+ -- ["<C-i>"] = actions.select_horizontal,
+ -- Add up multiple actions
+ -- You can perform as many actions in a row as you like
+ -- ["<CR>"] = actions.select_default + actions.center + my_cool_custom_action,
+ },
+ n = {
+ ["<C-n>"] = actions.move_selection_next,
+ ["<C-p>"] = actions.move_selection_previous,
+ ["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist,
+ -- ["<c-t>"] = trouble.open_with_trouble,
+ -- ["<C-i>"] = my_cool_custom_action,
+ },
+ },
+ },
+ extensions = {
+ fzy_native = {
+ override_generic_sorter = false,
+ override_file_sorter = true,
+ },
+ },
+ })
+end
+
+function M.find_lunarvim_files(opts)
+ opts = opts or {}
+ local themes = require "telescope.themes"
+ local theme_opts = themes.get_ivy {
+ sorting_strategy = "ascending",
+ layout_strategy = "bottom_pane",
+ prompt_prefix = ">> ",
+ prompt_title = "~ LunarVim files ~",
+ cwd = get_runtime_dir(),
+ search_dirs = { get_runtime_dir() .. "/lvim", lvim.lsp.templates_dir },
+ }
+ opts = vim.tbl_deep_extend("force", theme_opts, opts)
+ require("telescope.builtin").find_files(opts)
+end
+
+function M.grep_lunarvim_files(opts)
+ opts = opts or {}
+ local themes = require "telescope.themes"
+ local theme_opts = themes.get_ivy {
+ sorting_strategy = "ascending",
+ layout_strategy = "bottom_pane",
+ prompt_prefix = ">> ",
+ prompt_title = "~ search LunarVim ~",
+ cwd = get_runtime_dir(),
+ search_dirs = { get_runtime_dir() .. "/lvim", lvim.lsp.templates_dir },
+ }
+ opts = vim.tbl_deep_extend("force", theme_opts, opts)
+ require("telescope.builtin").live_grep(opts)
+end
+
+function M.view_lunarvim_changelog()
+ local finders = require "telescope.finders"
+ local make_entry = require "telescope.make_entry"
+ local pickers = require "telescope.pickers"
+ local previewers = require "telescope.previewers"
+ local actions = require "telescope.actions"
+ local opts = {}
+
+ local conf = require("telescope.config").values
+ opts.entry_maker = make_entry.gen_from_git_commits(opts)
+
+ pickers.new(opts, {
+ prompt_title = "LunarVim changelog",
+
+ finder = finders.new_oneshot_job(
+ vim.tbl_flatten {
+ "git",
+ "log",
+ "--pretty=oneline",
+ "--abbrev-commit",
+ "--",
+ ".",
+ },
+ opts
+ ),
+ previewer = {
+ previewers.git_commit_diff_to_parent.new(opts),
+ previewers.git_commit_diff_to_head.new(opts),
+ previewers.git_commit_diff_as_was.new(opts),
+ previewers.git_commit_message.new(opts),
+ },
+
+ --TODO: consider opening a diff view when pressing enter
+ attach_mappings = function(_, map)
+ map("i", "<enter>", actions._close)
+ map("n", "<enter>", actions._close)
+ map("i", "<esc>", actions._close)
+ map("n", "<esc>", actions._close)
+ map("n", "q", actions._close)
+ return true
+ end,
+ sorter = conf.file_sorter(opts),
+ }):find()
+end
+
+function M.code_actions()
+ local opts = {
+ winblend = 15,
+ layout_config = {
+ prompt_position = "top",
+ width = 80,
+ height = 12,
+ },
+ borderchars = {
+ prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" },
+ results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" },
+ preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
+ },
+ border = {},
+ previewer = false,
+ shorten_path = false,
+ }
+ require("telescope.builtin").lsp_code_actions(require("telescope.themes").get_dropdown(opts))
+end
+
+function M.setup()
+ local telescope = require "telescope"
+
+ telescope.setup(lvim.builtin.telescope)
+ if lvim.builtin.project.active then
+ telescope.load_extension "projects"
+ end
+
+ if lvim.builtin.telescope.on_config_done then
+ lvim.builtin.telescope.on_config_done(telescope)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/terminal.lua b/lua/lvim/core/terminal.lua
new file mode 100644
index 00000000..aa6989ec
--- /dev/null
+++ b/lua/lvim/core/terminal.lua
@@ -0,0 +1,114 @@
+local M = {}
+local Log = require "lvim.core.log"
+
+M.config = function()
+ lvim.builtin["terminal"] = {
+ on_config_done = nil,
+ -- size can be a number or function which is passed the current terminal
+ size = 20,
+ -- open_mapping = [[<c-\>]],
+ open_mapping = [[<c-t>]],
+ hide_numbers = true, -- hide the number column in toggleterm buffers
+ shade_filetypes = {},
+ shade_terminals = true,
+ shading_factor = 2, -- the degree by which to darken to terminal colour, default: 1 for dark backgrounds, 3 for light
+ start_in_insert = true,
+ insert_mappings = true, -- whether or not the open mapping applies in insert mode
+ persist_size = false,
+ -- direction = 'vertical' | 'horizontal' | 'window' | 'float',
+ direction = "float",
+ close_on_exit = true, -- close the terminal window when the process exits
+ shell = vim.o.shell, -- change the default shell
+ -- This field is only relevant if direction is set to 'float'
+ float_opts = {
+ -- The border key is *almost* the same as 'nvim_win_open'
+ -- see :h nvim_win_open for details on borders however
+ -- the 'curved' border is a custom border type
+ -- not natively supported but implemented in this plugin.
+ -- border = 'single' | 'double' | 'shadow' | 'curved' | ... other options supported by win open
+ border = "curved",
+ -- width = <value>,
+ -- height = <value>,
+ winblend = 0,
+ highlights = {
+ border = "Normal",
+ background = "Normal",
+ },
+ },
+ -- Add executables on the config.lua
+ -- { exec, keymap, name}
+ -- lvim.builtin.terminal.execs = {{}} to overwrite
+ -- lvim.builtin.terminal.execs[#lvim.builtin.terminal.execs+1] = {"gdb", "tg", "GNU Debugger"}
+ execs = {
+ { "lazygit", "gg", "LazyGit" },
+ },
+ }
+end
+
+M.setup = function()
+ local terminal = require "toggleterm"
+ for _, exec in pairs(lvim.builtin.terminal.execs) do
+ require("lvim.core.terminal").add_exec(exec[1], exec[2], exec[3])
+ end
+ terminal.setup(lvim.builtin.terminal)
+
+ if lvim.builtin.terminal.on_config_done then
+ lvim.builtin.terminal.on_config_done(terminal)
+ end
+end
+
+M.add_exec = function(exec, keymap, name)
+ vim.api.nvim_set_keymap(
+ "n",
+ "<leader>" .. keymap,
+ "<cmd>lua require('lvim.core.terminal')._exec_toggle('" .. exec .. "')<CR>",
+ { noremap = true, silent = true }
+ )
+ lvim.builtin.which_key.mappings[keymap] = name
+end
+
+M._split = function(inputstr, sep)
+ if sep == nil then
+ sep = "%s"
+ end
+ local t = {}
+ for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
+ table.insert(t, str)
+ end
+ return t
+end
+
+M._exec_toggle = function(exec)
+ local binary = M._split(exec)[1]
+ if vim.fn.executable(binary) ~= 1 then
+ Log:error("Unable to run executable " .. binary .. ". Please make sure it is installed properly.")
+ return
+ end
+ local Terminal = require("toggleterm.terminal").Terminal
+ local exec_term = Terminal:new { cmd = exec, hidden = true }
+ exec_term:toggle()
+end
+
+---Toggles a log viewer according to log.viewer.layout_config
+---@param logfile string the fullpath to the logfile
+M.toggle_log_view = function(logfile)
+ local log_viewer = lvim.log.viewer.cmd
+ if vim.fn.executable(log_viewer) ~= 1 then
+ log_viewer = "less +F"
+ end
+ log_viewer = log_viewer .. " " .. logfile
+ local term_opts = vim.tbl_deep_extend("force", lvim.builtin.terminal, {
+ cmd = log_viewer,
+ open_mapping = lvim.log.viewer.layout_config.open_mapping,
+ direction = lvim.log.viewer.layout_config.direction,
+ -- TODO: this might not be working as expected
+ size = lvim.log.viewer.layout_config.size,
+ float_opts = lvim.log.viewer.layout_config.float_opts,
+ })
+
+ local Terminal = require("toggleterm.terminal").Terminal
+ local log_view = Terminal:new(term_opts)
+ log_view:toggle()
+end
+
+return M
diff --git a/lua/lvim/core/treesitter.lua b/lua/lvim/core/treesitter.lua
new file mode 100644
index 00000000..ce99deba
--- /dev/null
+++ b/lua/lvim/core/treesitter.lua
@@ -0,0 +1,81 @@
+local M = {}
+local Log = require "lvim.core.log"
+
+M.config = function()
+ lvim.builtin.treesitter = {
+ on_config_done = nil,
+ ensure_installed = {}, -- one of "all", "maintained" (parsers with maintainers), or a list of languages
+ ignore_install = {},
+ matchup = {
+ enable = false, -- mandatory, false will disable the whole extension
+ -- disable = { "c", "ruby" }, -- optional, list of language that will be disabled
+ },
+ highlight = {
+ enable = true, -- false will disable the whole extension
+ additional_vim_regex_highlighting = true,
+ disable = { "latex" },
+ },
+ context_commentstring = {
+ enable = false,
+ config = { css = "// %s" },
+ },
+ -- indent = {enable = true, disable = {"python", "html", "javascript"}},
+ -- TODO seems to be broken
+ indent = { enable = true, disable = { "yaml" } },
+ autotag = { enable = false },
+ textobjects = {
+ swap = {
+ enable = false,
+ -- swap_next = textobj_swap_keymaps,
+ },
+ -- move = textobj_move_keymaps,
+ select = {
+ enable = false,
+ -- keymaps = textobj_sel_keymaps,
+ },
+ },
+ textsubjects = {
+ enable = false,
+ keymaps = { ["."] = "textsubjects-smart", [";"] = "textsubjects-big" },
+ },
+ playground = {
+ enable = false,
+ disable = {},
+ updatetime = 25, -- Debounced time for highlighting nodes in the playground from source code
+ persist_queries = false, -- Whether the query persists across vim sessions
+ keybindings = {
+ toggle_query_editor = "o",
+ toggle_hl_groups = "i",
+ toggle_injected_languages = "t",
+ toggle_anonymous_nodes = "a",
+ toggle_language_display = "I",
+ focus_language = "f",
+ unfocus_language = "F",
+ update = "R",
+ goto_node = "<cr>",
+ show_help = "?",
+ },
+ },
+ rainbow = {
+ enable = false,
+ extended_mode = true, -- Highlight also non-parentheses delimiters, boolean or table: lang -> boolean
+ max_file_lines = 1000, -- Do not enable for files with more than 1000 lines, int
+ },
+ }
+end
+
+M.setup = function()
+ local status_ok, treesitter_configs = pcall(require, "nvim-treesitter.configs")
+ if not status_ok then
+ Log:get_default().error "Failed to load nvim-treesitter.configs"
+ return
+ end
+
+ treesitter_configs.setup(lvim.builtin.treesitter)
+
+ if lvim.builtin.treesitter.on_config_done then
+ lvim.builtin.treesitter.on_config_done(treesitter_configs)
+ end
+end
+
+return M
diff --git a/lua/lvim/core/which-key.lua b/lua/lvim/core/which-key.lua
new file mode 100644
index 00000000..15f63273
--- /dev/null
+++ b/lua/lvim/core/which-key.lua
@@ -0,0 +1,270 @@
+local M = {}
+
+M.config = function()
+ lvim.builtin.which_key = {
+ ---@usage disable which-key completely [not recommeded]
+ active = true,
+ on_config_done = nil,
+ setup = {
+ plugins = {
+ marks = true, -- shows a list of your marks on ' and `
+ registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
+ -- the presets plugin, adds help for a bunch of default keybindings in Neovim
+ -- No actual key bindings are created
+ presets = {
+ operators = false, -- adds help for operators like d, y, ...
+ motions = false, -- adds help for motions
+ text_objects = false, -- help for text objects triggered after entering an operator
+ windows = true, -- default bindings on <c-w>
+ nav = true, -- misc bindings to work with windows
+ z = true, -- bindings for folds, spelling and others prefixed with z
+ g = true, -- bindings for prefixed with g
+ },
+ spelling = { enabled = true, suggestions = 20 }, -- use which-key for spelling hints
+ },
+ icons = {
+ breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
+ separator = "➜", -- symbol used between a key and it's label
+ group = "+", -- symbol prepended to a group
+ },
+ window = {
+ border = "single", -- none, single, double, shadow
+ position = "bottom", -- bottom, top
+ margin = { 1, 0, 1, 0 }, -- extra window margin [top, right, bottom, left]
+ padding = { 2, 2, 2, 2 }, -- extra window padding [top, right, bottom, left]
+ },
+ layout = {
+ height = { min = 4, max = 25 }, -- min and max height of the columns
+ width = { min = 20, max = 50 }, -- min and max width of the columns
+ spacing = 3, -- spacing between columns
+ },
+ hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "call", "lua", "^:", "^ " }, -- hide mapping boilerplate
+ show_help = true, -- show help message on the command line when the popup is visible
+ },
+
+ opts = {
+ mode = "n", -- NORMAL mode
+ prefix = "<leader>",
+ buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
+ silent = true, -- use `silent` when creating keymaps
+ noremap = true, -- use `noremap` when creating keymaps
+ nowait = true, -- use `nowait` when creating keymaps
+ },
+ vopts = {
+ mode = "v", -- VISUAL mode
+ prefix = "<leader>",
+ buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
+ silent = true, -- use `silent` when creating keymaps
+ noremap = true, -- use `noremap` when creating keymaps
+ nowait = true, -- use `nowait` when creating keymaps
+ },
+ -- NOTE: Prefer using : over <cmd> as the latter avoids going back in normal-mode.
+ -- see https://neovim.io/doc/user/map.html#:map-cmd
+ vmappings = {
+ ["/"] = { ":CommentToggle<CR>", "Comment" },
+ },
+ mappings = {
+ ["w"] = { "<cmd>w!<CR>", "Save" },
+ ["q"] = { "<cmd>q!<CR>", "Quit" },
+ ["/"] = { "<cmd>CommentToggle<CR>", "Comment" },
+ ["c"] = { "<cmd>BufferClose!<CR>", "Close Buffer" },
+ ["f"] = { "<cmd>Telescope find_files<CR>", "Find File" },
+ ["h"] = { "<cmd>nohlsearch<CR>", "No Highlight" },
+ b = {
+ name = "Buffers",
+ j = { "<cmd>BufferPick<cr>", "Jump" },
+ f = { "<cmd>Telescope buffers<cr>", "Find" },
+ b = { "<cmd>b#<cr>", "Previous" },
+ w = { "<cmd>BufferWipeout<cr>", "Wipeout" },
+ e = {
+ "<cmd>BufferCloseAllButCurrent<cr>",
+ "Close all but current",
+ },
+ h = { "<cmd>BufferCloseBuffersLeft<cr>", "Close all to the left" },
+ l = {
+ "<cmd>BufferCloseBuffersRight<cr>",
+ "Close all to the right",
+ },
+ D = {
+ "<cmd>BufferOrderByDirectory<cr>",
+ "Sort by directory",
+ },
+ L = {
+ "<cmd>BufferOrderByLanguage<cr>",
+ "Sort by language",
+ },
+ },
+ p = {
+ name = "Packer",
+ c = { "<cmd>PackerCompile<cr>", "Compile" },
+ i = { "<cmd>PackerInstall<cr>", "Install" },
+ r = { "<cmd>lua require('lvim.utils').reload_lv_config()<cr>", "Reload" },
+ s = { "<cmd>PackerSync<cr>", "Sync" },
+ S = { "<cmd>PackerStatus<cr>", "Status" },
+ u = { "<cmd>PackerUpdate<cr>", "Update" },
+ },
+
+ -- " Available Debug Adapters:
+ -- " https://microsoft.github.io/debug-adapter-protocol/implementors/adapters/
+ -- " Adapter configuration and installation instructions:
+ -- " https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation
+ -- " Debug Adapter protocol:
+ -- " https://microsoft.github.io/debug-adapter-protocol/
+ -- " Debugging
+ g = {
+ name = "Git",
+ j = { "<cmd>lua require 'gitsigns'.next_hunk()<cr>", "Next Hunk" },
+ k = { "<cmd>lua require 'gitsigns'.prev_hunk()<cr>", "Prev Hunk" },
+ l = { "<cmd>lua require 'gitsigns'.blame_line()<cr>", "Blame" },
+ p = { "<cmd>lua require 'gitsigns'.preview_hunk()<cr>", "Preview Hunk" },
+ r = { "<cmd>lua require 'gitsigns'.reset_hunk()<cr>", "Reset Hunk" },
+ R = { "<cmd>lua require 'gitsigns'.reset_buffer()<cr>", "Reset Buffer" },
+ s = { "<cmd>lua require 'gitsigns'.stage_hunk()<cr>", "Stage Hunk" },
+ u = {
+ "<cmd>lua require 'gitsigns'.undo_stage_hunk()<cr>",
+ "Undo Stage Hunk",
+ },
+ o = { "<cmd>Telescope git_status<cr>", "Open changed file" },
+ b = { "<cmd>Telescope git_branches<cr>", "Checkout branch" },
+ c = { "<cmd>Telescope git_commits<cr>", "Checkout commit" },
+ C = {
+ "<cmd>Telescope git_bcommits<cr>",
+ "Checkout commit(for current file)",
+ },
+ d = {
+ "<cmd>Gitsigns diffthis HEAD<cr>",
+ "Git Diff",
+ },
+ },
+
+ l = {
+ name = "LSP",
+ a = { "<cmd>lua require('core.telescope').code_actions()<cr>", "Code Action" },
+ d = {
+ "<cmd>Telescope lsp_document_diagnostics<cr>",
+ "Document Diagnostics",
+ },
+ w = {
+ "<cmd>Telescope lsp_workspace_diagnostics<cr>",
+ "Workspace Diagnostics",
+ },
+ f = { "<cmd>lua vim.lsp.buf.formatting()<cr>", "Format" },
+ i = { "<cmd>LspInfo<cr>", "Info" },
+ I = { "<cmd>LspInstallInfo<cr>", "Installer Info" },
+ j = {
+ "<cmd>lua vim.lsp.diagnostic.goto_next({popup_opts = {border = lvim.lsp.popup_border}})<cr>",
+ "Next Diagnostic",
+ },
+ k = {
+ "<cmd>lua vim.lsp.diagnostic.goto_prev({popup_opts = {border = lvim.lsp.popup_border}})<cr>",
+ "Prev Diagnostic",
+ },
+ l = { "<cmd>lua vim.lsp.codelens.run()<cr>", "CodeLens Action" },
+ p = {
+ name = "Peek",
+ d = { "<cmd>lua require('lvim.lsp.peek').Peek('definition')<cr>", "Definition" },
+ t = { "<cmd>lua require('lvim.lsp.peek').Peek('typeDefinition')<cr>", "Type Definition" },
+ i = { "<cmd>lua require('lvim.lsp.peek').Peek('implementation')<cr>", "Implementation" },
+ },
+ q = { "<cmd>lua vim.lsp.diagnostic.set_loclist()<cr>", "Quickfix" },
+ r = { "<cmd>lua vim.lsp.buf.rename()<cr>", "Rename" },
+ s = { "<cmd>Telescope lsp_document_symbols<cr>", "Document Symbols" },
+ S = {
+ "<cmd>Telescope lsp_dynamic_workspace_symbols<cr>",
+ "Workspace Symbols",
+ },
+ },
+ L = {
+ name = "+LunarVim",
+ c = {
+ "<cmd>edit" .. get_config_dir() .. "/config.lua<cr>",
+ "Edit config.lua",
+ },
+ f = {
+ "<cmd>lua require('lvim.core.telescope').find_lunarvim_files()<cr>",
+ "Find LunarVim files",
+ },
+ g = {
+ "<cmd>lua require('lvim.core.telescope').grep_lunarvim_files()<cr>",
+ "Grep LunarVim files",
+ },
+ k = { "<cmd>lua require('lvim.keymappings').print()<cr>", "View LunarVim's default keymappings" },
+ i = {
+ "<cmd>lua require('lvim.core.info').toggle_popup(vim.bo.filetype)<cr>",
+ "Toggle LunarVim Info",
+ },
+ I = {
+ "<cmd>lua require('lvim.core.telescope').view_lunarvim_changelog()<cr>",
+ "View LunarVim's changelog",
+ },
+ l = {
+ name = "+logs",
+ d = {
+ "<cmd>lua require('lvim.core.terminal').toggle_log_view(require('lvim.core.log').get_path())<cr>",
+ "view default log",
+ },
+ D = {
+ "<cmd>lua vim.fn.execute('edit ' .. require('lvim.core.log').get_path())<cr>",
+ "Open the default logfile",
+ },
+ l = { "<cmd>lua require('lvim.core.terminal').toggle_log_view(vim.lsp.get_log_path())<cr>", "view lsp log" },
+ L = { "<cmd>lua vim.fn.execute('edit ' .. vim.lsp.get_log_path())<cr>", "Open the LSP logfile" },
+ n = {
+ "<cmd>lua require('lvim.core.terminal').toggle_log_view(os.getenv('NVIM_LOG_FILE'))<cr>",
+ "view neovim log",
+ },
+ N = { "<cmd>edit $NVIM_LOG_FILE<cr>", "Open the Neovim logfile" },
+ p = {
+ "<cmd>lua require('lvim.core.terminal').toggle_log_view('packer.nvim')<cr>",
+ "view packer log",
+ },
+ P = { "<cmd>exe 'edit '.stdpath('cache').'/packer.nvim.log'<cr>", "Open the Packer logfile" },
+ },
+ r = { "<cmd>lua require('lvim.utils').reload_lv_config()<cr>", "Reload configurations" },
+ u = { "<cmd>LvimUpdate<cr>", "Update LunarVim" },
+ },
+ s = {
+ name = "Search",
+ b = { "<cmd>Telescope git_branches<cr>", "Checkout branch" },
+ c = { "<cmd>Telescope colorscheme<cr>", "Colorscheme" },
+ f = { "<cmd>Telescope find_files<cr>", "Find File" },
+ h = { "<cmd>Telescope help_tags<cr>", "Find Help" },
+ M = { "<cmd>Telescope man_pages<cr>", "Man Pages" },
+ r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
+ R = { "<cmd>Telescope registers<cr>", "Registers" },
+ t = { "<cmd>Telescope live_grep<cr>", "Text" },
+ k = { "<cmd>Telescope keymaps<cr>", "Keymaps" },
+ C = { "<cmd>Telescope commands<cr>", "Commands" },
+ p = {
+ "<cmd>lua require('telescope.builtin.internal').colorscheme({enable_preview = true})<cr>",
+ "Colorscheme with Preview",
+ },
+ },
+ T = {
+ name = "Treesitter",
+ i = { ":TSConfigInfo<cr>", "Info" },
+ },
+ },
+ }
+end
+
+M.setup = function()
+ local which_key = require "which-key"
+
+ which_key.setup(lvim.builtin.which_key.setup)
+
+ local opts = lvim.builtin.which_key.opts
+ local vopts = lvim.builtin.which_key.vopts
+
+ local mappings = lvim.builtin.which_key.mappings
+ local vmappings = lvim.builtin.which_key.vmappings
+
+ which_key.register(mappings, opts)
+ which_key.register(vmappings, vopts)
+
+ if lvim.builtin.which_key.on_config_done then
+ lvim.builtin.which_key.on_config_done(which_key)
+ end
+end
+
+return M
diff --git a/lua/lvim/impatient.lua b/lua/lvim/impatient.lua
new file mode 100644
index 00000000..4fdc0026
--- /dev/null
+++ b/lua/lvim/impatient.lua
@@ -0,0 +1,360 @@
+-- modified version from https://github.com/lewis6991/impatient.nvim
+
+local vim = vim
+local uv = vim.loop
+local impatient_load_start = uv.hrtime()
+local api = vim.api
+local ffi = require "ffi"
+
+local get_option, set_option = api.nvim_get_option, api.nvim_set_option
+local get_runtime_file = api.nvim_get_runtime_file
+
+local impatient_dur
+
+local M = {
+ cache = {},
+ profile = nil,
+ dirty = false,
+ path = nil,
+ log = {},
+}
+
+_G.__luacache = M
+
+--{{{
+local cachepack = {}
+
+-- using double for packing/unpacking numbers has no conversion overhead
+-- 32-bit ARM causes a bus error when casting to double, so use int there
+local number_t = jit.arch ~= "arm" and "double" or "int"
+ffi.cdef("typedef " .. number_t .. " number_t;")
+
+local c_number_t = ffi.typeof "number_t[1]"
+local c_sizeof_number_t = ffi.sizeof "number_t"
+
+local out_buf = {}
+
+function out_buf.write_number(buf, num)
+ buf[#buf + 1] = ffi.string(c_number_t(num), c_sizeof_number_t)
+end
+
+function out_buf.write_string(buf, str)
+ out_buf.write_number(buf, #str)
+ buf[#buf + 1] = str
+end
+
+function out_buf.to_string(buf)
+ return table.concat(buf)
+end
+
+local in_buf = {}
+
+function in_buf.read_number(buf)
+ if buf.size < buf.pos then
+ error "buffer access violation"
+ end
+ local res = ffi.cast("number_t*", buf.ptr + buf.pos)[0]
+ buf.pos = buf.pos + c_sizeof_number_t
+ return res
+end
+
+function in_buf.read_string(buf)
+ local len = in_buf.read_number(buf)
+ local res = ffi.string(buf.ptr + buf.pos, len)
+ buf.pos = buf.pos + len
+
+ return res
+end
+
+function cachepack.pack(cache)
+ local total_keys = vim.tbl_count(cache)
+ local buf = {}
+
+ out_buf.write_number(buf, total_keys)
+ for k, v in pairs(cache) do
+ out_buf.write_string(buf, k)
+ out_buf.write_string(buf, v[1] or "")
+ out_buf.write_number(buf, v[2] or 0)
+ out_buf.write_string(buf, v[3] or "")
+ end
+
+ return out_buf.to_string(buf)
+end
+
+function cachepack.unpack(str, raw_buf_size)
+ if raw_buf_size == 0 or str == nil or (raw_buf_size == nil and #str == 0) then
+ return {}
+ end
+
+ local buf = {
+ ptr = raw_buf_size and str or ffi.new("const char[?]", #str, str),
+ pos = 0,
+ size = raw_buf_size or #str,
+ }
+ local cache = {}
+
+ local total_keys = in_buf.read_number(buf)
+ for _ = 1, total_keys do
+ local k = in_buf.read_string(buf)
+ local v = {
+ in_buf.read_string(buf),
+ in_buf.read_number(buf),
+ in_buf.read_string(buf),
+ }
+ cache[k] = v
+ end
+
+ return cache
+end
+--}}}
+
+local function log(...)
+ M.log[#M.log + 1] = table.concat({ string.format(...) }, " ")
+end
+
+function M.print_log()
+ for _, l in ipairs(M.log) do
+ print(l)
+ end
+end
+
+function M.enable_profile()
+ M.profile = {}
+ M.print_profile = function()
+ M.profile["lvim.impatient"] = {
+ resolve = 0,
+ load = impatient_dur,
+ loader = "standard",
+ }
+ require("lvim.impatient.profile").print_profile(M.profile)
+ end
+ vim.cmd [[command! LuaCacheProfile lua _G.__luacache.print_profile()]]
+end
+
+local function is_cacheable(path)
+ -- Don't cache files in /tmp since they are not likely to persist.
+ -- Note: Appimage versions of Neovim mount $VIMRUNTIME in /tmp in a unique
+ -- directory on each launch.
+ return not vim.startswith(path, "/tmp/")
+end
+
+local function hash(modpath)
+ local stat = uv.fs_stat(modpath)
+ if stat then
+ return stat.mtime.sec
+ end
+end
+
+local function hrtime()
+ if M.profile then
+ return uv.hrtime()
+ end
+end
+
+local function load_package_with_cache(name, loader)
+ local resolve_start = hrtime()
+
+ local basename = name:gsub("%.", "/")
+ local paths = { "lua/" .. basename .. ".lua", "lua/" .. basename .. "/init.lua" }
+
+ for _, path in ipairs(paths) do
+ local modpath = get_runtime_file(path, false)[1]
+ if modpath then
+ local load_start = hrtime()
+ local chunk, err = loadfile(modpath)
+
+ if M.profile then
+ M.profile[name] = {
+ resolve = load_start - resolve_start,
+ load = hrtime() - load_start,
+ loader = loader or "standard",
+ }
+ end
+
+ if chunk == nil then
+ return err
+ end
+
+ if is_cacheable(modpath) then
+ log("Creating cache for module %s", name)
+ M.cache[name] = { modpath, hash(modpath), string.dump(chunk) }
+ M.dirty = true
+ else
+ log("Unable to cache module %s", name)
+ end
+
+ return chunk
+ end
+ end
+ return nil
+end
+
+local reduced_rtp
+
+-- Speed up non-cached loads by reducing the rtp path during requires
+function M.update_reduced_rtp()
+ local luadirs = get_runtime_file("lua/", true)
+
+ for i = 1, #luadirs do
+ luadirs[i] = luadirs[i]:sub(1, -6)
+ end
+
+ reduced_rtp = table.concat(luadirs, ",")
+end
+
+local function load_package_with_cache_reduced_rtp(name)
+ local orig_rtp = get_option "runtimepath"
+ local orig_ei = get_option "eventignore"
+
+ if not reduced_rtp then
+ M.update_reduced_rtp()
+ end
+
+ set_option("eventignore", "all")
+ set_option("rtp", reduced_rtp)
+
+ local found = load_package_with_cache(name, "reduced")
+
+ set_option("rtp", orig_rtp)
+ set_option("eventignore", orig_ei)
+
+ return found
+end
+
+local function load_from_cache(name)
+ local resolve_start = hrtime()
+ if M.cache[name] == nil then
+ log("No cache for module %s", name)
+ return "No cache entry"
+ end
+
+ local modpath, mhash, codes = unpack(M.cache[name])
+
+ if mhash ~= hash(modpath) then
+ log("Stale cache for module %s", name)
+ M.cache[name] = nil
+ M.dirty = true
+ return "Stale cache"
+ end
+
+ local load_start = hrtime()
+ local chunk = loadstring(codes)
+
+ if M.profile then
+ M.profile[name] = {
+ resolve = load_start - resolve_start,
+ load = hrtime() - load_start,
+ loader = "cache",
+ }
+ end
+
+ if not chunk then
+ M.cache[name] = nil
+ M.dirty = true
+ log("Error loading cache for module. Invalidating", name)
+ return "Cache error"
+ end
+
+ return chunk
+end
+
+function M.save_cache()
+ if M.dirty then
+ log("Updating cache file: %s", M.path)
+ local f = io.open(M.path, "w+b")
+ f:write(cachepack.pack(M.cache))
+ f:flush()
+ M.dirty = false
+ end
+end
+
+function M.clear_cache()
+ M.cache = {}
+ os.remove(M.path)
+end
+
+impatient_dur = uv.hrtime() - impatient_load_start
+
+function M.setup(opts)
+ opts = opts or {}
+ M.path = opts.path or vim.fn.stdpath "cache" .. "/lvim_cache"
+
+ if opts.enable_profiling then
+ M.enable_profile()
+ end
+
+ local impatient_setup_start = uv.hrtime()
+ local stat = uv.fs_stat(M.path)
+ if stat then
+ log("Loading cache file %s", M.path)
+ local ok
+ -- Linux/macOS lets us easily mmap the cache file for faster reads without passing to Lua
+ if jit.os == "Linux" or jit.os == "OSX" then
+ local size = stat.size
+
+ local C = ffi.C
+ local O_RDONLY = 0x00
+ local PROT_READ = 0x01
+ local MAP_PRIVATE = 0x02
+
+ ffi.cdef [[
+ int open(const char *pathname, int flags);
+ int close(int fd);
+ void *mmap(void *addr, size_t length, int prot, int flags, int fd, long int offset);
+ int munmap(void *addr, size_t length);
+ ]]
+ local f = C.open(M.path, O_RDONLY)
+
+ local addr = C.mmap(nil, size, PROT_READ, MAP_PRIVATE, f, 0)
+ ok = ffi.cast("intptr_t", addr) ~= -1
+
+ if ok then
+ M.cache = cachepack.unpack(ffi.cast("char *", addr), size)
+ C.munmap(addr, size)
+ end
+
+ C.close(f)
+ else
+ local f = io.open(M.path, "rb")
+ ok, M.cache = pcall(function()
+ return cachepack.unpack(f:read "*a")
+ end)
+ end
+
+ if not ok then
+ log("Corrupted cache file, %s. Invalidating...", M.path)
+ os.remove(M.path)
+ M.cache = {}
+ end
+ M.dirty = not ok
+ end
+
+ local insert = table.insert
+ local package = package
+
+ -- Fix the position of the preloader. This also makes loading modules like 'ffi'
+ -- and 'bit' quicker
+ if package.loaders[1] == vim._load_package then
+ -- Move vim._load_package to the second position
+ local vim_load = table.remove(package.loaders, 1)
+ insert(package.loaders, 2, vim_load)
+ end
+
+ insert(package.loaders, 2, load_from_cache)
+ insert(package.loaders, 3, load_package_with_cache_reduced_rtp)
+ insert(package.loaders, 4, load_package_with_cache)
+
+ vim.cmd [[
+ augroup impatient
+ autocmd VimEnter,VimLeave * lua _G.__luacache.save_cache()
+ autocmd OptionSet runtimepath lua _G.__luacache.update_reduced_rtp(true)
+ augroup END
+
+ command! LuaCacheClear lua _G.__luacache.clear_cache()
+ command! LuaCacheLog lua _G.__luacache.print_log()
+ ]]
+
+ impatient_dur = impatient_dur + (uv.hrtime() - impatient_setup_start)
+end
+
+return M
diff --git a/lua/lvim/impatient/profile.lua b/lua/lvim/impatient/profile.lua
new file mode 100644
index 00000000..0f4f8236
--- /dev/null
+++ b/lua/lvim/impatient/profile.lua
@@ -0,0 +1,145 @@
+local M = {}
+
+local api = vim.api
+
+function M.print_profile(profile)
+ if not profile then
+ print "Error: profiling was not enabled"
+ return
+ end
+
+ local total_resolve = 0
+ local total_load = 0
+ local name_pad = 0
+ local modules = {}
+ local plugins = {}
+
+ for module, p in pairs(profile) do
+ p.resolve = p.resolve / 1000000
+ p.load = p.load / 1000000
+ p.total = p.resolve + p.load
+ p.module = module:gsub("/", ".")
+
+ local plugin = p.module:match "([^.]+)"
+ if plugin then
+ if not plugins[plugin] then
+ plugins[plugin] = {
+ module = plugin,
+ resolve = 0,
+ load = 0,
+ total = 0,
+ }
+ end
+ local r = plugins[plugin]
+
+ r.resolve = r.resolve + p.resolve
+ r.load = r.load + p.load
+ r.total = r.total + p.total
+
+ if not r.loader then
+ r.loader = p.loader
+ elseif r.loader ~= p.loader then
+ r.loader = "mixed"
+ end
+ end
+
+ total_resolve = total_resolve + p.resolve
+ total_load = total_load + p.load
+
+ if #module > name_pad then
+ name_pad = #module
+ end
+
+ modules[#modules + 1] = p
+ end
+
+ table.sort(modules, function(a, b)
+ return a.module > b.module
+ end)
+
+ do
+ local plugins_a = {}
+ for _, v in pairs(plugins) do
+ plugins_a[#plugins_a + 1] = v
+ end
+ plugins = plugins_a
+ end
+
+ table.sort(plugins, function(a, b)
+ return a.total > b.total
+ end)
+
+ local lines = {}
+ local function add(...)
+ lines[#lines + 1] = string.format(...)
+ end
+
+ local l = string.rep("─", name_pad + 1)
+
+ add(
+ "%s┬───────────┬────────────┬────────────┬────────────┐",
+ l
+ )
+ add("%-" .. name_pad .. "s │ Loader │ Resolve │ Load │ Total │", "")
+ add(
+ "%s┼───────────┼────────────┼────────────┼────────────┤",
+ l
+ )
+ add(
+ "%-" .. name_pad .. "s │ │ %8.4fms │ %8.4fms │ %8.4fms │",
+ "Total",
+ total_resolve,
+ total_load,
+ total_resolve + total_load
+ )
+ add(
+ "%s┴───────────┴────────────┴────────────┴────────────┤",
+ l
+ )
+ add("%-" .. name_pad .. "s │", "By Plugin")
+ add(
+ "%s┬───────────┬────────────┬────────────┬────────────┤",
+ l
+ )
+ for _, p in ipairs(plugins) do
+ add(
+ "%-" .. name_pad .. "s │ %9s │ %8.4fms │ %8.4fms │ %8.4fms │",
+ p.module,
+ p.loader,
+ p.resolve,
+ p.load,
+ p.total
+ )
+ end
+ add(
+ "%s┴───────────┴────────────┴────────────┴────────────┤",
+ l
+ )
+ add("%-" .. name_pad .. "s │", "By Module")
+ add(
+ "%s┬───────────┬────────────┬────────────┬────────────┤",
+ l
+ )
+ for _, p in pairs(modules) do
+ add(
+ "%-" .. name_pad .. "s │ %9s │ %8.4fms │ %8.4fms │ %8.4fms │",
+ p.module,
+ p.loader,
+ p.resolve,
+ p.load,
+ p.total
+ )
+ end
+ add(
+ "%s┴───────────┴────────────┴────────────┴────────────┘",
+ l
+ )
+
+ local bufnr = api.nvim_create_buf(false, false)
+ api.nvim_buf_set_lines(bufnr, 0, 0, false, lines)
+ api.nvim_buf_set_option(bufnr, "buftype", "nofile")
+ api.nvim_buf_set_name(bufnr, "Impatient Profile Report")
+ api.nvim_set_current_buf(bufnr)
+end
+
+return M
diff --git a/lua/lvim/interface/popup.lua b/lua/lvim/interface/popup.lua
new file mode 100644
index 00000000..b628125c
--- /dev/null
+++ b/lua/lvim/interface/popup.lua
@@ -0,0 +1,62 @@
+local Popup = {}
+
+--- Create a new floating window
+-- @param config The configuration passed to vim.api.nvim_open_win
+-- @param win_opts The options registered with vim.api.nvim_win_set_option
+-- @param buf_opts The options registered with vim.api.nvim_buf_set_option
+-- @return A new popup
+function Popup:new(opts)
+ opts = opts or {}
+ opts.layout = opts.layout or {}
+ opts.win_opts = opts.win_opts or {}
+ opts.buf_opts = opts.buf_opts or {}
+
+ Popup.__index = Popup
+
+ local editor_layout = {
+ height = vim.o.lines - vim.o.cmdheight - 2, -- Add margin for status and buffer line
+ width = vim.o.columns,
+ }
+ local popup_layout = {
+ relative = "editor",
+ height = math.floor(editor_layout.height * 0.9),
+ width = math.floor(editor_layout.width * 0.8),
+ style = "minimal",
+ border = "rounded",
+ }
+ popup_layout.row = math.floor((editor_layout.height - popup_layout.height) / 2)
+ popup_layout.col = math.floor((editor_layout.width - popup_layout.width) / 2)
+
+ local obj = {
+ buffer = vim.api.nvim_create_buf(false, true),
+ layout = vim.tbl_deep_extend("force", popup_layout, opts.layout),
+ win_opts = opts.win_opts,
+ buf_opts = opts.buf_opts,
+ }
+
+ setmetatable(obj, Popup)
+
+ return obj
+end
+
+--- Display the popup with the provided content
+-- @param content_provider A function accepting the popup's layout and returning the content to display
+function Popup:display(content_provider)
+ self.win_id = vim.api.nvim_open_win(self.buffer, true, self.layout)
+ vim.lsp.util.close_preview_autocmd({ "BufHidden", "BufLeave" }, self.win_id)
+
+ local lines = content_provider(self.layout)
+ vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, lines)
+
+ -- window options
+ for key, value in pairs(self.win_opts) do
+ vim.api.nvim_win_set_option(self.win_id, key, value)
+ end
+
+ -- buffer options
+ for key, value in pairs(self.buf_opts) do
+ vim.api.nvim_buf_set_option(self.buffer, key, value)
+ end
+end
+
+return Popup
diff --git a/lua/lvim/interface/text.lua b/lua/lvim/interface/text.lua
new file mode 100644
index 00000000..6bf280e8
--- /dev/null
+++ b/lua/lvim/interface/text.lua
@@ -0,0 +1,95 @@
+local M = {}
+
+local function max_len_line(lines)
+ local max_len = 0
+
+ for _, line in ipairs(lines) do
+ local line_len = line:len()
+ if line_len > max_len then
+ max_len = line_len
+ end
+ end
+
+ return max_len
+end
+
+--- Left align lines relatively to the parent container
+-- @param container The container where lines will be displayed
+-- @param lines The text to align
+-- @param alignment The alignment value, range: [0-1]
+function M.align_left(container, lines, alignment)
+ local max_len = max_len_line(lines)
+ local indent_amount = math.ceil(math.max(container.width - max_len, 0) * alignment)
+ return M.shift_right(lines, indent_amount)
+end
+
+--- Center align lines relatively to the parent container
+-- @param container The container where lines will be displayed
+-- @param lines The text to align
+-- @param alignment The alignment value, range: [0-1]
+function M.align_center(container, lines, alignment)
+ local output = {}
+ local max_len = max_len_line(lines)
+
+ for _, line in ipairs(lines) do
+ local padding = string.rep(" ", (math.max(container.width, max_len) - line:len()) * alignment)
+ table.insert(output, padding .. line)
+ end
+
+ return output
+end
+
+--- Shift lines by a given amount
+-- @params lines The lines the shift
+-- @param amount The amount of spaces to add
+function M.shift_right(lines, amount)
+ local output = {}
+ local padding = string.rep(" ", amount)
+
+ for _, line in ipairs(lines) do
+ table.insert(output, padding .. line)
+ end
+
+ return output
+end
+
+--- Pretty format tables
+-- @param entries The table to format
+-- @param col_count The number of column to span the table on
+-- @param col_sep The separator between each colummn, default: " "
+function M.format_table(entries, col_count, col_sep)
+ col_sep = col_sep or " "
+
+ local col_rows = math.ceil(vim.tbl_count(entries) / col_count)
+ local cols = {}
+ local count = 0
+
+ for i, entry in ipairs(entries) do
+ if ((i - 1) % col_rows) == 0 then
+ table.insert(cols, {})
+ count = count + 1
+ end
+ table.insert(cols[count], entry)
+ end
+
+ local col_max_len = {}
+ for _, col in ipairs(cols) do
+ table.insert(col_max_len, max_len_line(col))
+ end
+
+ local output = {}
+ for i, col in ipairs(cols) do
+ for j, entry in ipairs(col) do
+ if not output[j] then
+ output[j] = entry
+ else
+ local padding = string.rep(" ", col_max_len[i - 1] - cols[i - 1][j]:len())
+ output[j] = output[j] .. padding .. col_sep .. entry
+ end
+ end
+ end
+
+ return output
+end
+
+return M
diff --git a/lua/lvim/keymappings.lua b/lua/lvim/keymappings.lua
new file mode 100644
index 00000000..68a49393
--- /dev/null
+++ b/lua/lvim/keymappings.lua
@@ -0,0 +1,180 @@
+local M = {}
+local Log = require "lvim.core.log"
+
+local generic_opts_any = { noremap = true, silent = true }
+
+local generic_opts = {
+ insert_mode = generic_opts_any,
+ normal_mode = generic_opts_any,
+ visual_mode = generic_opts_any,
+ visual_block_mode = generic_opts_any,
+ command_mode = generic_opts_any,
+ term_mode = { silent = true },
+}
+
+local mode_adapters = {
+ insert_mode = "i",
+ normal_mode = "n",
+ term_mode = "t",
+ visual_mode = "v",
+ visual_block_mode = "x",
+ command_mode = "c",
+}
+
+-- Append key mappings to lunarvim's defaults for a given mode
+-- @param keymaps The table of key mappings containing a list per mode (normal_mode, insert_mode, ..)
+function M.append_to_defaults(keymaps)
+ for mode, mappings in pairs(keymaps) do
+ for k, v in ipairs(mappings) do
+ lvim.keys[mode][k] = v
+ end
+ end
+end
+
+-- Set key mappings individually
+-- @param mode The keymap mode, can be one of the keys of mode_adapters
+-- @param key The key of keymap
+-- @param val Can be form as a mapping or tuple of mapping and user defined opt
+function M.set_keymaps(mode, key, val)
+ local opt = generic_opts[mode] and generic_opts[mode] or generic_opts_any
+ if type(val) == "table" then
+ opt = val[2]
+ val = val[1]
+ end
+ vim.api.nvim_set_keymap(mode, key, val, opt)
+end
+
+-- Load key mappings for a given mode
+-- @param mode The keymap mode, can be one of the keys of mode_adapters
+-- @param keymaps The list of key mappings
+function M.load_mode(mode, keymaps)
+ mode = mode_adapters[mode] and mode_adapters[mode] or mode
+ for k, v in pairs(keymaps) do
+ M.set_keymaps(mode, k, v)
+ end
+end
+
+-- Load key mappings for all provided modes
+-- @param keymaps A list of key mappings for each mode
+function M.load(keymaps)
+ for mode, mapping in pairs(keymaps) do
+ M.load_mode(mode, mapping)
+ end
+end
+
+function M.config()
+ lvim.keys = {
+ ---@usage change or add keymappings for insert mode
+ insert_mode = {
+ -- 'jk' for quitting insert mode
+ ["jk"] = "<ESC>",
+ -- 'kj' for quitting insert mode
+ ["kj"] = "<ESC>",
+ -- 'jj' for quitting insert mode
+ ["jj"] = "<ESC>",
+ -- Move current line / block with Alt-j/k ala vscode.
+ ["<A-j>"] = "<Esc>:m .+1<CR>==gi",
+ -- Move current line / block with Alt-j/k ala vscode.
+ ["<A-k>"] = "<Esc>:m .-2<CR>==gi",
+ -- navigation
+ ["<A-Up>"] = "<C-\\><C-N><C-w>k",
+ ["<A-Down>"] = "<C-\\><C-N><C-w>j",
+ ["<A-Left>"] = "<C-\\><C-N><C-w>h",
+ ["<A-Right>"] = "<C-\\><C-N><C-w>l",
+ -- navigate tab completion with <c-j> and <c-k>
+ -- runs conditionally
+ ["<C-j>"] = { 'pumvisible() ? "\\<down>" : "\\<C-j>"', { expr = true, noremap = true } },
+ ["<C-k>"] = { 'pumvisible() ? "\\<up>" : "\\<C-k>"', { expr = true, noremap = true } },
+ },
+
+ ---@usage change or add keymappings for normal mode
+ normal_mode = {
+ -- Better window movement
+ ["<C-h>"] = "<C-w>h",
+ ["<C-j>"] = "<C-w>j",
+ ["<C-k>"] = "<C-w>k",
+ ["<C-l>"] = "<C-w>l",
+
+ -- Resize with arrows
+ ["<C-Up>"] = ":resize -2<CR>",
+ ["<C-Down>"] = ":resize +2<CR>",
+ ["<C-Left>"] = ":vertical resize -2<CR>",
+ ["<C-Right>"] = ":vertical resize +2<CR>",
+
+ -- Tab switch buffer
+ ["<S-l>"] = ":BufferNext<CR>",
+ ["<S-h>"] = ":BufferPrevious<CR>",
+
+ -- Move current line / block with Alt-j/k a la vscode.
+ ["<A-j>"] = ":m .+1<CR>==",
+ ["<A-k>"] = ":m .-2<CR>==",
+
+ -- QuickFix
+ ["]q"] = ":cnext<CR>",
+ ["[q"] = ":cprev<CR>",
+ ["<C-q>"] = ":call QuickFixToggle()<CR>",
+ },
+
+ ---@usage change or add keymappings for terminal mode
+ term_mode = {
+ -- Terminal window navigation
+ ["<C-h>"] = "<C-\\><C-N><C-w>h",
+ ["<C-j>"] = "<C-\\><C-N><C-w>j",
+ ["<C-k>"] = "<C-\\><C-N><C-w>k",
+ ["<C-l>"] = "<C-\\><C-N><C-w>l",
+ },
+
+ ---@usage change or add keymappings for visual mode
+ visual_mode = {
+ -- Better indenting
+ ["<"] = "<gv",
+ [">"] = ">gv",
+
+ -- ["p"] = '"0p',
+ -- ["P"] = '"0P',
+ },
+
+ ---@usage change or add keymappings for visual block mode
+ visual_block_mode = {
+ -- Move selected line / block of text in visual mode
+ ["K"] = ":move '<-2<CR>gv-gv",
+ ["J"] = ":move '>+1<CR>gv-gv",
+
+ -- Move current line / block with Alt-j/k ala vscode.
+ ["<A-j>"] = ":m '>+1<CR>gv-gv",
+ ["<A-k>"] = ":m '<-2<CR>gv-gv",
+ },
+
+ ---@usage change or add keymappings for command mode
+ command_mode = {
+ -- navigate tab completion with <c-j> and <c-k>
+ -- runs conditionally
+ ["<C-j>"] = { 'pumvisible() ? "\\<C-n>" : "\\<C-j>"', { expr = true, noremap = true } },
+ ["<C-k>"] = { 'pumvisible() ? "\\<C-p>" : "\\<C-k>"', { expr = true, noremap = true } },
+ },
+ }
+
+ if vim.fn.has "mac" == 1 then
+ lvim.keys.normal_mode["<A-Up>"] = lvim.keys.normal_mode["<C-Up>"]
+ lvim.keys.normal_mode["<A-Down>"] = lvim.keys.normal_mode["<C-Down>"]
+ lvim.keys.normal_mode["<A-Left>"] = lvim.keys.normal_mode["<C-Left>"]
+ lvim.keys.normal_mode["<A-Right>"] = lvim.keys.normal_mode["<C-Right>"]
+ Log:debug "Activated mac keymappings"
+ end
+end
+
+function M.print(mode)
+ print "List of LunarVim's default keymappings (not including which-key)"
+ if mode then
+ print(vim.inspect(lvim.keys[mode]))
+ else
+ print(vim.inspect(lvim.keys))
+ end
+end
+
+function M.setup()
+ vim.g.mapleader = (lvim.leader == "space" and " ") or lvim.leader
+ M.load(lvim.keys)
+end
+
+return M
diff --git a/lua/lvim/lsp/config.lua b/lua/lvim/lsp/config.lua
new file mode 100644
index 00000000..30336cc2
--- /dev/null
+++ b/lua/lvim/lsp/config.lua
@@ -0,0 +1,45 @@
+return {
+ templates_dir = join_paths(get_runtime_dir(), "site", "after", "ftplugin"),
+ diagnostics = {
+ signs = {
+ active = true,
+ values = {
+ { name = "LspDiagnosticsSignError", text = "" },
+ { name = "LspDiagnosticsSignWarning", text = "" },
+ { name = "LspDiagnosticsSignHint", text = "" },
+ { name = "LspDiagnosticsSignInformation", text = "" },
+ },
+ },
+ virtual_text = true,
+ update_in_insert = false,
+ underline = true,
+ severity_sort = true,
+ },
+ override = {},
+ document_highlight = true,
+ code_lens_refresh = true,
+ popup_border = "single",
+ on_attach_callback = nil,
+ on_init_callback = nil,
+ automatic_servers_installation = true,
+ buffer_mappings = {
+ normal_mode = {
+ ["K"] = { "<cmd>lua vim.lsp.buf.hover()<CR>", "Show hover" },
+ ["gd"] = { "<cmd>lua vim.lsp.buf.definition()<CR>", "Goto Definition" },
+ ["gD"] = { "<cmd>lua vim.lsp.buf.declaration()<CR>", "Goto declaration" },
+ ["gr"] = { "<cmd>lua vim.lsp.buf.references()<CR>", "Goto references" },
+ ["gI"] = { "<cmd>lua vim.lsp.buf.implementation()<CR>", "Goto Implementation" },
+ ["gs"] = { "<cmd>lua vim.lsp.buf.signature_help()<CR>", "show signature help" },
+ ["gp"] = { "<cmd>lua require'lvim.lsp.peek'.Peek('definition')<CR>", "Peek definition" },
+ ["gl"] = {
+ "<cmd>lua require'lvim.lsp.handlers'.show_line_diagnostics()<CR>",
+ "Show line diagnostics",
+ },
+ },
+ insert_mode = {},
+ visual_mode = {},
+ },
+ null_ls = {
+ setup = {},
+ },
+}
diff --git a/lua/lvim/lsp/handlers.lua b/lua/lvim/lsp/handlers.lua
new file mode 100644
index 00000000..ffb7564a
--- /dev/null
+++ b/lua/lvim/lsp/handlers.lua
@@ -0,0 +1,169 @@
+-- Set Default Prefix.
+-- Note: You can set a prefix per lsp server in the lv-globals.lua file
+local M = {}
+
+function M.setup()
+ local config = { -- your config
+ virtual_text = lvim.lsp.diagnostics.virtual_text,
+ signs = lvim.lsp.diagnostics.signs,
+ underline = lvim.lsp.diagnostics.underline,
+ update_in_insert = lvim.lsp.diagnostics.update_in_insert,
+ severity_sort = lvim.lsp.diagnostics.severity_sort,
+ }
+ if vim.fn.has "nvim-0.5.1" > 0 then
+ vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, result, ctx, _)
+ local uri = result.uri
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not bufnr then
+ return
+ end
+
+ local diagnostics = result.diagnostics
+ local ok, vim_diag = pcall(require, "vim.diagnostic")
+ if ok then
+ -- FIX: why can't we just use vim.diagnostic.get(buf_id)?
+ config.signs = true
+ for i, diagnostic in ipairs(diagnostics) do
+ local rng = diagnostic.range
+ diagnostics[i].lnum = rng["start"].line
+ diagnostics[i].end_lnum = rng["end"].line
+ diagnostics[i].col = rng["start"].character
+ diagnostics[i].end_col = rng["end"].character
+ end
+ local namespace = vim.lsp.diagnostic.get_namespace(ctx.client_id)
+
+ vim_diag.set(namespace, bufnr, diagnostics, config)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
+ local sign_names = {
+ "DiagnosticSignError",
+ "DiagnosticSignWarn",
+ "DiagnosticSignInfo",
+ "DiagnosticSignHint",
+ }
+ for i, sign in ipairs(lvim.lsp.diagnostics.signs.values) do
+ vim.fn.sign_define(sign_names[i], { texthl = sign_names[i], text = sign.text, numhl = "" })
+ end
+ vim_diag.show(namespace, bufnr, diagnostics, config)
+ else
+ vim.lsp.diagnostic.save(diagnostics, bufnr, ctx.client_id)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ vim.lsp.diagnostic.display(diagnostics, bufnr, ctx.client_id, config)
+ end
+ end
+ else
+ vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, _, params, client_id, _)
+ local uri = params.uri
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not bufnr then
+ return
+ end
+
+ local diagnostics = params.diagnostics
+ vim.lsp.diagnostic.save(diagnostics, bufnr, client_id)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ vim.lsp.diagnostic.display(diagnostics, bufnr, client_id, config)
+ end
+ end
+
+ vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, {
+ border = lvim.lsp.popup_border,
+ })
+
+ vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, {
+ border = lvim.lsp.popup_border,
+ })
+end
+
+local function split_by_chunk(text, chunkSize)
+ local s = {}
+ for i = 1, #text, chunkSize do
+ s[#s + 1] = text:sub(i, i + chunkSize - 1)
+ end
+ return s
+end
+
+function M.show_line_diagnostics()
+ -- TODO: replace all this with vim.diagnostic.show_position_diagnostics()
+ local diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
+ local severity_highlight = {
+ "LspDiagnosticsFloatingError",
+ "LspDiagnosticsFloatingWarning",
+ "LspDiagnosticsFloatingInformation",
+ "LspDiagnosticsFloatingHint",
+ }
+ local ok, vim_diag = pcall(require, "vim.diagnostic")
+ if ok then
+ local buf_id = vim.api.nvim_win_get_buf(0)
+ local win_id = vim.api.nvim_get_current_win()
+ local cursor_position = vim.api.nvim_win_get_cursor(win_id)
+ severity_highlight = {
+ "DiagnosticFloatingError",
+ "DiagnosticFloatingWarn",
+ "DiagnosticFloatingInfo",
+ "DiagnosticFloatingHint",
+ }
+ diagnostics = vim_diag.get(buf_id, { lnum = cursor_position[1] - 1 })
+ end
+ local lines = {}
+ local max_width = vim.fn.winwidth(0) - 5
+ local height = #diagnostics
+ local width = 0
+ local opts = {}
+ local close_events = { "CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre" }
+ if height == 0 then
+ return
+ end
+ local bufnr = vim.api.nvim_create_buf(false, true)
+ local diag_message
+ table.sort(diagnostics, function(a, b)
+ return a.severity < b.severity
+ end)
+ for i, diagnostic in ipairs(diagnostics) do
+ local source = diagnostic.source
+ diag_message = diagnostic.message:gsub("[\n\r]", " ")
+ if source then
+ if string.find(source, "/") then
+ source = string.sub(diagnostic.source, string.find(diagnostic.source, "([%w-_]+)$"))
+ end
+ diag_message = string.format("%d. %s: %s", i, source, diag_message)
+ else
+ diag_message = string.format("%d. %s", i, diag_message)
+ end
+ if diagnostic.code then
+ diag_message = string.format("%s [%s]", diag_message, diagnostic.code)
+ end
+ local msgs = split_by_chunk(diag_message, max_width)
+ for _, diag in ipairs(msgs) do
+ table.insert(lines, { message = diag, severity = diagnostic.severity })
+ width = math.max(diag:len(), width)
+ end
+ end
+ height = #lines
+ opts = vim.lsp.util.make_floating_popup_options(width, height, opts)
+ opts["style"] = "minimal"
+ opts["border"] = "rounded"
+ opts["focusable"] = true
+
+ vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
+ local winnr = vim.api.nvim_open_win(bufnr, false, opts)
+ vim.api.nvim_win_set_option(winnr, "winblend", 0)
+ vim.api.nvim_buf_set_var(bufnr, "lsp_floating_window", winnr)
+ for i, diag in ipairs(lines) do
+ vim.api.nvim_buf_set_lines(bufnr, i - 1, i - 1, 0, { diag.message })
+ vim.api.nvim_buf_add_highlight(bufnr, -1, severity_highlight[diag.severity], i - 1, 0, diag.message:len())
+ end
+
+ vim.api.nvim_command(
+ "autocmd QuitPre <buffer> ++nested ++once lua pcall(vim.api.nvim_win_close, " .. winnr .. ", true)"
+ )
+ vim.lsp.util.close_preview_autocmd(close_events, winnr)
+end
+
+return M
diff --git a/lua/lvim/lsp/init.lua b/lua/lvim/lsp/init.lua
new file mode 100644
index 00000000..c8d583a9
--- /dev/null
+++ b/lua/lvim/lsp/init.lua
@@ -0,0 +1,165 @@
+local M = {}
+local Log = require "lvim.core.log"
+local utils = require "lvim.utils"
+
+local function lsp_highlight_document(client)
+ if lvim.lsp.document_highlight == false then
+ return -- we don't need further
+ end
+ -- Set autocommands conditional on server_capabilities
+ if client.resolved_capabilities.document_highlight then
+ vim.api.nvim_exec(
+ [[
+ augroup lsp_document_highlight
+ autocmd! * <buffer>
+ autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
+ autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
+ augroup END
+ ]],
+ false
+ )
+ end
+end
+
+local function lsp_code_lens_refresh(client)
+ if lvim.lsp.code_lens_refresh == false then
+ return
+ end
+
+ if client.resolved_capabilities.code_lens then
+ vim.api.nvim_exec(
+ [[
+ augroup lsp_code_lens_refresh
+ autocmd! * <buffer>
+ autocmd InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+ autocmd InsertLeave <buffer> lua vim.lsp.codelens.display()
+ augroup END
+ ]],
+ false
+ )
+ end
+end
+
+local function add_lsp_buffer_keybindings(bufnr)
+ local mappings = {
+ normal_mode = "n",
+ insert_mode = "i",
+ visual_mode = "v",
+ }
+
+ if lvim.builtin.which_key.active then
+ -- Remap using which_key
+ local status_ok, wk = pcall(require, "which-key")
+ if not status_ok then
+ return
+ end
+ for mode_name, mode_char in pairs(mappings) do
+ wk.register(lvim.lsp.buffer_mappings[mode_name], { mode = mode_char, buffer = bufnr })
+ end
+ else
+ -- Remap using nvim api
+ for mode_name, mode_char in pairs(mappings) do
+ for key, remap in pairs(lvim.lsp.buffer_mappings[mode_name]) do
+ vim.api.nvim_buf_set_keymap(bufnr, mode_char, key, remap[1], { noremap = true, silent = true })
+ end
+ end
+ end
+end
+
+function M.common_capabilities()
+ local capabilities = vim.lsp.protocol.make_client_capabilities()
+ capabilities.textDocument.completion.completionItem.snippetSupport = true
+ capabilities.textDocument.completion.completionItem.resolveSupport = {
+ properties = {
+ "documentation",
+ "detail",
+ "additionalTextEdits",
+ },
+ }
+
+ local status_ok, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp")
+ if status_ok then
+ capabilities = cmp_nvim_lsp.update_capabilities(capabilities)
+ end
+
+ return capabilities
+end
+
+local function select_default_formater(client)
+ local client_formatting = client.resolved_capabilities.document_formatting
+ or client.resolved_capabilities.document_range_formatting
+ if client.name == "null-ls" or not client_formatting then
+ return
+ end
+ Log:debug("Checking for formatter overriding for " .. client.name)
+ local client_filetypes = client.config.filetypes or {}
+ for _, filetype in ipairs(client_filetypes) do
+ if lvim.lang[filetype] and #vim.tbl_keys(lvim.lang[filetype].formatters) > 0 then
+ Log:debug("Formatter overriding detected. Disabling formatting capabilities for " .. client.name)
+ client.resolved_capabilities.document_formatting = false
+ client.resolved_capabilities.document_range_formatting = false
+ end
+ end
+end
+
+function M.common_on_init(client, bufnr)
+ if lvim.lsp.on_init_callback then
+ lvim.lsp.on_init_callback(client, bufnr)
+ Log:debug "Called lsp.on_init_callback"
+ return
+ end
+ select_default_formater(client)
+end
+
+function M.common_on_attach(client, bufnr)
+ if lvim.lsp.on_attach_callback then
+ lvim.lsp.on_attach_callback(client, bufnr)
+ Log:debug "Called lsp.on_attach_callback"
+ end
+ lsp_highlight_document(client)
+ lsp_code_lens_refresh(client)
+ add_lsp_buffer_keybindings(bufnr)
+end
+
+local function bootstrap_nlsp(opts)
+ opts = opts or {}
+ local lsp_settings_status_ok, lsp_settings = pcall(require, "nlspsettings")
+ if lsp_settings_status_ok then
+ lsp_settings.setup(opts)
+ end
+end
+
+function M.get_common_opts()
+ return {
+ on_attach = M.common_on_attach,
+ on_init = M.common_on_init,
+ capabilities = M.common_capabilities(),
+ }
+end
+
+function M.setup()
+ Log:debug "Setting up LSP support"
+
+ local lsp_status_ok, _ = pcall(require, "lspconfig")
+ if not lsp_status_ok then
+ return
+ end
+
+ for _, sign in ipairs(lvim.lsp.diagnostics.signs.values) do
+ vim.fn.sign_define(sign.name, { texthl = sign.name, text = sign.text, numhl = sign.name })
+ end
+
+ require("lvim.lsp.handlers").setup()
+
+ if not utils.is_directory(lvim.lsp.templates_dir) then
+ require("lvim.lsp.templates").generate_templates()
+ end
+
+ bootstrap_nlsp { config_home = utils.join_paths(get_config_dir(), "lsp-settings") }
+
+ require("lvim.lsp.null-ls").setup()
+
+ require("lvim.utils").toggle_autoformat()
+end
+
+return M
diff --git a/lua/lvim/lsp/manager.lua b/lua/lvim/lsp/manager.lua
new file mode 100644
index 00000000..678a08af
--- /dev/null
+++ b/lua/lvim/lsp/manager.lua
@@ -0,0 +1,86 @@
+local M = {}
+
+local Log = require "lvim.core.log"
+local lsp_utils = require "lvim.lsp.utils"
+
+function M.init_defaults(languages)
+ for _, entry in ipairs(languages) do
+ if not lvim.lang[entry] then
+ lvim.lang[entry] = {
+ formatters = {},
+ linters = {},
+ lsp = {},
+ }
+ end
+ end
+end
+
+local function is_overridden(server)
+ local overrides = lvim.lsp.override
+ if type(overrides) == "table" then
+ if vim.tbl_contains(overrides, server) then
+ return true
+ end
+ end
+end
+
+---Resolve the configuration for a server based on both common and user configuration
+---@param name string
+---@param user_config table [optional]
+---@return table
+local function resolve_config(name, user_config)
+ local config = {
+ on_attach = require("lvim.lsp").common_on_attach,
+ on_init = require("lvim.lsp").common_on_init,
+ capabilities = require("lvim.lsp").common_capabilities(),
+ }
+
+ local status_ok, custom_config = pcall(require, "lvim.lsp/providers/" .. name)
+ if status_ok then
+ Log:debug("Using custom configuration for requested server: " .. name)
+ config = vim.tbl_deep_extend("force", config, custom_config)
+ end
+
+ if user_config then
+ config = vim.tbl_deep_extend("force", config, user_config)
+ end
+
+ return config
+end
+
+---Setup a language server by providing a name
+---@param server_name string name of the language server
+---@param user_config table [optional] when available it will take predence over any default configurations
+function M.setup(server_name, user_config)
+ vim.validate { name = { server_name, "string" } }
+
+ if lsp_utils.is_client_active(server_name) or is_overridden(server_name) then
+ return
+ end
+
+ local config = resolve_config(server_name, user_config)
+ local server_available, requested_server = require("nvim-lsp-installer.servers").get_server(server_name)
+
+ local function ensure_installed(server)
+ if server:is_installed() then
+ return true
+ end
+ if not lvim.lsp.automatic_servers_installation then
+ Log:debug(server.name .. " is not managed by the automatic installer")
+ return false
+ end
+ Log:debug(string.format("Installing [%s]", server.name))
+ server:install()
+ vim.schedule(function()
+ vim.cmd [[LspStart]]
+ end)
+ end
+
+ if server_available and ensure_installed(requested_server) then
+ requested_server:setup(config)
+ else
+ require("lspconfig")[server_name].setup(config)
+ end
+end
+
+return M
diff --git a/lua/lvim/lsp/null-ls/formatters.lua b/lua/lvim/lsp/null-ls/formatters.lua
new file mode 100644
index 00000000..663e2586
--- /dev/null
+++ b/lua/lvim/lsp/null-ls/formatters.lua
@@ -0,0 +1,66 @@
+local M = {}
+
+local null_ls = require "null-ls"
+local services = require "lvim.lsp.null-ls.services"
+local Log = require "lvim.core.log"
+
+function M.list_supported_names(filetype)
+ local null_ls_methods = require "null-ls.methods"
+ local formatter_method = null_ls_methods.internal["FORMATTING"]
+ local registered_providers = services.list_registered_providers_names(filetype)
+ return registered_providers[formatter_method] or {}
+end
+
+function M.list_available(filetype)
+ local formatters = {}
+ local tbl = require "lvim.utils.table"
+ for _, provider in pairs(null_ls.builtins.formatting) do
+ if tbl.contains(provider.filetypes or {}, function(ft)
+ return ft == "*" or ft == filetype
+ end) then
+ table.insert(formatters, provider.name)
+ end
+ end
+
+ return formatters
+end
+
+function M.list_configured(formatter_configs)
+ local formatters, errors = {}, {}
+
+ for _, fmt_config in ipairs(formatter_configs) do
+ local formatter_name = fmt_config.exe:gsub("-", "_")
+ local formatter = null_ls.builtins.formatting[formatter_name]
+
+ if not formatter then
+ Log:error("Not a valid formatter: " .. fmt_config.exe)
+ errors[fmt_config.exe] = {} -- Add data here when necessary
+ else
+ local formatter_cmd = services.find_command(formatter._opts.command)
+ if not formatter_cmd then
+ Log:warn("Not found: " .. formatter._opts.command)
+ errors[fmt_config.exe] = {} -- Add data here when necessary
+ else
+ Log:debug("Using formatter: " .. formatter_cmd)
+ formatters[fmt_config.exe] = formatter.with {
+ command = formatter_cmd,
+ extra_args = fmt_config.args,
+ filetypes = fmt_config.filetypes,
+ }
+ end
+ end
+ end
+
+ return { supported = formatters, unsupported = errors }
+end
+
+function M.setup(formatter_configs)
+ if vim.tbl_isempty(formatter_configs) then
+ return
+ end
+
+ local formatters_by_ft = M.list_configured(formatter_configs)
+ null_ls.register { sources = formatters_by_ft.supported }
+end
+
+return M
diff --git a/lua/lvim/lsp/null-ls/init.lua b/lua/lvim/lsp/null-ls/init.lua
new file mode 100644
index 00000000..f2d3216d
--- /dev/null
+++ b/lua/lvim/lsp/null-ls/init.lua
@@ -0,0 +1,32 @@
+local M = {}
+
+local Log = require "lvim.core.log"
+local formatters = require "lvim.lsp.null-ls.formatters"
+local linters = require "lvim.lsp.null-ls.linters"
+
+function M:setup()
+ local status_ok, null_ls = pcall(require, "null-ls")
+ if not status_ok then
+ Log:error "Missing null-ls dependency"
+ return
+ end
+
+ null_ls.config()
+ require("lspconfig")["null-ls"].setup(lvim.lsp.null_ls.setup)
+ for filetype, config in pairs(lvim.lang) do
+ if not vim.tbl_isempty(config.formatters) then
+ vim.tbl_map(function(c)
+ c.filetypes = { filetype }
+ end, config.formatters)
+ formatters.setup(config.formatters)
+ end
+ if not vim.tbl_isempty(config.linters) then
+ vim.tbl_map(function(c)
+ c.filetypes = { filetype }
+ end, config.formatters)
+ linters.setup(config.linters)
+ end
+ end
+end
+
+return M
diff --git a/lua/lvim/lsp/null-ls/linters.lua b/lua/lvim/lsp/null-ls/linters.lua
new file mode 100644
index 00000000..9ea2d55b
--- /dev/null
+++ b/lua/lvim/lsp/null-ls/linters.lua
@@ -0,0 +1,66 @@
+local M = {}
+
+local null_ls = require "null-ls"
+local services = require "lvim.lsp.null-ls.services"
+local Log = require "lvim.core.log"
+
+function M.list_supported_names(filetype)
+ local null_ls_methods = require "null-ls.methods"
+ local linter_method = null_ls_methods.internal["DIAGNOSTICS"]
+ local registered_providers = services.list_registered_providers_names(filetype)
+ return registered_providers[linter_method] or {}
+end
+
+function M.list_available(filetype)
+ local linters = {}
+ local tbl = require "lvim.utils.table"
+ for _, provider in pairs(null_ls.builtins.diagnostics) do
+ if tbl.contains(provider.filetypes or {}, function(ft)
+ return ft == "*" or ft == filetype
+ end) then
+ table.insert(linters, provider.name)
+ end
+ end
+
+ return linters
+end
+
+function M.list_configured(linter_configs)
+ local linters, errors = {}, {}
+
+ for _, lnt_config in pairs(linter_configs) do
+ local linter_name = lnt_config.exe:gsub("-", "_")
+ local linter = null_ls.builtins.diagnostics[linter_name]
+
+ if not linter then
+ Log:error("Not a valid linter: " .. lnt_config.exe)
+ errors[lnt_config.exe] = {} -- Add data here when necessary
+ else
+ local linter_cmd = services.find_command(linter._opts.command)
+ if not linter_cmd then
+ Log:warn("Not found: " .. linter._opts.command)
+ errors[lnt_config.exe] = {} -- Add data here when necessary
+ else
+ Log:debug("Using linter: " .. linter_cmd)
+ linters[lnt_config.exe] = linter.with {
+ command = linter_cmd,
+ extra_args = lnt_config.args,
+ filetypes = lnt_config.filetypes,
+ }
+ end
+ end
+ end
+
+ return { supported = linters, unsupported = errors }
+end
+
+function M.setup(linter_configs)
+ if vim.tbl_isempty(linter_configs) then
+ return
+ end
+
+ local linters = M.list_configured(linter_configs)
+ null_ls.register { sources = linters.supported }
+end
+
+return M
diff --git a/lua/lvim/lsp/null-ls/services.lua b/lua/lvim/lsp/null-ls/services.lua
new file mode 100644
index 00000000..9cb29f49
--- /dev/null
+++ b/lua/lvim/lsp/null-ls/services.lua
@@ -0,0 +1,63 @@
+local M = {}
+
+local function find_root_dir()
+ local util = require "lspconfig/util"
+ local lsp_utils = require "lvim.lsp.utils"
+
+ local ts_client = lsp_utils.is_client_active "typescript"
+ if ts_client then
+ return ts_client.config.root_dir
+ end
+ local dirname = vim.fn.expand "%:p:h"
+ return util.root_pattern "package.json"(dirname)
+end
+
+local function from_node_modules(command)
+ local root_dir = find_root_dir()
+
+ if not root_dir then
+ return nil
+ end
+
+ return root_dir .. "/node_modules/.bin/" .. command
+end
+
+local local_providers = {
+ prettier = { find = from_node_modules },
+ prettierd = { find = from_node_modules },
+ prettier_d_slim = { find = from_node_modules },
+ eslint_d = { find = from_node_modules },
+ eslint = { find = from_node_modules },
+ stylelint = { find = from_node_modules },
+}
+
+function M.find_command(command)
+ if local_providers[command] then
+ local local_command = local_providers[command].find(command)
+ if local_command and vim.fn.executable(local_command) == 1 then
+ return local_command
+ end
+ end
+
+ if vim.fn.executable(command) == 1 then
+ return command
+ end
+ return nil
+end
+
+function M.list_registered_providers_names(filetype)
+ local u = require "null-ls.utils"
+ local c = require "null-ls.config"
+ local registered = {}
+ for method, source in pairs(c.get()._methods) do
+ for name, filetypes in pairs(source) do
+ if u.filetype_matches(filetypes, filetype) then
+ registered[method] = registered[method] or {}
+ table.insert(registered[method], name)
+ end
+ end
+ end
+ return registered
+end
+
+return M
diff --git a/lua/lvim/lsp/peek.lua b/lua/lvim/lsp/peek.lua
new file mode 100644
index 00000000..08345aff
--- /dev/null
+++ b/lua/lvim/lsp/peek.lua
@@ -0,0 +1,152 @@
+local M = {
+ floating_buf = nil,
+ floating_win = nil,
+ prev_result = nil,
+}
+
+local function create_floating_file(location, opts)
+ vim.validate {
+ location = { location, "t" },
+ opts = { opts, "t", true },
+ }
+
+ -- Set some defaults
+ opts = opts or {}
+ local close_events = opts.close_events or { "CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre" }
+
+ -- location may be LocationLink or Location
+ local uri = location.targetUri or location.uri
+ if uri == nil then
+ return
+ end
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+
+ local range = location.targetRange or location.range
+
+ local contents = vim.api.nvim_buf_get_lines(
+ bufnr,
+ range.start.line,
+ math.min(range["end"].line + 1 + (opts.context or 10), range.start.line + (opts.max_height or 15)), -- Don't let the window be more that 15 lines long(height)
+ false
+ )
+ local width, height = vim.lsp.util._make_floating_popup_size(contents, opts)
+ opts = vim.lsp.util.make_floating_popup_options(width, height, opts)
+ -- Don't make it minimal as it is meant to be fully featured
+ opts["style"] = nil
+
+ vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
+
+ local winnr = vim.api.nvim_open_win(bufnr, false, opts)
+ vim.api.nvim_win_set_option(winnr, "winblend", 0)
+
+ vim.api.nvim_win_set_cursor(winnr, { range.start.line + 1, range.start.character })
+ vim.api.nvim_buf_set_var(bufnr, "lsp_floating_window", winnr)
+
+ -- Set some autocmds to close the window
+ vim.api.nvim_command(
+ "autocmd QuitPre <buffer> ++nested ++once lua pcall(vim.api.nvim_win_close, " .. winnr .. ", true)"
+ )
+ vim.lsp.util.close_preview_autocmd(close_events, winnr)
+
+ return bufnr, winnr
+end
+
+local function preview_location_callback(result)
+ if result == nil or vim.tbl_isempty(result) then
+ return nil
+ end
+
+ local opts = {
+ border = "rounded",
+ context = 10,
+ }
+
+ if vim.tbl_islist(result) then
+ M.prev_result = result[1]
+ M.floating_buf, M.floating_win = create_floating_file(result[1], opts)
+ else
+ M.prev_result = result
+ M.floating_buf, M.floating_win = create_floating_file(result, opts)
+ end
+end
+
+local function preview_location_callback_old_signature(_, _, result)
+ return preview_location_callback(result)
+end
+
+local function preview_location_callback_new_signature(_, result)
+ return preview_location_callback(result)
+end
+
+function M.open_file()
+ -- Get the file currently open in the floating window
+ local filepath = vim.fn.expand "%:."
+
+ if not filepath then
+ print "peek: Unable to open the file!"
+ return
+ end
+
+ -- Close the floating window
+ pcall(vim.api.nvim_win_close, M.floating_win, true)
+
+ -- Edit the file
+ vim.cmd("edit " .. filepath)
+
+ local winnr = vim.api.nvim_get_current_win()
+
+ -- Set the cursor at the right position
+ M.set_cursor_to_prev_pos(winnr)
+end
+
+function M.set_cursor_to_prev_pos(winnr)
+ -- Get position of the thing to peek at
+ local location = M.prev_result
+ local range = location.targetRange or location.range
+ local cursor_pos = { range.start.line + 1, range.start.character }
+
+ -- Set the winnr to the floating window if none was passed in
+ winnr = winnr or M.floating_win
+ -- Set the cursor at the correct position in the floating window
+ vim.api.nvim_win_set_cursor(winnr, cursor_pos)
+end
+
+function M.Peek(what)
+ -- If a window already exists, focus it at the right position!
+ if vim.tbl_contains(vim.api.nvim_list_wins(), M.floating_win) then
+ local success_1, _ = pcall(vim.api.nvim_set_current_win, M.floating_win)
+ if not success_1 then
+ print "peek: You cannot edit the current file in a preview!"
+ return
+ end
+
+ -- Set the cursor at the correct position in the floating window
+ M.set_cursor_to_prev_pos()
+
+ vim.api.nvim_buf_set_keymap(
+ M.floating_buf,
+ "n",
+ "<CR>",
+ ":lua require('lvim.lsp.peek').open_file()<CR>",
+ { noremap = true, silent = true }
+ )
+ else
+ -- Make a new request and then create the new window in the callback
+ local params = vim.lsp.util.make_position_params()
+ local preview_callback = preview_location_callback_old_signature
+ if vim.fn.has "nvim-0.5.1" > 0 then
+ preview_callback = preview_location_callback_new_signature
+ end
+ local success, _ = pcall(vim.lsp.buf_request, 0, "textDocument/" .. what, params, preview_callback)
+ if not success then
+ print(
+ 'peek: Error calling LSP method "textDocument/' .. what .. '". The current language lsp might not support it.'
+ )
+ end
+ end
+end
+
+return M
diff --git a/lua/lvim/lsp/providers/jsonls.lua b/lua/lvim/lsp/providers/jsonls.lua
new file mode 100644
index 00000000..1fffa686
--- /dev/null
+++ b/lua/lvim/lsp/providers/jsonls.lua
@@ -0,0 +1,197 @@
+local default_schemas = nil
+local status_ok, jsonls_settings = pcall(require, "nlspsettings.jsonls")
+if status_ok then
+ default_schemas = jsonls_settings.get_default_schemas()
+end
+
+local schemas = {
+ {
+ description = "TypeScript compiler configuration file",
+ fileMatch = {
+ "tsconfig.json",
+ "tsconfig.*.json",
+ },
+ url = "https://json.schemastore.org/tsconfig.json",
+ },
+ {
+ description = "Lerna config",
+ fileMatch = { "lerna.json" },
+ url = "https://json.schemastore.org/lerna.json",
+ },
+ {
+ description = "Babel configuration",
+ fileMatch = {
+ ".babelrc.json",
+ ".babelrc",
+ "babel.config.json",
+ },
+ url = "https://json.schemastore.org/babelrc.json",
+ },
+ {
+ description = "ESLint config",
+ fileMatch = {
+ ".eslintrc.json",
+ ".eslintrc",
+ },
+ url = "https://json.schemastore.org/eslintrc.json",
+ },
+ {
+ description = "Bucklescript config",
+ fileMatch = { "bsconfig.json" },
+ url = "https://raw.githubusercontent.com/rescript-lang/rescript-compiler/8.2.0/docs/docson/build-schema.json",
+ },
+ {
+ description = "Prettier config",
+ fileMatch = {
+ ".prettierrc",
+ ".prettierrc.json",
+ "prettier.config.json",
+ },
+ url = "https://json.schemastore.org/prettierrc",
+ },
+ {
+ description = "Vercel Now config",
+ fileMatch = { "now.json" },
+ url = "https://json.schemastore.org/now",
+ },
+ {
+ description = "Stylelint config",
+ fileMatch = {
+ ".stylelintrc",
+ ".stylelintrc.json",
+ "stylelint.config.json",
+ },
+ url = "https://json.schemastore.org/stylelintrc",
+ },
+ {
+ description = "A JSON schema for the ASP.NET LaunchSettings.json files",
+ fileMatch = { "launchsettings.json" },
+ url = "https://json.schemastore.org/launchsettings.json",
+ },
+ {
+ description = "Schema for CMake Presets",
+ fileMatch = {
+ "CMakePresets.json",
+ "CMakeUserPresets.json",
+ },
+ url = "https://raw.githubusercontent.com/Kitware/CMake/master/Help/manual/presets/schema.json",
+ },
+ {
+ description = "Configuration file as an alternative for configuring your repository in the settings page.",
+ fileMatch = {
+ ".codeclimate.json",
+ },
+ url = "https://json.schemastore.org/codeclimate.json",
+ },
+ {
+ description = "LLVM compilation database",
+ fileMatch = {
+ "compile_commands.json",
+ },
+ url = "https://json.schemastore.org/compile-commands.json",
+ },
+ {
+ description = "Config file for Command Task Runner",
+ fileMatch = {
+ "commands.json",
+ },
+ url = "https://json.schemastore.org/commands.json",
+ },
+ {
+ description = "AWS CloudFormation provides a common language for you to describe and provision all the infrastructure resources in your cloud environment.",
+ fileMatch = {
+ "*.cf.json",
+ "cloudformation.json",
+ },
+ url = "https://raw.githubusercontent.com/awslabs/goformation/v5.2.9/schema/cloudformation.schema.json",
+ },
+ {
+ description = "The AWS Serverless Application Model (AWS SAM, previously known as Project Flourish) extends AWS CloudFormation to provide a simplified way of defining the Amazon API Gateway APIs, AWS Lambda functions, and Amazon DynamoDB tables needed by your serverless application.",
+ fileMatch = {
+ "serverless.template",
+ "*.sam.json",
+ "sam.json",
+ },
+ url = "https://raw.githubusercontent.com/awslabs/goformation/v5.2.9/schema/sam.schema.json",
+ },
+ {
+ description = "Json schema for properties json file for a GitHub Workflow template",
+ fileMatch = {
+ ".github/workflow-templates/**.properties.json",
+ },
+ url = "https://json.schemastore.org/github-workflow-template-properties.json",
+ },
+ {
+ description = "golangci-lint configuration file",
+ fileMatch = {
+ ".golangci.toml",
+ ".golangci.json",
+ },
+ url = "https://json.schemastore.org/golangci-lint.json",
+ },
+ {
+ description = "JSON schema for the JSON Feed format",
+ fileMatch = {
+ "feed.json",
+ },
+ url = "https://json.schemastore.org/feed.json",
+ versions = {
+ ["1"] = "https://json.schemastore.org/feed-1.json",
+ ["1.1"] = "https://json.schemastore.org/feed.json",
+ },
+ },
+ {
+ description = "Packer template JSON configuration",
+ fileMatch = {
+ "packer.json",
+ },
+ url = "https://json.schemastore.org/packer.json",
+ },
+ {
+ description = "NPM configuration file",
+ fileMatch = {
+ "package.json",
+ },
+ url = "https://json.schemastore.org/package.json",
+ },
+ {
+ description = "JSON schema for Visual Studio component configuration files",
+ fileMatch = {
+ "*.vsconfig",
+ },
+ url = "https://json.schemastore.org/vsconfig.json",
+ },
+ {
+ description = "Resume json",
+ fileMatch = { "resume.json" },
+ url = "https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json",
+ },
+}
+
+local function extend(tab1, tab2)
+ for _, value in ipairs(tab2) do
+ table.insert(tab1, value)
+ end
+ return tab1
+end
+
+local extended_schemas = extend(schemas, default_schemas)
+
+local opts = {
+ settings = {
+ json = {
+ schemas = extended_schemas,
+ },
+ },
+ setup = {
+ commands = {
+ Format = {
+ function()
+ vim.lsp.buf.range_formatting({}, { 0, 0 }, { vim.fn.line "$", 0 })
+ end,
+ },
+ },
+ },
+}
+
+return opts
diff --git a/lua/lvim/lsp/providers/sumneko_lua.lua b/lua/lvim/lsp/providers/sumneko_lua.lua
new file mode 100644
index 00000000..6585c8c7
--- /dev/null
+++ b/lua/lvim/lsp/providers/sumneko_lua.lua
@@ -0,0 +1,19 @@
+local opts = {
+ settings = {
+ Lua = {
+ diagnostics = {
+ globals = { "vim", "lvim" },
+ },
+ workspace = {
+ library = {
+ [require("lvim.utils").join_paths(get_runtime_dir(), "lvim", "lua")] = true,
+ [vim.fn.expand "$VIMRUNTIME/lua"] = true,
+ [vim.fn.expand "$VIMRUNTIME/lua/vim/lsp"] = true,
+ },
+ maxPreload = 100000,
+ preloadFileSize = 10000,
+ },
+ },
+ },
+}
+return opts
diff --git a/lua/lvim/lsp/providers/vuels.lua b/lua/lvim/lsp/providers/vuels.lua
new file mode 100644
index 00000000..326363fd
--- /dev/null
+++ b/lua/lvim/lsp/providers/vuels.lua
@@ -0,0 +1,26 @@
+local opts = {
+ setup = {
+ root_dir = function(fname)
+ local util = require "lvim.lspconfig/util"
+ return util.root_pattern "package.json"(fname) or util.root_pattern "vue.config.js"(fname) or vim.fn.getcwd()
+ end,
+ init_options = {
+ config = {
+ vetur = {
+ completion = {
+ autoImport = true,
+ tagCasing = "kebab",
+ useScaffoldSnippets = true,
+ },
+ useWorkspaceDependencies = true,
+ validation = {
+ script = true,
+ style = true,
+ template = true,
+ },
+ },
+ },
+ },
+ },
+}
+return opts
diff --git a/lua/lvim/lsp/providers/yamlls.lua b/lua/lvim/lsp/providers/yamlls.lua
new file mode 100644
index 00000000..156a35b0
--- /dev/null
+++ b/lua/lvim/lsp/providers/yamlls.lua
@@ -0,0 +1,30 @@
+local opts = {
+ settings = {
+ yaml = {
+ hover = true,
+ completion = true,
+ validate = true,
+ schemaStore = {
+ enable = true,
+ url = "https://www.schemastore.org/api/json/catalog.json",
+ },
+ schemas = {
+ kubernetes = {
+ "daemon.{yml,yaml}",
+ "manager.{yml,yaml}",
+ "restapi.{yml,yaml}",
+ "role.{yml,yaml}",
+ "role_binding.{yml,yaml}",
+ "*onfigma*.{yml,yaml}",
+ "*ngres*.{yml,yaml}",
+ "*ecre*.{yml,yaml}",
+ "*eployment*.{yml,yaml}",
+ "*ervic*.{yml,yaml}",
+ "kubectl-edit*.yaml",
+ },
+ },
+ },
+ },
+}
+
+return opts
diff --git a/lua/lvim/lsp/templates.lua b/lua/lvim/lsp/templates.lua
new file mode 100644
index 00000000..e0741b1b
--- /dev/null
+++ b/lua/lvim/lsp/templates.lua
@@ -0,0 +1,98 @@
+local M = {}
+
+local Log = require "lvim.core.log"
+local utils = require "lvim.utils"
+local get_supported_filetypes = require("lvim.lsp.utils").get_supported_filetypes
+
+local ftplugin_dir = lvim.lsp.templates_dir
+
+local join_paths = _G.join_paths
+
+function M.remove_template_files()
+ -- remove any outdated files
+ for _, file in ipairs(vim.fn.glob(ftplugin_dir .. "/*.lua", 1, 1)) do
+ vim.fn.delete(file)
+ end
+end
+
+---Checks if a server is ignored by default because of a conflict
+---Only TSServer is enabled by default for the javascript-family
+---@param server_name string
+function M.is_ignored(server_name, filetypes)
+ --TODO: this is easy to be made configurable once stable
+ filetypes = filetypes or get_supported_filetypes(server_name)
+
+ if vim.tbl_contains(filetypes, "javascript") then
+ if server_name == "tsserver" then
+ return false
+ else
+ return true
+ end
+ end
+
+ local blacklist = {
+ "jedi_language_server",
+ "pylsp",
+ "sqlls",
+ "sqls",
+ "angularls",
+ "ansiblels",
+ }
+ return vim.tbl_contains(blacklist, server_name)
+end
+
+---Generates an ftplugin file based on the server_name in the selected directory
+---@param server_name string name of a valid language server, e.g. pyright, gopls, tsserver, etc.
+---@param dir string the full path to the desired directory
+function M.generate_ftplugin(server_name, dir)
+ -- we need to go through lspconfig to get the corresponding filetypes currently
+ local filetypes = get_supported_filetypes(server_name) or {}
+ if not filetypes then
+ return
+ end
+
+ if M.is_ignored(server_name, filetypes) then
+ return
+ end
+
+ -- print("got associated filetypes: " .. vim.inspect(filetypes))
+
+ for _, filetype in ipairs(filetypes) do
+ local filename = join_paths(dir, filetype .. ".lua")
+ local setup_cmd = string.format([[require("lvim.lsp.manager").setup(%q)]], server_name)
+ -- print("using setup_cmd: " .. setup_cmd)
+ -- overwrite the file completely
+ utils.write_file(filename, setup_cmd .. "\n", "a")
+ end
+end
+
+---Generates ftplugin files based on a list of server_names
+---The files are generated to a runtimepath: "$LUNARVIM_RUNTIME_DIR/site/after/ftplugin/template.lua"
+---@param servers_names table list of servers to be enabled. Will add all by default
+function M.generate_templates(servers_names)
+ servers_names = servers_names or {}
+
+ Log:debug "Templates installation in progress"
+
+ M.remove_template_files()
+
+ if vim.tbl_isempty(servers_names) then
+ local available_servers = require("nvim-lsp-installer.servers").get_available_servers()
+
+ for _, server in pairs(available_servers) do
+ table.insert(servers_names, server.name)
+ end
+ end
+
+ -- create the directory if it didn't exist
+ if not utils.is_directory(lvim.lsp.templates_dir) then
+ vim.fn.mkdir(ftplugin_dir, "p")
+ end
+
+ for _, server in ipairs(servers_names) do
+ M.generate_ftplugin(server, ftplugin_dir)
+ end
+ Log:debug "Templates installation is complete"
+end
+
+return M
diff --git a/lua/lvim/lsp/utils.lua b/lua/lvim/lsp/utils.lua
new file mode 100644
index 00000000..a34fbf44
--- /dev/null
+++ b/lua/lvim/lsp/utils.lua
@@ -0,0 +1,62 @@
+local M = {}
+
+local tbl = require "lvim.utils.table"
+
+function M.is_client_active(name)
+ local clients = vim.lsp.get_active_clients()
+ return tbl.find_first(clients, function(client)
+ return client.name == name
+ end)
+end
+
+function M.get_active_clients_by_ft(filetype)
+ local matches = {}
+ local clients = vim.lsp.get_active_clients()
+ for _, client in pairs(clients) do
+ local supported_filetypes = client.config.filetypes or {}
+ if client.name ~= "null-ls" and vim.tbl_contains(supported_filetypes, filetype) then
+ table.insert(matches, client)
+ end
+ end
+ return matches
+end
+
+function M.get_client_capabilities(client_id)
+ if not client_id then
+ local buf_clients = vim.lsp.buf_get_clients()
+ for _, buf_client in ipairs(buf_clients) do
+ if buf_client.name ~= "null-ls" then
+ client_id = buf_client.id
+ break
+ end
+ end
+ end
+ if not client_id then
+ error "Unable to determine client_id"
+ return
+ end
+
+ local client = vim.lsp.get_client_by_id(tonumber(client_id))
+
+ local enabled_caps = {}
+ for capability, status in pairs(client.resolved_capabilities) do
+ if status == true then
+ table.insert(enabled_caps, capability)
+ end
+ end
+
+ return enabled_caps
+end
+
+function M.get_supported_filetypes(server_name)
+ -- print("got filetypes query request for: " .. server_name)
+ local configs = require "lspconfig/configs"
+ pcall(require, ("lspconfig/" .. server_name))
+ for _, config in pairs(configs) do
+ if config.name == server_name then
+ return config.document_config.default_config.filetypes or {}
+ end
+ end
+end
+
+return M
diff --git a/lua/lvim/lualine/themes/onedarker.lua b/lua/lvim/lualine/themes/onedarker.lua
new file mode 100644
index 00000000..396657bb
--- /dev/null
+++ b/lua/lvim/lualine/themes/onedarker.lua
@@ -0,0 +1,35 @@
+-- Copyright (c) 2020-2021 shadmansaleh
+-- MIT license, see LICENSE for more details.
+-- Credit: Zoltan Dalmadi(lightline)
+-- LuaFormatter off
+local colors = {
+ blue = "#61afef",
+ green = "#98c379",
+ purple = "#c678dd",
+ red1 = "#e06c75",
+ red2 = "#be5046",
+ yellow = "#e5c07b",
+ orange = "#D19A66",
+ fg = "#abb2bf",
+ bg = "#282c34",
+ gray1 = "#5c6370",
+ gray2 = "#2c323d",
+ gray3 = "#3e4452",
+}
+-- LuaFormatter on
+return {
+ normal = {
+ a = { fg = colors.fg, bg = colors.blue, gui = "bold" },
+ b = { fg = colors.fg, bg = colors.bg },
+ c = { fg = colors.fg, bg = colors.bg },
+ },
+ insert = { a = { fg = colors.fg, bg = colors.green, gui = "bold" } },
+ visual = { a = { fg = colors.fg, bg = colors.purple, gui = "bold" } },
+ command = { a = { fg = colors.fg, bg = colors.yellow, gui = "bold" } },
+ replace = { a = { fg = colors.fg, bg = colors.red1, gui = "bold" } },
+ inactive = {
+ a = { fg = colors.gray1, bg = colors.bg, gui = "bold" },
+ b = { fg = colors.gray1, bg = colors.bg },
+ c = { fg = colors.gray1, bg = colors.bg },
+ },
+}
diff --git a/lua/lvim/plugin-loader.lua b/lua/lvim/plugin-loader.lua
new file mode 100644
index 00000000..feef7ea7
--- /dev/null
+++ b/lua/lvim/plugin-loader.lua
@@ -0,0 +1,63 @@
+local plugin_loader = {}
+
+local utils = require "lvim.utils"
+local Log = require "lvim.core.log"
+-- we need to reuse this outside of init()
+local compile_path = get_config_dir() .. "/plugin/packer_compiled.lua"
+
+function plugin_loader:init(opts)
+ opts = opts or {}
+
+ local install_path = opts.install_path or vim.fn.stdpath "data" .. "/site/pack/packer/start/packer.nvim"
+ local package_root = opts.package_root or vim.fn.stdpath "data" .. "/site/pack"
+
+ if vim.fn.empty(vim.fn.glob(install_path)) > 0 then
+ vim.fn.system { "git", "clone", "--depth", "1", "https://github.com/wbthomason/packer.nvim", install_path }
+ vim.cmd "packadd packer.nvim"
+ end
+
+ local packer_ok, packer = pcall(require, "packer")
+ if not packer_ok then
+ return
+ end
+
+ packer.init {
+ package_root = package_root,
+ compile_path = compile_path,
+ git = { clone_timeout = 300 },
+ display = {
+ open_fn = function()
+ return require("packer.util").float { border = "rounded" }
+ end,
+ },
+ }
+
+ self.packer = packer
+ return self
+end
+
+function plugin_loader:cache_clear()
+ if vim.fn.delete(compile_path) == 0 then
+ Log:debug "deleted packer_compiled.lua"
+ end
+end
+
+function plugin_loader:cache_reset()
+ self.cache_clear()
+ require("packer").compile()
+ if utils.is_file(compile_path) then
+ Log:debug "generated packer_compiled.lua"
+ end
+end
+
+function plugin_loader:load(configurations)
+ return self.packer.startup(function(use)
+ for _, plugins in ipairs(configurations) do
+ for _, plugin in ipairs(plugins) do
+ use(plugin)
+ end
+ end
+ end)
+end
+
+return plugin_loader
diff --git a/lua/lvim/plugins.lua b/lua/lvim/plugins.lua
new file mode 100644
index 00000000..fcb23328
--- /dev/null
+++ b/lua/lvim/plugins.lua
@@ -0,0 +1,180 @@
+return {
+ -- Packer can manage itself as an optional plugin
+ { "wbthomason/packer.nvim" },
+ { "neovim/nvim-lspconfig" },
+ { "tamago324/nlsp-settings.nvim" },
+ { "jose-elias-alvarez/null-ls.nvim" },
+ { "antoinemadec/FixCursorHold.nvim" }, -- Needed while issue https://github.com/neovim/neovim/issues/12587 is still open
+ {
+ "williamboman/nvim-lsp-installer",
+ },
+
+ { "nvim-lua/popup.nvim" },
+ { "nvim-lua/plenary.nvim" },
+ -- Telescope
+ {
+ "nvim-telescope/telescope.nvim",
+ config = function()
+ require("lvim.core.telescope").setup()
+ end,
+ disable = not lvim.builtin.telescope.active,
+ },
+ -- Install nvim-cmp, and buffer source as a dependency
+ {
+ "hrsh7th/nvim-cmp",
+ config = function()
+ require("lvim.core.cmp").setup()
+ end,
+ requires = {
+ "L3MON4D3/LuaSnip",
+ "saadparwaiz1/cmp_luasnip",
+ "hrsh7th/cmp-buffer",
+ "hrsh7th/cmp-nvim-lsp",
+ "hrsh7th/cmp-path",
+ "hrsh7th/cmp-nvim-lua",
+ },
+ run = function()
+ -- cmp's config requires cmp to be installed to run the first time
+ if not lvim.builtin.cmp then
+ require("lvim.core.cmp").config()
+ end
+ end,
+ },
+ {
+ "rafamadriz/friendly-snippets",
+ -- event = "InsertCharPre",
+ -- disable = not lvim.builtin.compe.active,
+ },
+
+ -- Autopairs
+ {
+ "windwp/nvim-autopairs",
+ -- event = "InsertEnter",
+ config = function()
+ require("lvim.core.autopairs").setup()
+ end,
+ disable = not lvim.builtin.autopairs.active,
+ },
+
+ -- Treesitter
+ {
+ "nvim-treesitter/nvim-treesitter",
+ branch = "0.5-compat",
+ -- run = ":TSUpdate",
+ config = function()
+ require("lvim.core.treesitter").setup()
+ end,
+ },
+
+ -- NvimTree
+ {
+ "kyazdani42/nvim-tree.lua",
+ -- event = "BufWinOpen",
+ -- cmd = "NvimTreeToggle",
+ -- commit = "fd7f60e242205ea9efc9649101c81a07d5f458bb",
+ config = function()
+ require("lvim.core.nvimtree").setup()
+ end,
+ disable = not lvim.builtin.nvimtree.active,
+ },
+
+ {
+ "lewis6991/gitsigns.nvim",
+
+ config = function()
+ require("lvim.core.gitsigns").setup()
+ end,
+ event = "BufRead",
+ disable = not lvim.builtin.gitsigns.active,
+ },
+
+ -- Whichkey
+ {
+ "folke/which-key.nvim",
+ config = function()
+ require("lvim.core.which-key").setup()
+ end,
+ event = "BufWinEnter",
+ disable = not lvim.builtin.which_key.active,
+ },
+
+ -- Comments
+ {
+ "terrortylor/nvim-comment",
+ event = "BufRead",
+ config = function()
+ require("lvim.core.comment").setup()
+ end,
+ disable = not lvim.builtin.comment.active,
+ },
+
+ -- project.nvim
+ {
+ "ahmedkhalf/project.nvim",
+ config = function()
+ require("lvim.core.project").setup()
+ end,
+ disable = not lvim.builtin.project.active,
+ },
+
+ -- Icons
+ { "kyazdani42/nvim-web-devicons" },
+
+ -- Status Line and Bufferline
+ {
+ -- "hoob3rt/lualine.nvim",
+ "shadmansaleh/lualine.nvim",
+ -- "Lunarvim/lualine.nvim",
+ config = function()
+ require("lvim.core.lualine").setup()
+ end,
+ disable = not lvim.builtin.lualine.active,
+ },
+
+ {
+ "romgrk/barbar.nvim",
+ config = function()
+ require("lvim.core.bufferline").setup()
+ end,
+ event = "BufWinEnter",
+ disable = not lvim.builtin.bufferline.active,
+ },
+
+ -- Debugging
+ {
+ "mfussenegger/nvim-dap",
+ -- event = "BufWinEnter",
+ config = function()
+ require("lvim.core.dap").setup()
+ end,
+ disable = not lvim.builtin.dap.active,
+ },
+
+ -- Debugger management
+ {
+ "Pocco81/DAPInstall.nvim",
+ -- event = "BufWinEnter",
+ -- event = "BufRead",
+ disable = not lvim.builtin.dap.active,
+ },
+
+ -- Dashboard
+ {
+ "ChristianChiarulli/dashboard-nvim",
+ event = "BufWinEnter",
+ config = function()
+ require("lvim.core.dashboard").setup()
+ end,
+ disable = not lvim.builtin.dashboard.active,
+ },
+
+ -- Terminal
+ {
+ "akinsho/toggleterm.nvim",
+ event = "BufWinEnter",
+ config = function()
+ require("lvim.core.terminal").setup()
+ end,
+ disable = not lvim.builtin.terminal.active,
+ },
+}
diff --git a/lua/lvim/utils/ft.lua b/lua/lvim/utils/ft.lua
new file mode 100644
index 00000000..e9852e6f
--- /dev/null
+++ b/lua/lvim/utils/ft.lua
@@ -0,0 +1,47 @@
+-- Here be dragons
+-- Opening files with telescope will not start LSP without this
+local ft = {}
+
+ft.find_lua_ftplugins = function(filetype)
+ local patterns = {
+ string.format("ftplugin/%s.lua", filetype),
+
+ -- Looks like we don't need this, because the first one works
+ -- string.format("after/ftplugin/%s.lua", filetype),
+ }
+
+ local result = {}
+ for _, pat in ipairs(patterns) do
+ vim.list_extend(result, vim.api.nvim_get_runtime_file(pat, true))
+ end
+
+ return result
+end
+
+ft.do_filetype = function(filetype)
+ local ftplugins = ft.find_lua_ftplugins(filetype)
+
+ local f_env = setmetatable({
+ -- Override print, so the prints still go through, otherwise it's confusing for people
+ print = vim.schedule_wrap(print),
+ }, {
+ -- Buf default back read/write to whatever is going on in the global landscape
+ __index = _G,
+ __newindex = _G,
+ })
+
+ for _, file in ipairs(ftplugins) do
+ local f = loadfile(file)
+ if not f then
+ vim.api.nvim_err_writeln("Unable to load file: " .. file)
+ else
+ local ok, msg = pcall(setfenv(f, f_env))
+
+ if not ok then
+ vim.api.nvim_err_writeln("Error while processing file: " .. file .. "\n" .. msg)
+ end
+ end
+ end
+end
+
+return ft
diff --git a/lua/lvim/utils/hooks.lua b/lua/lvim/utils/hooks.lua
new file mode 100644
index 00000000..d536bc76
--- /dev/null
+++ b/lua/lvim/utils/hooks.lua
@@ -0,0 +1,35 @@
+local M = {}
+
+local Log = require "lvim.core.log"
+local in_headless = #vim.api.nvim_list_uis() == 0
+
+function M.run_pre_update()
+ Log:debug "Starting pre-update hook"
+ _G.__luacache.clear_cache()
+end
+
+---Reset any startup cache files used by Packer and Impatient
+---It also forces regenerating any template ftplugin files
+---Tip: Useful for clearing any outdated settings
+function M.reset_cache()
+ _G.__luacache.clear_cache()
+ require("lvim.plugin-loader"):cache_reset()
+ package.loaded["lvim.lsp.templates"] = nil
+ require("lvim.lsp.templates").generate_templates()
+end
+
+function M.run_post_update()
+ Log:debug "Starting post-update hook"
+ M.reset_cache()
+
+ if not in_headless then
+ vim.schedule(function()
+ require("packer").install()
+ -- TODO: add a changelog
+ vim.notify("Update complete", vim.log.levels.INFO)
+ vim.cmd "LspStart"
+ end)
+ end
+end
+
+return M
diff --git a/lua/lvim/utils/init.lua b/lua/lvim/utils/init.lua
new file mode 100644
index 00000000..cebbe75c
--- /dev/null
+++ b/lua/lvim/utils/init.lua
@@ -0,0 +1,205 @@
+local utils = {}
+local Log = require "lvim.core.log"
+local uv = vim.loop
+
+-- recursive Print (structure, limit, separator)
+local function r_inspect_settings(structure, limit, separator)
+ limit = limit or 100 -- default item limit
+ separator = separator or "." -- indent string
+ if limit < 1 then
+ print "ERROR: Item limit reached."
+ return limit - 1
+ end
+ if structure == nil then
+ io.write("-- O", separator:sub(2), " = nil\n")
+ return limit - 1
+ end
+ local ts = type(structure)
+
+ if ts == "table" then
+ for k, v in pairs(structure) do
+ -- replace non alpha keys with ["key"]
+ if tostring(k):match "[^%a_]" then
+ k = '["' .. tostring(k) .. '"]'
+ end
+ limit = r_inspect_settings(v, limit, separator .. "." .. tostring(k))
+ if limit < 0 then
+ break
+ end
+ end
+ return limit
+ end
+
+ if ts == "string" then
+ -- escape sequences
+ structure = string.format("%q", structure)
+ end
+ separator = separator:gsub("%.%[", "%[")
+ if type(structure) == "function" then
+ -- don't print functions
+ io.write("-- lvim", separator:sub(2), " = function ()\n")
+ else
+ io.write("lvim", separator:sub(2), " = ", tostring(structure), "\n")
+ end
+ return limit - 1
+end
+
+function utils.generate_settings()
+ -- Opens a file in append mode
+ local file = io.open("lv-settings.lua", "w")
+
+ -- sets the default output file as test.lua
+ io.output(file)
+
+ -- write all `lvim` related settings to `lv-settings.lua` file
+ r_inspect_settings(lvim, 10000, ".")
+
+ -- closes the open file
+ io.close(file)
+end
+
+-- autoformat
+function utils.toggle_autoformat()
+ if lvim.format_on_save then
+ require("lvim.core.autocmds").define_augroups {
+ autoformat = {
+ {
+ "BufWritePre",
+ "*",
+ ":silent lua vim.lsp.buf.formatting_sync()",
+ },
+ },
+ }
+ Log:debug "Format on save active"
+ end
+
+ if not lvim.format_on_save then
+ vim.cmd [[
+ if exists('#autoformat#BufWritePre')
+ :autocmd! autoformat
+ endif
+ ]]
+ Log:debug "Format on save off"
+ end
+end
+
+function utils.unrequire(m)
+ package.loaded[m] = nil
+ _G[m] = nil
+end
+
+function utils.gsub_args(args)
+ if args == nil or type(args) ~= "table" then
+ return args
+ end
+ local buffer_filepath = vim.fn.fnameescape(vim.api.nvim_buf_get_name(0))
+ for i = 1, #args do
+ args[i] = string.gsub(args[i], "${FILEPATH}", buffer_filepath)
+ end
+ return args
+end
+
+--- Returns a table with the default values that are missing.
+--- either paramter can be empty.
+--@param config (table) table containing entries that take priority over defaults
+--@param default_config (table) table contatining default values if found
+function utils.apply_defaults(config, default_config)
+ config = config or {}
+ default_config = default_config or {}
+ local new_config = vim.tbl_deep_extend("keep", vim.empty_dict(), config)
+ new_config = vim.tbl_deep_extend("keep", new_config, default_config)
+ return new_config
+end
+
+--- Checks whether a given path exists and is a file.
+--@param path (string) path to check
+--@returns (bool)
+function utils.is_file(path)
+ local stat = uv.fs_stat(path)
+ return stat and stat.type == "file" or false
+end
+
+--- Checks whether a given path exists and is a directory
+--@param path (string) path to check
+--@returns (bool)
+function utils.is_directory(path)
+ local stat = uv.fs_stat(path)
+ return stat and stat.type == "directory" or false
+end
+
+utils.join_paths = _G.join_paths
+
+function utils.write_file(path, txt, flag)
+ uv.fs_open(path, flag, 438, function(open_err, fd)
+ assert(not open_err, open_err)
+ uv.fs_write(fd, txt, -1, function(write_err)
+ assert(not write_err, write_err)
+ uv.fs_close(fd, function(close_err)
+ assert(not close_err, close_err)
+ end)
+ end)
+ end)
+end
+
+function utils.debounce(ms, fn)
+ local timer = vim.loop.new_timer()
+ return function(...)
+ local argv = { ... }
+ timer:start(ms, 0, function()
+ timer:stop()
+ vim.schedule_wrap(fn)(unpack(argv))
+ end)
+ end
+end
+
+function utils.search_file(file, args)
+ local Job = require "plenary.job"
+ local stderr = {}
+ local stdout, ret = Job
+ :new({
+ command = "grep",
+ args = { args, file },
+ cwd = get_cache_dir(),
+ on_stderr = function(_, data)
+ table.insert(stderr, data)
+ end,
+ })
+ :sync()
+ return stdout, ret, stderr
+end
+
+function utils.file_contains(file, query)
+ local stdout, ret, stderr = utils.search_file(file, query)
+ if ret == 0 then
+ return true
+ end
+ if not vim.tbl_isempty(stderr) then
+ error(vim.inspect(stderr))
+ end
+ if not vim.tbl_isempty(stdout) then
+ error(vim.inspect(stdout))
+ end
+ return false
+end
+
+function utils.log_contains(query)
+ local logfile = require("lvim.core.log"):get_path()
+ local stdout, ret, stderr = utils.search_file(logfile, query)
+ if ret == 0 then
+ return true
+ end
+ if not vim.tbl_isempty(stderr) then
+ error(vim.inspect(stderr))
+ end
+ if not vim.tbl_isempty(stdout) then
+ error(vim.inspect(stdout))
+ end
+ if not vim.tbl_isempty(stderr) then
+ error(vim.inspect(stderr))
+ end
+ return false
+end
+
+return utils
+
+-- TODO: find a new home for these autocommands
diff --git a/lua/lvim/utils/table.lua b/lua/lvim/utils/table.lua
new file mode 100644
index 00000000..1ac5949e
--- /dev/null
+++ b/lua/lvim/utils/table.lua
@@ -0,0 +1,24 @@
+local Table = {}
+
+--- Find the first entry for which the predicate returns true.
+-- @param t The table
+-- @param predicate The function called for each entry of t
+-- @return The entry for which the predicate returned True or nil
+function Table.find_first(t, predicate)
+ for _, entry in pairs(t) do
+ if predicate(entry) then
+ return entry
+ end
+ end
+ return nil
+end
+
+--- Check if the predicate returns True for at least one entry of the table.
+-- @param t The table
+-- @param predicate The function called for each entry of t
+-- @return True if predicate returned True at least once, false otherwise
+function Table.contains(t, predicate)
+ return Table.find_first(t, predicate) ~= nil
+end
+
+return Table