Compare commits

..

11 Commits

Author SHA1 Message Date
173ff5e47a feat: lsp 2025-10-23 20:16:45 +03:00
bb12a11cc0 feat: lazy, mason, ts 2025-10-23 05:08:38 +03:00
08c5a0536e mend 2025-10-22 17:57:07 +03:00
65ef4a6828 feat: markdown formatter 2025-10-22 17:53:14 +03:00
77d4250057 refactor: unify syntax langs 2025-10-22 17:17:24 +03:00
bcbd5c9faa update nvim-tree to fork 2025-10-22 03:10:12 +03:00
d7525c88d4 feat: new keymaps 2025-10-22 02:58:18 +03:00
804832e0c3 feat: tabline 2025-10-20 22:41:03 +03:00
d4ec924088 update lazy-lock 2025-10-20 21:31:29 +03:00
6cf0a92d91 fix: remove dev load 2025-10-20 21:28:16 +03:00
eeea996f0b update lazy-lock 2025-10-20 21:11:31 +03:00
21 changed files with 1225 additions and 139 deletions

View File

@@ -21,6 +21,7 @@ require('config.autocmds')
require('config.clipboard')
require('config.terminal')
require('custom.navigation')
require('custom.tabline').setup()
require('lazy').setup({
spec = { { import = 'plugins' } },
install = { missing = false },

View File

@@ -1,12 +1,11 @@
{
"conform.nvim": { "branch": "master", "commit": "fbcb4fa7f34bfea9be702ffff481a8e336ebf6ed" },
"fzf-lua": { "branch": "main", "commit": "58ebb27333bd12cb497f27b7da07a677116bc0ef" },
"conform.nvim": { "branch": "master", "commit": "9fd3d5e0b689ec1bf400c53cbbec72c6fdf24081" },
"invero.nvim": { "branch": "main", "commit": "6acdefa2f2fdf544b53b5786e748be0ae8e9fd23" },
"lazy.nvim": { "branch": "main", "commit": "1ea3c4085785f460fb0e46d2fe1ee895f5f9e7c1" },
"mason.nvim": { "branch": "main", "commit": "ad7146aa61dcaeb54fa900144d768f040090bff0" },
"nvim-autopairs": { "branch": "master", "commit": "7a2c97cccd60abc559344042fefb1d5a85b3e33b" },
"nvim-lint": { "branch": "master", "commit": "9da1fb942dd0668d5182f9c8dee801b9c190e2bb" },
"nvim-tree.lua": { "branch": "master", "commit": "321bc61580fd066b76861c32de3319c3a6d089e7" },
"nvim-tree.lua": { "branch": "master", "commit": "7c0f7e906ab6f11b61eec52171eaf7dc06726ef1" },
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" },
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" },
"rose-pine": { "branch": "main", "commit": "72a04c4065345b51b56aed4859ea1d884f734097" }
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" }
}

29
lsp/bashls.lua Normal file
View File

@@ -0,0 +1,29 @@
---@brief
---
--- https://github.com/bash-lsp/bash-language-server
---
--- `bash-language-server` can be installed via `npm`:
--- ```sh
--- npm i -g bash-language-server
--- ```
---
--- Language server for bash, written using tree sitter in typescript.
---@type vim.lsp.Config
return {
cmd = { 'bash-language-server', 'start' },
settings = {
bashIde = {
-- Glob pattern for finding and parsing shell script files in the workspace.
-- Used by the background analysis features across files.
-- Prevent recursive scanning which will cause issues when opening a file
-- directly in the home directory (e.g. ~/foo.sh).
--
-- Default upstream pattern is "**/*@(.sh|.inc|.bash|.command)".
globPattern = vim.env.GLOB_PATTERN or '*@(.sh|.inc|.bash|.command)',
},
},
filetypes = { 'bash', 'sh' },
root_markers = { '.git' },
}

100
lsp/clangd.lua Normal file
View File

@@ -0,0 +1,100 @@
---@brief
---
--- https://clangd.llvm.org/installation.html
---
--- - **NOTE:** Clang >= 11 is recommended! See [#23](https://github.com/neovim/nvim-lspconfig/issues/23).
--- - If `compile_commands.json` lives in a build directory, you should
--- symlink it to the root of your source tree.
--- ```
--- ln -s /path/to/myproject/build/compile_commands.json /path/to/myproject/
--- ```
--- - clangd relies on a [JSON compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html)
--- specified as compile_commands.json, see https://clangd.llvm.org/installation#compile_commandsjson
-- https://clangd.llvm.org/extensions.html#switch-between-sourceheader
local function switch_source_header(bufnr, client)
local method_name = 'textDocument/switchSourceHeader'
---@diagnostic disable-next-line:param-type-mismatch
if not client or not client:supports_method(method_name) then
return vim.notify(('method %s is not supported by any servers active on the current buffer'):format(method_name))
end
local params = vim.lsp.util.make_text_document_params(bufnr)
---@diagnostic disable-next-line:param-type-mismatch
client:request(method_name, params, function(err, result)
if err then
error(tostring(err))
end
if not result then
vim.notify('corresponding file cannot be determined')
return
end
vim.cmd.edit(vim.uri_to_fname(result))
end, bufnr)
end
local function symbol_info(bufnr, client)
local method_name = 'textDocument/symbolInfo'
---@diagnostic disable-next-line:param-type-mismatch
if not client or not client:supports_method(method_name) then
return vim.notify('Clangd client not found', vim.log.levels.ERROR)
end
local win = vim.api.nvim_get_current_win()
local params = vim.lsp.util.make_position_params(win, client.offset_encoding)
---@diagnostic disable-next-line:param-type-mismatch
client:request(method_name, params, function(err, res)
if err or #res == 0 then
-- Clangd always returns an error, there is no reason to parse it
return
end
local container = string.format('container: %s', res[1].containerName) ---@type string
local name = string.format('name: %s', res[1].name) ---@type string
vim.lsp.util.open_floating_preview({ name, container }, '', {
height = 2,
width = math.max(string.len(name), string.len(container)),
focusable = false,
focus = false,
title = 'Symbol Info',
})
end, bufnr)
end
---@class ClangdInitializeResult: lsp.InitializeResult
---@field offsetEncoding? string
---@type vim.lsp.Config
return {
cmd = { 'clangd' },
filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'cuda' },
root_markers = {
'.clangd',
'.clang-tidy',
'.clang-format',
'compile_commands.json',
'compile_flags.txt',
'configure.ac', -- AutoTools
'.git',
},
capabilities = {
textDocument = {
completion = {
editsNearCursor = true,
},
},
offsetEncoding = { 'utf-8', 'utf-16' },
},
---@param init_result ClangdInitializeResult
on_init = function(client, init_result)
if init_result.offsetEncoding then
client.offset_encoding = init_result.offsetEncoding
end
end,
on_attach = function(client, bufnr)
vim.api.nvim_buf_create_user_command(bufnr, 'LspClangdSwitchSourceHeader', function()
switch_source_header(bufnr, client)
end, { desc = 'Switch between source/header' })
vim.api.nvim_buf_create_user_command(bufnr, 'LspClangdShowSymbolInfo', function()
symbol_info(bufnr, client)
end, { desc = 'Show symbol info' })
end,
}

34
lsp/cssls.lua Normal file
View File

@@ -0,0 +1,34 @@
---@brief
---
--- https://github.com/hrsh7th/vscode-langservers-extracted
---
--- `css-languageserver` can be installed via `npm`:
---
--- ```sh
--- npm i -g vscode-langservers-extracted
--- ```
---
--- Neovim does not currently include built-in snippets. `vscode-css-language-server` only provides completions when snippet support is enabled. To enable completion, install a snippet plugin and add the following override to your language client capabilities during setup.
---
--- ```lua
--- --Enable (broadcasting) snippet capability for completion
--- local capabilities = vim.lsp.protocol.make_client_capabilities()
--- capabilities.textDocument.completion.completionItem.snippetSupport = true
---
--- vim.lsp.config('cssls', {
--- capabilities = capabilities,
--- })
--- ```
---@type vim.lsp.Config
return {
cmd = { 'vscode-css-language-server', '--stdio' },
filetypes = { 'css', 'scss', 'less' },
init_options = { provideFormatter = true }, -- needed to enable formatting capabilities
root_markers = { 'package.json', '.git' },
settings = {
css = { validate = true },
scss = { validate = true },
less = { validate = true },
},
}

226
lsp/eslint.lua Normal file
View File

@@ -0,0 +1,226 @@
--- @brief
---
--- https://github.com/hrsh7th/vscode-langservers-extracted
---
--- `vscode-eslint-language-server` is a linting engine for JavaScript / Typescript.
--- It can be installed via `npm`:
---
--- ```sh
--- npm i -g vscode-langservers-extracted
--- ```
---
--- The default `on_attach` config provides the `LspEslintFixAll` command that can be used to format a document on save:
--- ```lua
--- local base_on_attach = vim.lsp.config.eslint.on_attach
--- vim.lsp.config("eslint", {
--- on_attach = function(client, bufnr)
--- if not base_on_attach then return end
---
--- base_on_attach(client, bufnr)
--- vim.api.nvim_create_autocmd("BufWritePre", {
--- buffer = bufnr,
--- command = "LspEslintFixAll",
--- })
--- end,
--- })
--- ```
---
--- See [vscode-eslint](https://github.com/microsoft/vscode-eslint/blob/55871979d7af184bf09af491b6ea35ebd56822cf/server/src/eslintServer.ts#L216-L229) for configuration options.
---
--- Messages handled in lspconfig: `eslint/openDoc`, `eslint/confirmESLintExecution`, `eslint/probeFailed`, `eslint/noLibrary`
---
--- Additional messages you can handle: `eslint/noConfig`
---
--- ### Monorepo support
---
--- `vscode-eslint-language-server` supports monorepos by default. It will automatically find the config file corresponding to the package you are working on. You can use different configs in different packages.
--- This works without the need of spawning multiple instances of `vscode-eslint-language-server`.
--- You can use a different version of ESLint in each package, but it is recommended to use the same version of ESLint in all packages. The location of the ESLint binary will be determined automatically.
---
--- /!\ When using flat config files, you need to use them across all your packages in your monorepo, as it's a global setting for the server.
local utils = require('utils')
local lsp = vim.lsp
local eslint_config_files = {
'.eslintrc',
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
'eslint.config.js',
'eslint.config.mjs',
'eslint.config.cjs',
'eslint.config.ts',
'eslint.config.mts',
'eslint.config.cts',
}
---@type vim.lsp.Config
return {
cmd = { 'vscode-eslint-language-server', '--stdio' },
filetypes = {
'javascript',
'javascriptreact',
'javascript.jsx',
'typescript',
'typescriptreact',
'typescript.tsx',
'vue',
'svelte',
'astro',
'htmlangular',
},
workspace_required = true,
on_attach = function(client, bufnr)
vim.api.nvim_buf_create_user_command(0, 'LspEslintFixAll', function()
client:request_sync('workspace/executeCommand', {
command = 'eslint.applyAllFixes',
arguments = {
{
uri = vim.uri_from_bufnr(bufnr),
version = lsp.util.buf_versions[bufnr],
},
},
}, nil, bufnr)
end, {})
end,
root_dir = function(bufnr, on_dir)
-- The project root is where the LSP can be started from
-- As stated in the documentation above, this LSP supports monorepos and simple projects.
-- We select then from the project root, which is identified by the presence of a package
-- manager lock file.
local root_markers =
{ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' }
-- Give the root markers equal priority by wrapping them in a table
root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } }
or vim.list_extend(root_markers, { '.git' })
-- We fallback to the current working directory if no project root is found
local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd()
-- We know that the buffer is using ESLint if it has a config file
-- in its directory tree.
--
-- Eslint used to support package.json files as config files, but it doesn't anymore.
-- We keep this for backward compatibility.
local filename = vim.api.nvim_buf_get_name(bufnr)
local eslint_config_files_with_package_json =
utils.insert_package_json(eslint_config_files, 'eslintConfig', filename)
local is_buffer_using_eslint = vim.fs.find(eslint_config_files_with_package_json, {
path = filename,
type = 'file',
limit = 1,
upward = true,
stop = vim.fs.dirname(project_root),
})[1]
if not is_buffer_using_eslint then
return
end
on_dir(project_root)
end,
-- Refer to https://github.com/Microsoft/vscode-eslint#settings-options for documentation.
settings = {
validate = 'on',
packageManager = nil,
useESLintClass = false,
experimental = {
useFlatConfig = false,
},
codeActionOnSave = {
enable = false,
mode = 'all',
},
format = true,
quiet = false,
onIgnoredFiles = 'off',
rulesCustomizations = {},
run = 'onType',
problems = {
shortenToSingleLine = false,
},
-- nodePath configures the directory in which the eslint server should start its node_modules resolution.
-- This path is relative to the workspace folder (root dir) of the server instance.
nodePath = '',
-- use the workspace folder location or the file location (if no workspace folder is open) as the working directory
workingDirectory = { mode = 'auto' },
codeAction = {
disableRuleComment = {
enable = true,
location = 'separateLine',
},
showDocumentation = {
enable = true,
},
},
},
before_init = function(_, config)
-- The "workspaceFolder" is a VSCode concept. It limits how far the
-- server will traverse the file system when locating the ESLint config
-- file (e.g., .eslintrc).
local root_dir = config.root_dir
if root_dir then
config.settings = config.settings or {}
config.settings.workspaceFolder = {
uri = root_dir,
name = vim.fn.fnamemodify(root_dir, ':t'),
}
-- Support flat config files
-- They contain 'config' in the file name
local flat_config_files = vim.tbl_filter(function(file)
return file:match('config')
end, eslint_config_files)
for _, file in ipairs(flat_config_files) do
local found_files = vim.fn.globpath(root_dir, file, true, true)
-- Filter out files inside node_modules
local filtered_files = {}
for _, found_file in ipairs(found_files) do
if string.find(found_file, '[/\\]node_modules[/\\]') == nil then
table.insert(filtered_files, found_file)
end
end
if #filtered_files > 0 then
config.settings.experimental = config.settings.experimental or {}
config.settings.experimental.useFlatConfig = true
break
end
end
-- Support Yarn2 (PnP) projects
local pnp_cjs = root_dir .. '/.pnp.cjs'
local pnp_js = root_dir .. '/.pnp.js'
if vim.uv.fs_stat(pnp_cjs) or vim.uv.fs_stat(pnp_js) then
local cmd = config.cmd
config.cmd = vim.list_extend({ 'yarn', 'exec' }, cmd)
end
end
end,
handlers = {
['eslint/openDoc'] = function(_, result)
if result then
vim.ui.open(result.url)
end
return {}
end,
['eslint/confirmESLintExecution'] = function(_, result)
if not result then
return
end
return 4 -- approved
end,
['eslint/probeFailed'] = function()
vim.notify('[lspconfig] ESLint probe failed.', vim.log.levels.WARN)
return {}
end,
['eslint/noLibrary'] = function()
vim.notify('[lspconfig] Unable to find ESLint library.', vim.log.levels.WARN)
return {}
end,
},
}

36
lsp/html.lua Normal file
View File

@@ -0,0 +1,36 @@
---@brief
---
--- https://github.com/hrsh7th/vscode-langservers-extracted
---
--- `vscode-html-language-server` can be installed via `npm`:
--- ```sh
--- npm i -g vscode-langservers-extracted
--- ```
---
--- Neovim does not currently include built-in snippets. `vscode-html-language-server` only provides completions when snippet support is enabled.
--- To enable completion, install a snippet plugin and add the following override to your language client capabilities during setup.
---
--- The code-formatting feature of the lsp can be controlled with the `provideFormatter` option.
---
--- ```lua
--- --Enable (broadcasting) snippet capability for completion
--- local capabilities = vim.lsp.protocol.make_client_capabilities()
--- capabilities.textDocument.completion.completionItem.snippetSupport = true
---
--- vim.lsp.config('html', {
--- capabilities = capabilities,
--- })
--- ```
---@type vim.lsp.Config
return {
cmd = { 'vscode-html-language-server', '--stdio' },
filetypes = { 'html', 'templ' },
root_markers = { 'package.json', '.git' },
settings = {},
init_options = {
provideFormatter = true,
embeddedLanguages = { css = true, javascript = true },
configurationSection = { 'html', 'css', 'javascript' },
},
}

View File

@@ -1,14 +1,85 @@
---@brief
---
--- https://github.com/luals/lua-language-server
---
--- Lua language server.
---
--- `lua-language-server` can be installed by following the instructions [here](https://luals.github.io/#neovim-install).
---
--- The default `cmd` assumes that the `lua-language-server` binary can be found in `$PATH`.
---
--- If you primarily use `lua-language-server` for Neovim, and want to provide completions,
--- analysis, and location handling for plugins on runtime path, you can use the following
--- settings.
---
--- ```lua
--- vim.lsp.config('lua_ls', {
--- on_init = function(client)
--- if client.workspace_folders then
--- local path = client.workspace_folders[1].name
--- if
--- path ~= vim.fn.stdpath('config')
--- and (vim.uv.fs_stat(path .. '/.luarc.json') or vim.uv.fs_stat(path .. '/.luarc.jsonc'))
--- then
--- return
--- end
--- end
---
--- client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, {
--- runtime = {
--- -- Tell the language server which version of Lua you're using (most
--- -- likely LuaJIT in the case of Neovim)
--- version = 'LuaJIT',
--- -- Tell the language server how to find Lua modules same way as Neovim
--- -- (see `:h lua-module-load`)
--- path = {
--- 'lua/?.lua',
--- 'lua/?/init.lua',
--- },
--- },
--- -- Make the server aware of Neovim runtime files
--- workspace = {
--- checkThirdParty = false,
--- library = {
--- vim.env.VIMRUNTIME
--- -- Depending on the usage, you might want to add additional paths
--- -- here.
--- -- '${3rd}/luv/library'
--- -- '${3rd}/busted/library'
--- }
--- -- Or pull in all of 'runtimepath'.
--- -- NOTE: this is a lot slower and will cause issues when working on
--- -- your own configuration.
--- -- See https://github.com/neovim/nvim-lspconfig/issues/3189
--- -- library = {
--- -- vim.api.nvim_get_runtime_file('', true),
--- -- }
--- }
--- })
--- end,
--- settings = {
--- Lua = {}
--- }
--- })
--- ```
---
--- See `lua-language-server`'s [documentation](https://luals.github.io/wiki/settings/) for an explanation of the above fields:
--- * [Lua.runtime.path](https://luals.github.io/wiki/settings/#runtimepath)
--- * [Lua.workspace.library](https://luals.github.io/wiki/settings/#workspacelibrary)
---
---@type vim.lsp.Config
return {
cmd = { "lua-language-server" },
filetypes = { "lua" },
cmd = { 'lua-language-server' },
filetypes = { 'lua' },
root_markers = {
".luarc.json",
".luarc.jsonc",
".luacheckrc",
".stylua.toml",
"stylua.toml",
"selene.toml",
"selene.yml",
".git",
'.luarc.json',
'.luarc.jsonc',
'.luacheckrc',
'.stylua.toml',
'stylua.toml',
'selene.toml',
'selene.yml',
'.git',
},
}

65
lsp/pyright.lua Normal file
View File

@@ -0,0 +1,65 @@
---@brief
---
--- https://github.com/microsoft/pyright
---
--- `pyright`, a static type checker and language server for python
local function set_python_path(command)
local path = command.args
local clients = vim.lsp.get_clients {
bufnr = vim.api.nvim_get_current_buf(),
name = 'pyright',
}
for _, client in ipairs(clients) do
if client.settings then
client.settings.python = vim.tbl_deep_extend('force', client.settings.python, { pythonPath = path })
else
client.config.settings = vim.tbl_deep_extend('force', client.config.settings, { python = { pythonPath = path } })
end
client:notify('workspace/didChangeConfiguration', { settings = nil })
end
end
---@type vim.lsp.Config
return {
cmd = { 'pyright-langserver', '--stdio' },
filetypes = { 'python' },
root_markers = {
'pyproject.toml',
'setup.py',
'setup.cfg',
'requirements.txt',
'Pipfile',
'pyrightconfig.json',
'.git',
},
settings = {
python = {
analysis = {
autoSearchPaths = true,
useLibraryCodeForTypes = true,
diagnosticMode = 'openFilesOnly',
},
},
},
on_attach = function(client, bufnr)
vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightOrganizeImports', function()
local params = {
command = 'pyright.organizeimports',
arguments = { vim.uri_from_bufnr(bufnr) },
}
-- Using client.request() directly because "pyright.organizeimports" is private
-- (not advertised via capabilities), which client:exec_cmd() refuses to call.
-- https://github.com/neovim/neovim/blob/c333d64663d3b6e0dd9aa440e433d346af4a3d81/runtime/lua/vim/lsp/client.lua#L1024-L1030
client.request('workspace/executeCommand', params, nil, bufnr)
end, {
desc = 'Organize Imports',
})
vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightSetPythonPath', set_python_path, {
desc = 'Reconfigure pyright with the provided python path',
nargs = 1,
complete = 'file',
})
end,
}

19
lsp/sqls.lua Normal file
View File

@@ -0,0 +1,19 @@
---@brief
---
--- https://github.com/sqls-server/sqls
---
--- ```lua
--- vim.lsp.config('sqls', {
--- cmd = {"path/to/command", "-config", "path/to/config.yml"};
--- ...
--- })
--- ```
--- Sqls can be installed via `go install github.com/sqls-server/sqls@latest`. Instructions for compiling Sqls from the source can be found at [sqls-server/sqls](https://github.com/sqls-server/sqls).
---@type vim.lsp.Config
return {
cmd = { 'sqls' },
filetypes = { 'sql', 'mysql' },
root_markers = { 'config.yml' },
settings = {},
}

96
lsp/vtsls.lua Normal file
View File

@@ -0,0 +1,96 @@
---@brief
---
--- https://github.com/yioneko/vtsls
---
--- `vtsls` can be installed with npm:
--- ```sh
--- npm install -g @vtsls/language-server
--- ```
---
--- To configure a TypeScript project, add a
--- [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
--- or [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) to
--- the root of your project.
---
--- ### Vue support
---
--- Since v3.0.0, the Vue language server requires `vtsls` to support TypeScript.
---
--- ```
--- -- If you are using mason.nvim, you can get the ts_plugin_path like this
--- -- For Mason v1,
--- -- local mason_registry = require('mason-registry')
--- -- local vue_language_server_path = mason_registry.get_package('vue-language-server'):get_install_path() .. '/node_modules/@vue/language-server'
--- -- For Mason v2,
--- -- local vue_language_server_path = vim.fn.expand '$MASON/packages' .. '/vue-language-server' .. '/node_modules/@vue/language-server'
--- -- or even
--- -- local vue_language_server_path = vim.fn.stdpath('data') .. "/mason/packages/vue-language-server/node_modules/@vue/language-server"
--- local vue_language_server_path = '/path/to/@vue/language-server'
--- local vue_plugin = {
--- name = '@vue/typescript-plugin',
--- location = vue_language_server_path,
--- languages = { 'vue' },
--- configNamespace = 'typescript',
--- }
--- vim.lsp.config('vtsls', {
--- settings = {
--- vtsls = {
--- tsserver = {
--- globalPlugins = {
--- vue_plugin,
--- },
--- },
--- },
--- },
--- filetypes = { 'typescript', 'javascript', 'javascriptreact', 'typescriptreact', 'vue' },
--- })
--- ```
---
--- - `location` MUST be defined. If the plugin is installed in `node_modules`, `location` can have any value.
--- - `languages` must include vue even if it is listed in filetypes.
--- - `filetypes` is extended here to include Vue SFC.
---
--- You must make sure the Vue language server is setup. For example,
---
--- ```
--- vim.lsp.enable('vue_ls')
--- ```
---
--- See `vue_ls` section and https://github.com/vuejs/language-tools/wiki/Neovim for more information.
---
--- ### Monorepo support
---
--- `vtsls` supports monorepos by default. It will automatically find the `tsconfig.json` or `jsconfig.json` corresponding to the package you are working on.
--- This works without the need of spawning multiple instances of `vtsls`, saving memory.
---
--- It is recommended to use the same version of TypeScript in all packages, and therefore have it available in your workspace root. The location of the TypeScript binary will be determined automatically, but only once.
---@type vim.lsp.Config
return {
cmd = { 'vtsls', '--stdio' },
init_options = {
hostInfo = 'neovim',
},
filetypes = {
'javascript',
'javascriptreact',
'javascript.jsx',
'typescript',
'typescriptreact',
'typescript.tsx',
},
root_dir = function(bufnr, on_dir)
-- The project root is where the LSP can be started from
-- As stated in the documentation above, this LSP supports monorepos and simple projects.
-- We select then from the project root, which is identified by the presence of a package
-- manager lock file.
local root_markers = { 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' }
-- Give the root markers equal priority by wrapping them in a table
root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } }
or vim.list_extend(root_markers, { '.git' })
-- We fallback to the current working directory if no project root is found
local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd()
on_dir(project_root)
end,
}

View File

@@ -47,3 +47,9 @@ vim.api.nvim_create_autocmd({ 'WinLeave', 'InsertEnter' }, {
end
end,
})
-- Removes trailing whitespace before saving
vim.api.nvim_create_autocmd({ 'BufWritePre' }, {
pattern = '*',
command = [[%s/\s\+$//e]],
})

View File

@@ -12,9 +12,9 @@ if vim.env.CONTAINER then
}
end
vim.schedule(function()
vim.opt.clipboard = 'unnamedplus'
end)
-- vim.schedule(function()
-- vim.opt.clipboard = 'unnamedplus'
-- end)
-- TEMP: Check if it helps with edge cases
vim.api.nvim_create_user_command('FixClipboard', function()

View File

@@ -1,54 +1,79 @@
local map = vim.keymap.set
-- Helper functions
local function map(mode, lhs, rhs)
vim.keymap.set(mode, lhs, rhs, { silent = true })
end
map('n', '<leader>q', vim.diagnostic.setloclist)
local function cmd(str)
return '<cmd>' .. str .. '<CR>'
end
map({ 'i', 'c' }, 'jk', '<Esc>')
map('n', '<Esc>', '<cmd>nohlsearch<CR>')
-- QOL
map('i', 'jk', '<Esc>')
map('n', '<Esc>', cmd('nohlsearch'))
map('n', 'q:', '<nop>')
-- Prevent overriding the register
map('n', 'x', '"_x')
vim.keymap.set('n', 'J', 'mzJ`z')
vim.keymap.set('n', 'n', 'nzzzv')
vim.keymap.set('n', 'N', 'Nzzzv')
-- Window Navigation
vim.keymap.set('x', 'J', ":m '>+1<CR>gv=gv")
vim.keymap.set('x', 'K', ":m '<-2<CR>gv=gv")
vim.keymap.set('n', '<leader>s', [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/g<Left><Left><Left>]])
-- Proper registers
map('x', '<leader>p', '"_dP')
map({ 'n', 'x' }, '<leader>y', '"+y')
map('n', '<leader>Y', '"+y$')
map({ 'n', 'x' }, '<leader>d', '"_d')
map({ 'n', 'x' }, 'x', '"_x')
-- Window navigation
map('n', '<C-h>', '<C-w>h')
map('n', '<C-l>', '<C-w>l')
map('n', '<C-j>', '<C-w>j')
map('n', '<C-k>', '<C-w>k')
-- Tab management
map('n', '<Leader>tn', ':tabnew<CR>')
map('n', '<Leader>tc', ':tabclose<CR>')
map('n', '<Leader>tl', ':tabnext<CR>')
map('n', '<Leader>th', ':tabprevious<CR>')
map('n', '<Leader>tm.', ':tabmove +1<CR>')
map('n', '<Leader>tm,', ':tabmove -1<CR>')
map('n', ']t', cmd('tabnext'))
map('n', '[t', cmd('tabprevious'))
map('n', '<leader>tt', cmd('tabnew'))
map('n', '<leader>tn', cmd('tabnew'))
map('n', '<leader>tc', cmd('tabclose'))
map('n', '<Leader>tl', cmd('tabmove +1'))
map('n', '<Leader>th', cmd('tabmove -1'))
for i = 1, 9 do
map('n', string.format('<Leader>%d', i), string.format('%dgt', i))
map('n', string.format('<Leader>t%d', i), string.format('%dgt', i))
end
-- Buffer Management
-- map('n', '<Leader>bl', ':ls<CR>')
-- map('n', '<Leader>bd', ':bdelete<CR>')
-- map('n', ']b', ':bnext<CR>')
-- map('n', '[b', ':bprevious<CR>')
-- map('n', '<Leader>bb', ':b<Space>')
-- map('n', '<Leader>bo', ':bufdo bd|1bd<CR>')
-- Buffer management
map('n', ']b', cmd('bnext'))
map('n', '[b', cmd('bprevious'))
map('n', '<leader>bl', cmd('ls'))
map('n', '<leader>bd', cmd(':bp | bd #'))
map('n', '<leader>bo', cmd('%bd|e#')) -- close all except current
map('n', '<leader>bb', '<C-^>') -- toggle between buffers
-- Terminal
map('n', '<leader>tt', ':TermDefault<CR>')
map('n', '<leader>tr', ':TermRelative<CR>')
map('n', '<leader>ts', ':TermSplit<CR>')
map('n', '<leader>tv', ':TermVSplit<CR>')
-- Terminal mode mappings
local tn = '<C-\\><C-n>'
map('t', '<Esc>', tn)
map('t', 'jk', tn)
map('t', '<C-w>', tn .. '<C-w>')
map('t', '<C-h>', '<cmd>wincmd h<CR>')
map('t', '<C-j>', '<cmd>wincmd j<CR>')
map('t', '<C-k>', '<cmd>wincmd k<CR>')
map('t', '<C-l>', '<cmd>wincmd l<CR>')
map('n', '<leader>xx', cmd('TermDefault'))
map('n', '<leader>xr', cmd('TermRelative'))
map('n', '<leader>xs', cmd('TermSplit'))
map('n', '<leader>xv', cmd('TermVSplit'))
map('t', '<Esc>', '<C-\\><C-n>')
map('t', '<C-w>', '<C-\\><C-n><C-w>')
map('t', '<C-w>c', '<C-\\><C-n>:bd!<CR>')
-- File explorer
vim.keymap.set('n', '<leader>e', '<cmd>NvimTreeToggle<CR>')
vim.keymap.set('n', '<leader>E', '<cmd>NvimTreeOpen<CR>')
map('n', '<leader>e', cmd('NvimTreeToggle'))
map('n', '<leader>E', cmd('NvimTreeOpen'))
-- Diagnostics
map('n', ']d', function()
vim.diagnostic.jump({ count = 1, float = true })
end)
map('n', '[d', function()
vim.diagnostic.jump({ count = -1, float = true })
end)
map('n', '<leader>q', vim.diagnostic.setloclist)
map('n', '<leader>d', vim.diagnostic.open_float)
map('n', '<leader>s', vim.lsp.buf.signature_help)

View File

@@ -71,7 +71,7 @@ vim.opt.mouse = 'a' -- Enable mouse support
vim.opt.ignorecase = true
vim.opt.smartcase = true -- Override ignorecase if search contains upper case chars
vim.opt.inccommand = 'split' -- Live substitution preview
vim.opt.completeopt = { 'menu,menuone,noselect' }
vim.opt.completeopt = { 'fuzzy,menuone,popup,preview,noselect' }
-- Splits
vim.opt.splitright = true

86
lua/custom/tabline.lua Normal file
View File

@@ -0,0 +1,86 @@
-- ~/.config/nvim/lua/custom/tabline.lua
-- Custom Lua tabline with proper modified/unsaved and window count handling
local M = {}
-- Helper to get label for each tab page
local function tab_label(n)
local buflist = vim.fn.tabpagebuflist(n)
local winnr = vim.fn.tabpagewinnr(n)
local bufname = vim.fn.bufname(buflist[winnr])
if bufname == '' then
bufname = '[No Name]'
else
bufname = vim.fn.fnamemodify(bufname, ':t') -- tail only
end
-- Determine window count reliably
local win_count = vim.fn.tabpagewinnr(n, '$')
-- Check if any buffer in the tab is modified
local modified = false
for _, buf in ipairs(buflist) do
if vim.fn.getbufvar(buf, '&modified') == 1 then
modified = true
break
end
end
-- Construct label according to rules:
-- single clean: File
-- single edited: +:File
-- multi clean: 2:File
-- multi edited: 2+:File
if win_count == 1 then
if modified then
bufname = '+:' .. bufname
end
else
local prefix = tostring(win_count)
if modified then
prefix = prefix .. '+'
end
bufname = prefix .. ':' .. bufname
end
-- Truncate overly long names
if #bufname > 20 then
bufname = bufname:sub(1, 17) .. ''
end
return bufname
end
-- Main tabline builder
function M.tabline()
local s = ''
local num_tabs = vim.fn.tabpagenr('$')
for i = 1, num_tabs do
-- Highlight current tab
if i == vim.fn.tabpagenr() then
s = s .. '%#TabLineSel#'
else
s = s .. '%#TabLine#'
end
-- Mouse click target
s = s .. '%' .. i .. 'T'
-- Label
s = s .. ' ' .. tab_label(i) .. ' '
end
-- Fill empty space
s = s .. '%#TabLineFill#%T'
return s
end
function M.setup()
vim.o.showtabline = 1
vim.o.tabline = "%!v:lua.require'custom.tabline'.tabline()"
end
return M

View File

@@ -2,7 +2,6 @@ return {
'triimdev/invero.nvim',
lazy = false,
priority = 1000,
dev = true,
config = function()
vim.api.nvim_create_user_command('ReloadInvero', function()
require('invero').invalidate_cache()
@@ -13,9 +12,20 @@ return {
highlights = function(c, tool)
c.bg_float = tool(152)
return {
ModeMsg = { fg = c.yellow, bg = c.none, bold = true },
WinSeparator = { fg = c.outline, bg = c.base },
StatusLine = { fg = c.outline, bg = c.base },
StatusLineNC = { fg = c.text, bg = c.base, bold = true },
TabLine = { fg = c.muted, bg = c.none },
TabLineSel = { fg = c.text, bg = c.none, bold = true },
TabLineFill = { fg = c.outline_light, bg = c.none },
Pmenu = { fg = c.text, bg = c.surface },
PmenuSel = { fg = c.text, bg = c.accent_light },
QuickFixLine = { fg = c.accent, bg = c.none, bold = true },
-- PmenuSbar = { bg = c.surface },
-- PmenuThumb = { bg = c.outline },
-- PmenuBorder = { fg = c.outline },
}
end,
})

View File

@@ -47,9 +47,8 @@ local function my_on_attach(bufnr)
end
return {
'nvim-tree/nvim-tree.lua',
version = '*',
dev = true,
'https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua',
branch = 'master',
opts = {
on_attach = my_on_attach,
view = { signcolumn = 'no' },

View File

@@ -1,32 +1,346 @@
vim.keymap.set('n', '<leader>d', vim.diagnostic.open_float, { noremap = true, silent = true })
-- language_spec = { treesitter?, lsp?, linter?, formatter?, filetype? }
local language_specs = {
-- Docs / Config
'vim',
'vimdoc',
{ 'markdown', nil, nil, 'prettier' },
'markdown_inline',
'yaml',
'toml',
-- Data
'gitcommit',
'gitignore',
'dockerfile',
'diff',
{ 'json', 'json-lsp' },
{ 'jsonc', 'json-lsp' },
-- Shell / scripting
{ 'bash', 'bash-language-server', 'shellcheck', 'shfmt' },
{ 'lua', 'lua-language-server', 'luacheck', 'stylua' },
'sql',
-- Programming
'c',
'cpp',
'go',
'rust',
'python',
-- { 'python', 'pyright', 'ruff', 'ruff' }, -- install ensurepip
-- Web stack
{ 'html', 'html-lsp' },
{ 'css', 'css-lsp' },
{ 'javascript', { 'vtsls', 'eslint-lsp' }, nil, 'prettier' },
{ 'typescript', { 'vtsls', 'eslint-lsp' }, nil, 'prettier' },
{ nil, { 'vtsls', 'eslint-lsp' }, nil, 'prettier', filetype = 'javascriptreact' },
{ 'tsx', { 'vtsls', 'eslint-lsp' }, nil, 'prettier', filetype = 'typescriptreact' },
}
local function normalize(spec)
if type(spec) == 'string' then
spec = { spec }
end
return {
treesitter = spec[1],
lsp = spec[2],
linter = spec[3],
formatter = spec[4],
filetype = spec.filetype or spec[1],
}
end
local normalized = vim.tbl_map(normalize, language_specs)
local function uniq(tbl)
local seen, out = {}, {}
for _, v in ipairs(tbl) do
if v and not seen[v] then
seen[v] = true
table.insert(out, v)
end
end
return out
end
local function collect(specs)
local ts, lsps, linters, formatters = {}, {}, {}, {}
for _, s in ipairs(specs) do
if s.treesitter then
table.insert(ts, s.treesitter)
end
if s.lsp then
if type(s.lsp) == 'table' then
for _, v in ipairs(s.lsp) do
table.insert(lsps, v)
end
else
table.insert(lsps, s.lsp)
end
end
if s.linter then
linters[s.filetype] = { s.linter }
end
if s.formatter then
formatters[s.filetype] = { s.formatter }
end
end
return {
ts_parsers = uniq(ts),
lsps = uniq(lsps),
linters_by_ft = linters,
formatters_by_ft = formatters,
}
end
local general = collect(normalized)
---------------------------------------------------------------------
-- Mason Install Command
---------------------------------------------------------------------
vim.api.nvim_create_user_command('MasonInstallAll', function()
local registry = require('mason-registry')
local list = vim.list_extend(vim.list_extend({}, general.lsps), {})
for _, ftmap in pairs({ general.linters_by_ft, general.formatters_by_ft }) do
for _, tools in pairs(ftmap) do
vim.list_extend(list, tools)
end
end
list = uniq(list)
local installed = {}
for _, pkg in ipairs(registry.get_installed_packages()) do
installed[pkg.name] = true
end
for _, name in ipairs(list) do
if registry.has_package(name) then
if not installed[name] then
vim.notify('Installing ' .. name, vim.log.levels.INFO)
registry.get_package(name):install()
else
vim.notify('Already installed ' .. name, vim.log.levels.INFO)
end
else
vim.notify('Package not found in registry: ' .. name, vim.log.levels.WARN)
end
end
end, { desc = 'Install all Mason LSPs, linters, and formatters' })
---------------------------------------------------------------------
-- Fetch LSP default configs from nvim-lspconfig
---------------------------------------------------------------------
vim.api.nvim_create_user_command('FetchLspConfigs', function()
local registry = require('mason-registry')
local lspconfig_names = {}
for _, lsp in ipairs(general.lsps) do
if registry.has_package(lsp) then
local pkg = registry.get_package(lsp)
local spec = pkg.spec and pkg.spec.neovim
if spec and spec.lspconfig then
table.insert(lspconfig_names, spec.lspconfig)
else
table.insert(lspconfig_names, lsp)
end
else
table.insert(lspconfig_names, lsp)
end
end
lspconfig_names = uniq(lspconfig_names)
-- base URL same as your original
local base_url = 'https://raw.githubusercontent.com/neovim/nvim-lspconfig/master/lsp/'
-- write to current working directory
local lsp_dir = vim.fs.joinpath(vim.fn.getcwd(), 'lsp')
vim.fn.mkdir(lsp_dir, 'p')
for _, name in ipairs(lspconfig_names) do
local file = vim.fs.joinpath(lsp_dir, name .. '.lua')
if vim.fn.filereadable(file) == 0 then
local url = base_url .. name .. '.lua'
local cmd = string.format('curl -fsSL -o %q %q', file, url)
vim.fn.system(cmd)
if vim.v.shell_error ~= 0 then
vim.notify('Failed to fetch ' .. name .. '.lua', vim.log.levels.ERROR)
vim.fn.delete(file)
else
vim.notify('Fetched ' .. name .. '.lua', vim.log.levels.INFO)
end
else
vim.notify('Skipped existing ' .. name .. '.lua', vim.log.levels.INFO)
end
end
end, { desc = 'Fetch default LSP configs into ./lsp in cwd' })
vim.api.nvim_create_user_command('TreesitterInstallAll', function()
local parsers = require('nvim-treesitter.parsers')
local configs = require('nvim-treesitter.configs')
local langs = configs.get_module('ensure_installed') or {}
if type(langs) == 'string' and langs == 'all' then
vim.notify('Treesitter ensure_installed = "all" not supported here', vim.log.levels.WARN)
return
end
for _, lang in ipairs(langs) do
if not parsers.has_parser(lang) then
vim.cmd('TSInstall ' .. lang)
else
vim.notify('Parser already installed: ' .. lang, vim.log.levels.INFO)
end
end
end, { desc = 'Install all Treesitter parsers defined in ensure_installed' })
vim.api.nvim_create_user_command('General', function()
print(vim.inspect(general))
end, {})
local special_sources = {
lua_ls = 'lua',
eslint = 'eslint',
}
vim.diagnostic.config({
update_in_insert = false,
underline = true,
severity_sort = true,
update_in_insert = true,
virtual_text = {
prefix = '', -- remove annoying ▎ etc
format = function(diagnostic)
if diagnostic.source then
return string.format('[%s] %s', diagnostic.source, diagnostic.message)
local src = diagnostic.source and (special_sources[diagnostic.source] or diagnostic.source)
if src then
return string.format('%s: %s', src, diagnostic.message)
end
return diagnostic.message
end,
},
float = {
border = 'rounded',
source = true, -- show source in floating window too
header = '',
format = function(diagnostic)
local src = diagnostic.source and (special_sources[diagnostic.source] or diagnostic.source)
if src then
return string.format('%s: %s', src, diagnostic.message)
end
return diagnostic.message
end,
},
severity_sort = true,
})
vim.lsp.enable({ 'lua_ls' })
vim.lsp.enable({ 'json_ls' })
-- Override the virtual text diagnostic handler so that the most severe diagnostic is shown first.
local show_handler = vim.diagnostic.handlers.virtual_text.show
assert(show_handler)
local hide_handler = vim.diagnostic.handlers.virtual_text.hide
vim.diagnostic.handlers.virtual_text = {
show = function(ns, bufnr, diagnostics, opts)
table.sort(diagnostics, function(diag1, diag2)
return diag1.severity > diag2.severity
end)
return show_handler(ns, bufnr, diagnostics, opts)
end,
hide = hide_handler,
}
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('minimal_lsp', { clear = true }),
callback = function(ev)
local client = vim.lsp.get_client_by_id(ev.data.client_id)
if not client then
return
end
-- Enable native completion
if client:supports_method('textDocument/completion') then
vim.lsp.completion.enable(true, client.id, ev.buf, { autotrigger = true })
end
end,
})
---------------------------------------------------------------------
-- Optional: annotate completion items with their kind
---------------------------------------------------------------------
vim.lsp.handlers['textDocument/completion'] = function(err, result, ctx, config)
if err or not result then
return
end
for _, item in ipairs(result.items or result) do
if item.kind then
local kind = vim.lsp.protocol.CompletionItemKind[item.kind] or ''
item.menu = '[' .. kind .. ']'
end
end
return vim.lsp.completion._on_completion_result(err, result, ctx, config)
end
---------------------------------------------------------------------
-- Plugins
---------------------------------------------------------------------
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('lsp_attach_timing', { clear = true }),
callback = function(ev)
local client = vim.lsp.get_client_by_id(ev.data.client_id)
if client then
vim.notify(
string.format('LSP attached: %s (buf: %d)', client.name, ev.buf),
vim.log.levels.INFO
)
end
end,
})
local function enable_lsp_with_timing(lsp_name)
local start_time = vim.uv.hrtime()
vim.lsp.enable(lsp_name)
-- Track when the server actually attaches
local group = vim.api.nvim_create_augroup('lsp_timing_' .. lsp_name, { clear = true })
vim.api.nvim_create_autocmd('LspAttach', {
group = group,
callback = function(ev)
local client = vim.lsp.get_client_by_id(ev.data.client_id)
if client and client.name == lsp_name then
local elapsed = (vim.uv.hrtime() - start_time) / 1e6 -- Convert to milliseconds
vim.notify(string.format('%s attached in %.2f ms', lsp_name, elapsed), vim.log.levels.INFO)
vim.api.nvim_del_augroup_by_id(group)
end
end,
})
end
return {
{ 'mason-org/mason.nvim', config = true },
{ 'windwp/nvim-ts-autotag', config = true },
{ 'windwp/nvim-autopairs', event = 'InsertEnter', config = true },
{
'mason-org/mason.nvim',
config = function()
local mason = require('mason')
local registry = require('mason-registry')
mason.setup()
-- { 'saghen/blink.cmp', version = '1.*' },
local lsp_configs = {}
for _, lsp in ipairs(general.lsps) do
if registry.has_package(lsp) then
local pkg = registry.get_package(lsp)
local spec = pkg.spec and pkg.spec.neovim
local lsp_name = (spec and spec.lspconfig) or lsp
table.insert(lsp_configs, lsp_name)
-- Native enable call (Neovim ≥ 0.11)
-- vim.lsp.enable(lsp_name)
enable_lsp_with_timing(lsp_name)
else
vim.notify('Unknown LSP: ' .. lsp, vim.log.levels.WARN)
end
end
vim.notify('Enabled LSPs: ' .. table.concat(lsp_configs, ', '), vim.log.levels.INFO)
end,
},
{
'nvim-treesitter/nvim-treesitter',
build = ':TSUpdate',
@@ -34,52 +348,17 @@ return {
opts = {
highlight = { enable = true },
incremental_selection = { enable = true },
textobjects = { enable = true },
ensure_installed = {
-- Documentation
'vim',
'vimdoc',
'markdown',
'markdown_inline',
-- Data
'gitcommit',
'gitignore',
'dockerfile',
'diff',
'json',
'jsonc',
-- Scripting
'bash',
'lua',
'sql',
-- Programming
'c',
'cpp',
'go',
'rust',
'python',
-- Web
'html',
'css',
'javascript',
'typescript',
'tsx',
},
ensure_installed = general.ts_parsers,
},
},
{
'mfussenegger/nvim-lint',
event = { 'BufReadPre', 'BufNewFile' },
opts = {
linters_by_ft = {
lua = { 'luacheck' },
python = { 'ruff' },
javascript = { 'eslint_d' },
},
},
opts = { linters_by_ft = general.linters_by_ft },
config = function(_, opts)
local lint = require('lint')
lint.linters_by_ft = opts.linters_by_ft
vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, {
group = vim.api.nvim_create_augroup('lint_autocmd', { clear = true }),
callback = function()
@@ -92,37 +371,9 @@ return {
'stevearc/conform.nvim',
event = { 'BufWritePre' },
opts = {
-- Automatically format on save
format_on_save = {
timeout_ms = 500,
lsp_format = 'fallback', -- Use LSP when no formatter is configured
},
-- Formatters per filetype
format_on_save = { timeout_ms = 500, lsp_format = 'fallback' },
default_format_opts = { stop_after_first = true },
formatters_by_ft = {
lua = { 'stylua' },
sh = { 'shfmt' },
swift = { 'swift_format' },
python = { 'isort', 'black' },
json = { 'jq' },
javascript = { 'prettierd', 'prettier' },
javascriptreact = { 'prettierd', 'prettier' },
typescript = { 'prettierd', 'prettier' },
typescriptreact = { 'prettierd', 'prettier' },
},
formatters_by_ft = general.formatters_by_ft,
},
config = function(_, opts)
require('conform').setup(opts)
-- Create a command to format manually
vim.api.nvim_create_user_command('Format', function()
require('conform').format({
async = true,
lsp_format = 'fallback',
})
end, { desc = 'Format current buffer' })
end,
},
}

33
lua/utils.lua Normal file
View File

@@ -0,0 +1,33 @@
local M = {}
--- Appends `new_names` to `root_files` if `field` is found in any such file in any ancestor of `fname`.
---
--- NOTE: this does a "breadth-first" search, so is broken for multi-project workspaces:
--- https://github.com/neovim/nvim-lspconfig/issues/3818#issuecomment-2848836794
---
--- @param root_files string[] List of root-marker files to append to.
--- @param new_names string[] Potential root-marker filenames (e.g. `{ 'package.json', 'package.json5' }`) to inspect for the given `field`.
--- @param field string Field to search for in the given `new_names` files.
--- @param fname string Full path of the current buffer name to start searching upwards from.
function M.root_markers_with_field(root_files, new_names, field, fname)
local path = vim.fn.fnamemodify(fname, ':h')
local found = vim.fs.find(new_names, { path = path, upward = true })
for _, f in ipairs(found or {}) do
-- Match the given `field`.
for line in io.lines(f) do
if line:find(field) then
root_files[#root_files + 1] = vim.fs.basename(f)
break
end
end
end
return root_files
end
function M.insert_package_json(root_files, field, fname)
return M.root_markers_with_field(root_files, { 'package.json', 'package.json5' }, field, fname)
end
return M