feat/pack (#1)
Reviewed-on: #1 Co-authored-by: Tomas Mirchev <contact@tomastm.com> Co-committed-by: Tomas Mirchev <contact@tomastm.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
return {
|
||||
'triimdev/invero.nvim',
|
||||
lazy = false,
|
||||
priority = 1000,
|
||||
config = function()
|
||||
vim.api.nvim_create_user_command('ReloadInvero', function()
|
||||
require('invero').invalidate_cache()
|
||||
vim.cmd('Lazy reload invero.nvim')
|
||||
end, {})
|
||||
|
||||
require('invero').setup({
|
||||
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,
|
||||
})
|
||||
|
||||
vim.o.background = 'light'
|
||||
vim.cmd.colorscheme('invero')
|
||||
end,
|
||||
}
|
||||
@@ -46,102 +46,78 @@ local function my_on_attach(bufnr)
|
||||
vim.keymap.set('n', 'U', api.tree.reload, opts)
|
||||
end
|
||||
|
||||
return {
|
||||
'https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua',
|
||||
branch = 'master',
|
||||
opts = {
|
||||
on_attach = my_on_attach,
|
||||
view = { signcolumn = 'no' },
|
||||
actions = { file_popup = { open_win_config = { border = 'rounded' } } },
|
||||
renderer = {
|
||||
root_folder_label = false,
|
||||
-- root_folder_label = function(path)
|
||||
-- return '-- ' .. vim.fn.fnamemodify(path, ':t') .. ' --'
|
||||
-- end,
|
||||
special_files = {},
|
||||
require('nvim-tree').setup({
|
||||
on_attach = my_on_attach,
|
||||
view = { signcolumn = 'no' },
|
||||
actions = { file_popup = { open_win_config = { border = 'rounded' } } },
|
||||
renderer = {
|
||||
root_folder_label = false,
|
||||
special_files = {},
|
||||
|
||||
highlight_hidden = 'all',
|
||||
highlight_clipboard = 'all',
|
||||
highlight_hidden = 'all',
|
||||
highlight_clipboard = 'all',
|
||||
|
||||
indent_markers = {
|
||||
enable = true,
|
||||
inline_arrows = false,
|
||||
icons = { corner = '│', none = '│', bottom = ' ' },
|
||||
indent_markers = {
|
||||
enable = true,
|
||||
inline_arrows = false,
|
||||
icons = { corner = '│', none = '│', bottom = ' ' },
|
||||
},
|
||||
icons = {
|
||||
bookmarks_placement = 'after',
|
||||
git_placement = 'after',
|
||||
show = {
|
||||
file = false,
|
||||
folder = false,
|
||||
folder_arrow = false,
|
||||
git = true,
|
||||
modified = false,
|
||||
hidden = false,
|
||||
diagnostics = false,
|
||||
bookmarks = true,
|
||||
},
|
||||
icons = {
|
||||
bookmarks_placement = 'after',
|
||||
git_placement = 'after',
|
||||
show = {
|
||||
file = false,
|
||||
folder = false,
|
||||
folder_arrow = false, -- KEEP FALSE
|
||||
git = true,
|
||||
modified = false,
|
||||
hidden = false,
|
||||
diagnostics = false,
|
||||
bookmarks = true,
|
||||
glyphs = {
|
||||
git = {
|
||||
unstaged = '◇',
|
||||
staged = '',
|
||||
unmerged = '',
|
||||
renamed = '',
|
||||
untracked = '',
|
||||
deleted = '',
|
||||
ignored = '',
|
||||
},
|
||||
glyphs = {
|
||||
-- default = '•',
|
||||
default = ' ',
|
||||
symlink = '',
|
||||
bookmark = '',
|
||||
modified = '●',
|
||||
hidden = '',
|
||||
folder = {
|
||||
arrow_closed = '',
|
||||
arrow_open = '',
|
||||
default = '▸',
|
||||
open = '▾',
|
||||
empty = '',
|
||||
empty_open = '',
|
||||
symlink = '',
|
||||
symlink_open = '',
|
||||
},
|
||||
|
||||
git = {
|
||||
unstaged = '◇', -- '✗',
|
||||
staged = '',
|
||||
unmerged = '',
|
||||
renamed = '',
|
||||
untracked = '',
|
||||
deleted = '', -- '',
|
||||
ignored = '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
hijack_cursor = true,
|
||||
prefer_startup_root = true,
|
||||
update_focused_file = {
|
||||
enable = true,
|
||||
update_root = { enable = true, ignore_list = {} },
|
||||
exclude = false,
|
||||
},
|
||||
modified = { enable = true, show_on_dirs = true, show_on_open_dirs = true },
|
||||
filters = {
|
||||
enable = true,
|
||||
git_ignored = true,
|
||||
dotfiles = false,
|
||||
git_clean = false,
|
||||
no_buffer = false,
|
||||
no_bookmark = false,
|
||||
custom = {},
|
||||
exclude = {},
|
||||
},
|
||||
filesystem_watchers = {
|
||||
enable = true,
|
||||
debounce_delay = 50,
|
||||
ignore_dirs = {
|
||||
'/.git',
|
||||
'/.DS_Store',
|
||||
'/build',
|
||||
'/dist',
|
||||
'/public',
|
||||
'/node_modules',
|
||||
'/target',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hijack_cursor = true,
|
||||
prefer_startup_root = true,
|
||||
update_focused_file = {
|
||||
enable = true,
|
||||
update_root = { enable = true, ignore_list = {} },
|
||||
exclude = false,
|
||||
},
|
||||
modified = { enable = true, show_on_dirs = true, show_on_open_dirs = true },
|
||||
filters = {
|
||||
enable = true,
|
||||
git_ignored = true,
|
||||
dotfiles = false,
|
||||
git_clean = false,
|
||||
no_buffer = false,
|
||||
no_bookmark = false,
|
||||
custom = {},
|
||||
exclude = {},
|
||||
},
|
||||
filesystem_watchers = {
|
||||
enable = true,
|
||||
debounce_delay = 50,
|
||||
ignore_dirs = {
|
||||
'/.git',
|
||||
'/.DS_Store',
|
||||
'/build',
|
||||
'/dist',
|
||||
'/public',
|
||||
'/node_modules',
|
||||
'/target',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
323
lua/plugins/finder.lua
Normal file
323
lua/plugins/finder.lua
Normal file
@@ -0,0 +1,323 @@
|
||||
-- Minimal fuzzy finder + content search for Neovim 0.11+
|
||||
-- Optional: `fdfind` or `fd` for file listing, and `rg` (ripgrep) for text search.
|
||||
|
||||
local Fuzzy = {}
|
||||
|
||||
--------------------------------------------------------------------
|
||||
-- 🧩 Helpers
|
||||
--------------------------------------------------------------------
|
||||
|
||||
-- Collect all files (try fdfind/fd first, then globpath)
|
||||
local function get_file_list()
|
||||
local handle = io.popen('fdfind --type f 2>/dev/null || fd --type f 2>/dev/null')
|
||||
if handle then
|
||||
local result = handle:read('*a')
|
||||
handle:close()
|
||||
if result and result ~= '' then
|
||||
return vim.split(result, '\n', { trimempty = true })
|
||||
end
|
||||
end
|
||||
return vim.fn.globpath('.', '**/*', false, true)
|
||||
end
|
||||
|
||||
-- Create floating input + result windows
|
||||
local function open_float(prompt)
|
||||
local input_buf = vim.api.nvim_create_buf(false, true)
|
||||
local result_buf = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
-- mark both buffers as scratch/unlisted
|
||||
for _, b in ipairs({ input_buf, result_buf }) do
|
||||
vim.bo[b].bufhidden = 'wipe'
|
||||
vim.bo[b].buflisted = false
|
||||
vim.bo[b].swapfile = false
|
||||
end
|
||||
vim.bo[input_buf].buftype = 'prompt'
|
||||
vim.bo[result_buf].buftype = 'nofile'
|
||||
|
||||
local width = math.floor(vim.o.columns * 0.7)
|
||||
local height = 20
|
||||
local row = math.floor((vim.o.lines - height) / 2)
|
||||
local col = math.floor((vim.o.columns - width) / 2)
|
||||
|
||||
local input_win = vim.api.nvim_open_win(input_buf, true, {
|
||||
relative = 'editor',
|
||||
row = row,
|
||||
col = col,
|
||||
width = width,
|
||||
height = 1,
|
||||
style = 'minimal',
|
||||
border = 'rounded',
|
||||
})
|
||||
vim.fn.prompt_setprompt(input_buf, prompt)
|
||||
|
||||
local result_win = vim.api.nvim_open_win(result_buf, false, {
|
||||
relative = 'editor',
|
||||
row = row + 2,
|
||||
col = col,
|
||||
width = width,
|
||||
height = height - 2,
|
||||
style = 'minimal',
|
||||
border = 'single',
|
||||
})
|
||||
|
||||
return input_buf, result_buf, input_win, result_win
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------
|
||||
-- 🔵 Highlight current selection
|
||||
--------------------------------------------------------------------
|
||||
function Fuzzy:highlight_selection()
|
||||
if not self.result_buf then
|
||||
return
|
||||
end
|
||||
if not self.ns_id then
|
||||
self.ns_id = vim.api.nvim_create_namespace('FuzzyHighlight')
|
||||
end
|
||||
vim.api.nvim_buf_clear_namespace(self.result_buf, self.ns_id, 0, -1)
|
||||
if self.matches and self.matches[self.cursor] then
|
||||
local rel_cursor = self.cursor - (self.scroll or 0)
|
||||
if rel_cursor >= 1 and rel_cursor <= self.page_size then
|
||||
vim.api.nvim_buf_set_extmark(self.result_buf, self.ns_id, rel_cursor - 1, 0, {
|
||||
end_line = rel_cursor,
|
||||
hl_group = 'Visual',
|
||||
hl_eol = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------
|
||||
-- 🔴 Close all floating windows
|
||||
--------------------------------------------------------------------
|
||||
function Fuzzy.close()
|
||||
local wins = { Fuzzy.input_win, Fuzzy.result_win }
|
||||
for _, win in ipairs(wins) do
|
||||
if win and vim.api.nvim_win_is_valid(win) then
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end
|
||||
end
|
||||
Fuzzy.active = false
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------
|
||||
-- 🟢 File finder
|
||||
--------------------------------------------------------------------
|
||||
function Fuzzy.open()
|
||||
if Fuzzy.active then
|
||||
Fuzzy.close()
|
||||
end
|
||||
Fuzzy.active = true
|
||||
|
||||
Fuzzy.files = get_file_list()
|
||||
Fuzzy.matches = Fuzzy.files
|
||||
Fuzzy.cursor = 1
|
||||
Fuzzy.scroll = 0
|
||||
Fuzzy.page_size = 50
|
||||
|
||||
Fuzzy.input_buf, Fuzzy.result_buf, Fuzzy.input_win, Fuzzy.result_win = open_float('Search: ')
|
||||
|
||||
local function render_results()
|
||||
local total = #Fuzzy.matches
|
||||
if total == 0 then
|
||||
vim.api.nvim_buf_set_lines(Fuzzy.result_buf, 0, -1, false, { '-- no matches --' })
|
||||
return
|
||||
end
|
||||
local start_idx = Fuzzy.scroll + 1
|
||||
local end_idx = math.min(start_idx + Fuzzy.page_size - 1, total)
|
||||
local display = {}
|
||||
for i = start_idx, end_idx do
|
||||
display[#display + 1] = Fuzzy.matches[i]
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(Fuzzy.result_buf, 0, -1, false, display)
|
||||
Fuzzy:highlight_selection()
|
||||
end
|
||||
|
||||
local function update_results(text)
|
||||
if text == '' then
|
||||
Fuzzy.matches = Fuzzy.files
|
||||
else
|
||||
Fuzzy.matches = vim.fn.matchfuzzy(Fuzzy.files, text)
|
||||
end
|
||||
Fuzzy.cursor, Fuzzy.scroll = 1, 0
|
||||
render_results()
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd({ 'TextChangedI', 'TextChangedP' }, {
|
||||
buffer = Fuzzy.input_buf,
|
||||
callback = function()
|
||||
local text = vim.fn.getline('.'):gsub('^Search:%s*', '')
|
||||
update_results(text)
|
||||
end,
|
||||
})
|
||||
|
||||
vim.keymap.set('i', '<C-n>', function()
|
||||
if Fuzzy.cursor < #Fuzzy.matches then
|
||||
Fuzzy.cursor = Fuzzy.cursor + 1
|
||||
if Fuzzy.cursor > Fuzzy.scroll + Fuzzy.page_size then
|
||||
Fuzzy.scroll = Fuzzy.scroll + 1
|
||||
end
|
||||
render_results()
|
||||
end
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.keymap.set('i', '<C-p>', function()
|
||||
if Fuzzy.cursor > 1 then
|
||||
Fuzzy.cursor = Fuzzy.cursor - 1
|
||||
if Fuzzy.cursor <= Fuzzy.scroll then
|
||||
Fuzzy.scroll = math.max(Fuzzy.scroll - 1, 0)
|
||||
end
|
||||
render_results()
|
||||
end
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.keymap.set('i', '<CR>', function()
|
||||
local choice = Fuzzy.matches[Fuzzy.cursor]
|
||||
if choice then
|
||||
Fuzzy.close()
|
||||
vim.cmd.edit(vim.fn.fnameescape(choice))
|
||||
end
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.keymap.set('i', '<Esc>', Fuzzy.close, { buffer = Fuzzy.input_buf })
|
||||
vim.keymap.set('i', '<C-c>', Fuzzy.close, { buffer = Fuzzy.input_buf })
|
||||
vim.keymap.set('n', '<Esc>', Fuzzy.close, { buffer = Fuzzy.input_buf })
|
||||
vim.keymap.set('n', 'q', Fuzzy.close, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.cmd.startinsert()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------
|
||||
-- 🟣 Ripgrep-based content search (scrolling + match highlighting)
|
||||
--------------------------------------------------------------------
|
||||
function Fuzzy.open_grep()
|
||||
if Fuzzy.active then
|
||||
Fuzzy.close()
|
||||
end
|
||||
Fuzzy.active = true
|
||||
|
||||
Fuzzy.input_buf, Fuzzy.result_buf, Fuzzy.input_win, Fuzzy.result_win = open_float('Grep: ')
|
||||
Fuzzy.matches, Fuzzy.cursor, Fuzzy.scroll = {}, 1, 0
|
||||
Fuzzy.page_size = 50
|
||||
Fuzzy.ns_id = vim.api.nvim_create_namespace('FuzzyHighlight')
|
||||
|
||||
local function render_results(query)
|
||||
local total = #Fuzzy.matches
|
||||
if total == 0 then
|
||||
vim.api.nvim_buf_set_lines(Fuzzy.result_buf, 0, -1, false, { '-- no matches --' })
|
||||
return
|
||||
end
|
||||
|
||||
local start_idx = Fuzzy.scroll + 1
|
||||
local end_idx = math.min(start_idx + Fuzzy.page_size - 1, total)
|
||||
local display = {}
|
||||
for i = start_idx, end_idx do
|
||||
display[#display + 1] = Fuzzy.matches[i]
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(Fuzzy.result_buf, 0, -1, false, display)
|
||||
vim.api.nvim_buf_clear_namespace(Fuzzy.result_buf, Fuzzy.ns_id, 0, -1)
|
||||
|
||||
-- highlight selection
|
||||
local rel_cursor = math.min(Fuzzy.cursor - Fuzzy.scroll, #display)
|
||||
vim.api.nvim_buf_set_extmark(Fuzzy.result_buf, Fuzzy.ns_id, rel_cursor - 1, 0, {
|
||||
end_line = rel_cursor,
|
||||
hl_group = 'Visual',
|
||||
hl_eol = true,
|
||||
})
|
||||
|
||||
-- highlight query matches
|
||||
if query and query ~= '' then
|
||||
local pattern = vim.pesc(query)
|
||||
for i, line in ipairs(display) do
|
||||
for s, e in line:gmatch('()' .. pattern .. '()') do
|
||||
vim.api.nvim_buf_set_extmark(Fuzzy.result_buf, Fuzzy.ns_id, i - 1, s - 1, {
|
||||
end_col = e - 1,
|
||||
hl_group = 'Search',
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function run_grep(query)
|
||||
if query == '' then
|
||||
vim.api.nvim_buf_set_lines(Fuzzy.result_buf, 0, -1, false, { '-- type to search --' })
|
||||
return
|
||||
end
|
||||
local handle = io.popen('rg --vimgrep --hidden --smart-case ' .. vim.fn.shellescape(query))
|
||||
if not handle then
|
||||
return
|
||||
end
|
||||
local result = handle:read('*a')
|
||||
handle:close()
|
||||
Fuzzy.matches = vim.split(result, '\n', { trimempty = true })
|
||||
Fuzzy.cursor, Fuzzy.scroll = 1, 0
|
||||
render_results(query)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd({ 'TextChangedI', 'TextChangedP' }, {
|
||||
buffer = Fuzzy.input_buf,
|
||||
callback = function()
|
||||
local text = vim.fn.getline('.'):gsub('^Grep:%s*', '')
|
||||
run_grep(text)
|
||||
end,
|
||||
})
|
||||
|
||||
vim.keymap.set('i', '<C-n>', function()
|
||||
if Fuzzy.cursor < #Fuzzy.matches then
|
||||
Fuzzy.cursor = Fuzzy.cursor + 1
|
||||
if Fuzzy.cursor > Fuzzy.scroll + Fuzzy.page_size then
|
||||
Fuzzy.scroll = Fuzzy.scroll + 1
|
||||
end
|
||||
local query = vim.fn.getline('.'):gsub('^Grep:%s*', '')
|
||||
render_results(query)
|
||||
end
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.keymap.set('i', '<C-p>', function()
|
||||
if Fuzzy.cursor > 1 then
|
||||
Fuzzy.cursor = Fuzzy.cursor - 1
|
||||
if Fuzzy.cursor <= Fuzzy.scroll then
|
||||
Fuzzy.scroll = math.max(Fuzzy.scroll - 1, 0)
|
||||
end
|
||||
local query = vim.fn.getline('.'):gsub('^Grep:%s*', '')
|
||||
render_results(query)
|
||||
end
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.keymap.set('i', '<CR>', function()
|
||||
local line = Fuzzy.matches[Fuzzy.cursor]
|
||||
if line then
|
||||
local parts = vim.split(line, ':')
|
||||
local file, lnum = parts[1], tonumber(parts[2]) or 1
|
||||
Fuzzy.close()
|
||||
vim.cmd.edit(vim.fn.fnameescape(file))
|
||||
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||
end
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.keymap.set('i', '<Esc>', function()
|
||||
Fuzzy.close()
|
||||
end, { buffer = Fuzzy.input_buf })
|
||||
|
||||
vim.cmd.startinsert()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------
|
||||
-- 🧩 Commands & Keymaps
|
||||
--------------------------------------------------------------------
|
||||
vim.api.nvim_create_user_command('FuzzyLive', function()
|
||||
Fuzzy.open()
|
||||
end, {})
|
||||
vim.api.nvim_create_user_command('FuzzyGrep', function()
|
||||
Fuzzy.open_grep()
|
||||
end, {})
|
||||
|
||||
vim.keymap.set('n', '<leader>f', function()
|
||||
vim.cmd.FuzzyLive()
|
||||
end, { desc = 'Open fuzzy file finder' })
|
||||
vim.keymap.set('n', '<leader>g', function()
|
||||
vim.cmd.FuzzyGrep()
|
||||
end, { desc = 'Search file contents with ripgrep' })
|
||||
|
||||
return Fuzzy
|
||||
359
lua/plugins/language-manager.lua
Normal file
359
lua/plugins/language-manager.lua
Normal file
@@ -0,0 +1,359 @@
|
||||
local utils = require('utils')
|
||||
local M = {
|
||||
ts = {},
|
||||
lsp = {},
|
||||
lint = {},
|
||||
format = {},
|
||||
}
|
||||
|
||||
M.group = vim.api.nvim_create_augroup('language-manager', { clear = true })
|
||||
|
||||
local cache_path = vim.fn.stdpath('cache') .. '/language-manager.json'
|
||||
|
||||
local function wrap(item)
|
||||
if type(item) == 'string' then
|
||||
return { item }
|
||||
elseif type(item) == 'table' then
|
||||
return item
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
local function create_set()
|
||||
local seen = {}
|
||||
return function(ref, item)
|
||||
if not seen[ref] then
|
||||
seen[ref] = {}
|
||||
end
|
||||
if item and not seen[ref][item] then
|
||||
seen[ref][item] = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function read_file(path)
|
||||
local f = io.open(path, 'r')
|
||||
if not f then
|
||||
return nil
|
||||
end
|
||||
local content = f:read('*a')
|
||||
f:close()
|
||||
return content
|
||||
end
|
||||
|
||||
local function write_file(path, content)
|
||||
local f = io.open(path, 'w')
|
||||
if not f then
|
||||
return false
|
||||
end
|
||||
f:write(content)
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
|
||||
local function to_json(tbl)
|
||||
local ok, result = pcall(vim.json.encode, tbl)
|
||||
return ok and result or nil
|
||||
end
|
||||
|
||||
local function from_json(str)
|
||||
local ok, result = pcall(vim.json.decode, str)
|
||||
return ok and result or nil
|
||||
end
|
||||
|
||||
local function hash_spec(tbl)
|
||||
local encoded = to_json(tbl) or ''
|
||||
if vim.fn.exists('*sha256') == 1 then
|
||||
return vim.fn.sha256(encoded)
|
||||
else
|
||||
local tmp = vim.fn.tempname()
|
||||
write_file(tmp, encoded)
|
||||
local handle = io.popen('openssl dgst -sha256 ' .. tmp)
|
||||
local result = handle and handle:read('*a') or ''
|
||||
if handle then
|
||||
handle:close()
|
||||
end
|
||||
return result:match('([a-f0-9]+)') or ''
|
||||
end
|
||||
end
|
||||
|
||||
local function save_cache(data)
|
||||
local encoded = to_json(data)
|
||||
if encoded then
|
||||
write_file(cache_path, encoded)
|
||||
end
|
||||
end
|
||||
|
||||
local function load_cache()
|
||||
local raw = read_file(cache_path)
|
||||
if not raw then
|
||||
return nil
|
||||
end
|
||||
return from_json(raw)
|
||||
end
|
||||
|
||||
-- ======== Spec Builder ========
|
||||
|
||||
local function create_spec()
|
||||
local S = {}
|
||||
local unique = create_set()
|
||||
local specs = {}
|
||||
|
||||
function S.set(item, group)
|
||||
specs[group] = item
|
||||
end
|
||||
|
||||
function S.get()
|
||||
return specs
|
||||
end
|
||||
|
||||
function S.add(list, ...)
|
||||
local groups = { ... }
|
||||
local ref = table.concat(groups, '.')
|
||||
local pointer = specs
|
||||
local last_i = #groups
|
||||
|
||||
for i = 1, last_i do
|
||||
local group = groups[i]
|
||||
if i == last_i then
|
||||
for _, item in ipairs(wrap(list)) do
|
||||
if unique(ref, item) then
|
||||
pointer[group] = pointer[group] or {}
|
||||
table.insert(pointer[group], item)
|
||||
end
|
||||
end
|
||||
else
|
||||
pointer[group] = pointer[group] or {}
|
||||
pointer = pointer[group]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return S
|
||||
end
|
||||
|
||||
-- ======== Spec Generation ========
|
||||
|
||||
local function get_mason_registry()
|
||||
vim.cmd.packadd('mason.nvim')
|
||||
require('mason').setup()
|
||||
|
||||
local registry = require('mason-registry')
|
||||
registry.refresh()
|
||||
|
||||
return registry
|
||||
end
|
||||
|
||||
function M.generate_specs(specs_raw)
|
||||
local install_spec = create_spec()
|
||||
local specs = create_spec()
|
||||
local registry = get_mason_registry()
|
||||
|
||||
local lsp_map = {}
|
||||
|
||||
for _, spec in ipairs(specs_raw) do
|
||||
if type(spec) == 'string' then
|
||||
spec = { ft = spec }
|
||||
end
|
||||
|
||||
spec.ts = spec.ts or spec.ft
|
||||
specs.add(spec.ts, 'ts_parsers')
|
||||
|
||||
install_spec.add(spec.lsp, 'code_tools')
|
||||
local resolved_lsps = {}
|
||||
for _, language_server in ipairs(wrap(spec.lsp)) do
|
||||
if registry.has_package(language_server) then
|
||||
local pkg = registry.get_package(language_server)
|
||||
if pkg.spec and pkg.spec.neovim and pkg.spec.neovim.lspconfig then
|
||||
local lspconfig_name = pkg.spec and pkg.spec.neovim and pkg.spec.neovim.lspconfig
|
||||
lsp_map[lspconfig_name] = language_server
|
||||
table.insert(resolved_lsps, lspconfig_name)
|
||||
else
|
||||
print('Package found but not lspconfig name: ' .. language_server)
|
||||
end
|
||||
else
|
||||
print('Package not found: ' .. language_server)
|
||||
end
|
||||
end
|
||||
specs.add(resolved_lsps, 'language_servers')
|
||||
|
||||
install_spec.add(spec.lint, 'code_tools')
|
||||
install_spec.add(spec.format, 'code_tools')
|
||||
for _, ft in ipairs(wrap(spec.ft)) do
|
||||
specs.add(spec.lint, 'linters_by_ft', ft)
|
||||
specs.add(spec.format, 'formatters_by_ft', ft)
|
||||
end
|
||||
end
|
||||
|
||||
local result = specs.get()
|
||||
result.lsp_map = lsp_map
|
||||
return result, install_spec.get()
|
||||
end
|
||||
|
||||
-- ======== Cache ========
|
||||
|
||||
function M.load_specs()
|
||||
local cache = load_cache()
|
||||
if cache then
|
||||
M.general = cache.spec
|
||||
else
|
||||
local specs_raw = require('modules.language-specs').get()
|
||||
M.general, M.mason = M.generate_specs(specs_raw)
|
||||
save_cache({ hash = hash_spec(specs_raw), spec = M.general })
|
||||
end
|
||||
return M.general, M.mason
|
||||
end
|
||||
|
||||
function M.invalidate_cache()
|
||||
local ok = os.remove(cache_path)
|
||||
if ok then
|
||||
vim.notify('Language manager cache invalidated', vim.log.levels.INFO)
|
||||
else
|
||||
vim.notify('No cache to invalidate or failed to delete', vim.log.levels.WARN)
|
||||
end
|
||||
M.general = nil
|
||||
M.mason = nil
|
||||
end
|
||||
|
||||
function M.ts.install()
|
||||
vim.cmd.packadd('nvim-treesitter')
|
||||
local ts_install = require('nvim-treesitter.install').ensure_installed_sync
|
||||
ts_install(M.general.ts_parsers)
|
||||
end
|
||||
|
||||
function M.mason_install()
|
||||
local packages = M.mason.code_tools
|
||||
local registry = get_mason_registry()
|
||||
|
||||
local pending = #packages
|
||||
|
||||
local result = utils.await(function(resolve)
|
||||
for _, name in ipairs(packages) do
|
||||
local pkg = registry.get_package(name)
|
||||
if pkg:is_installed() then
|
||||
print('Mason package already installed: ' .. name)
|
||||
pending = pending - 1
|
||||
if pending == 0 then
|
||||
resolve(true)
|
||||
end
|
||||
else
|
||||
print('Mason package installing: ' .. name)
|
||||
pkg:install({}, function(success, error)
|
||||
if success then
|
||||
print('Mason package installed: ' .. name)
|
||||
else
|
||||
print('Mason package failed: ' .. name)
|
||||
print(' > Error: ' .. vim.inspect(error))
|
||||
end
|
||||
|
||||
pending = pending - 1
|
||||
if pending == 0 then
|
||||
resolve(true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end, 5 * 60 * 1000, 200)
|
||||
|
||||
if not result.ok or pending ~= 0 then
|
||||
print('\n!! >> Exited timeout, possible clean up needed!')
|
||||
print(' > status: ' .. result.ok)
|
||||
print(' > pending: ' .. pending)
|
||||
end
|
||||
end
|
||||
|
||||
-- ======== Public API ========
|
||||
|
||||
function M.install()
|
||||
print('\n> Starting ts parsers install')
|
||||
M.ts.install()
|
||||
|
||||
print('\n> Starting mason install: lsp, lint, format')
|
||||
M.mason_install()
|
||||
end
|
||||
|
||||
function M.ts.setup()
|
||||
require('nvim-treesitter.configs').setup({
|
||||
highlight = { enable = true },
|
||||
incremental_selection = { enable = true },
|
||||
})
|
||||
end
|
||||
|
||||
function M.lsp.setup()
|
||||
for _, lsp_name in ipairs((M.general and M.general.language_servers) or {}) do
|
||||
vim.lsp.enable(lsp_name)
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_create_user_command(0, 'LspInfo', function()
|
||||
vim.cmd('checkhealth vim.lsp')
|
||||
end, {})
|
||||
end
|
||||
|
||||
function M.lint.setup()
|
||||
vim.api.nvim_create_autocmd({ 'BufReadPre', 'BufNewFile' }, {
|
||||
group = M.group,
|
||||
once = true,
|
||||
callback = function()
|
||||
local lint = require('lint')
|
||||
lint.linters_by_ft = M.general.linters_by_ft
|
||||
|
||||
function M.debounce(ms, fn)
|
||||
local timer = vim.uv.new_timer()
|
||||
return function(...)
|
||||
local argv = { ... }
|
||||
timer:start(ms, 0, function()
|
||||
timer:stop()
|
||||
vim.schedule_wrap(fn)(unpack(argv))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function M.lint()
|
||||
if vim.bo.modifiable then
|
||||
lint.try_lint()
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd({ 'BufReadPost', 'InsertLeave', 'TextChanged' }, {
|
||||
group = vim.api.nvim_create_augroup('language-manager.lint', { clear = true }),
|
||||
callback = M.debounce(100, M.lint),
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd('BufEnter', {
|
||||
group = M.group,
|
||||
callback = function(args)
|
||||
local bufnr = args.buf
|
||||
local ft = vim.bo[bufnr].filetype
|
||||
local linters = lint.linters_by_ft[ft]
|
||||
|
||||
if linters then
|
||||
vim.api.nvim_buf_create_user_command(bufnr, 'LintInfo', function()
|
||||
print('Linters for ' .. ft .. ': ' .. table.concat(linters, ', '))
|
||||
end, {})
|
||||
end
|
||||
end,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function M.format.setup()
|
||||
require('conform').setup({
|
||||
formatters_by_ft = (M.general and M.general.formatters_by_ft) or {},
|
||||
default_format_opts = { stop_after_first = true, lsp_format = 'fallback' },
|
||||
format_on_save = { timeout_ms = 500 },
|
||||
})
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
M.load_specs()
|
||||
M.ts.setup()
|
||||
M.lsp.setup()
|
||||
M.lint.setup()
|
||||
M.format.setup()
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,67 +0,0 @@
|
||||
local lm = require('custom.language-manager')
|
||||
local language_specs = {
|
||||
{ ts = { 'yaml', 'toml', 'sql', 'diff', 'dockerfile', 'gitcommit', 'gitignore' } },
|
||||
{ ts = { 'c', 'cpp', 'go', 'rust', 'python' } },
|
||||
|
||||
{ ft = 'markdown', ts = { 'markdown', 'markdown_inline' }, format = 'prettier' },
|
||||
{ ft = 'bash', lsp = 'bash-language-server', lint = 'shellcheck', format = 'shfmt' },
|
||||
{ ft = 'lua', lsp = 'lua-language-server', lint = 'luacheck', format = 'stylua' },
|
||||
{ ft = { 'json', 'jsonc' }, lsp = 'json-lsp' },
|
||||
{ ft = 'html', lsp = 'html-lsp' },
|
||||
{ ft = 'css', lsp = { 'css-lsp', 'tailwindcss-language-server' } },
|
||||
{
|
||||
ft = { 'javascript', 'typescript', 'javascriptreact', 'typescriptreact' },
|
||||
ts = { 'javascript', 'typescript', 'tsx' },
|
||||
lsp = { 'vtsls', 'eslint-lsp' },
|
||||
format = { 'prettierd', 'prettier' },
|
||||
},
|
||||
}
|
||||
|
||||
local general = lm.generate_specs(language_specs)
|
||||
|
||||
return {
|
||||
{ 'windwp/nvim-ts-autotag', config = true },
|
||||
{ 'windwp/nvim-autopairs', event = 'InsertEnter', config = true },
|
||||
{
|
||||
'mason-org/mason.nvim',
|
||||
config = function()
|
||||
require('mason').setup()
|
||||
lm.lsp.enable()
|
||||
end,
|
||||
},
|
||||
{
|
||||
'nvim-treesitter/nvim-treesitter',
|
||||
build = ':TSUpdate',
|
||||
main = 'nvim-treesitter.configs',
|
||||
opts = {
|
||||
highlight = { enable = true },
|
||||
incremental_selection = { enable = true },
|
||||
ensure_installed = general.ts_parsers,
|
||||
},
|
||||
},
|
||||
{
|
||||
'mfussenegger/nvim-lint',
|
||||
event = { 'BufReadPre', 'BufNewFile' },
|
||||
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()
|
||||
lint.try_lint()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
'stevearc/conform.nvim',
|
||||
event = { 'BufWritePre' },
|
||||
opts = {
|
||||
format_on_save = { timeout_ms = 500, lsp_format = 'fallback' },
|
||||
default_format_opts = { stop_after_first = true },
|
||||
formatters_by_ft = general.formatters_by_ft,
|
||||
},
|
||||
},
|
||||
}
|
||||
83
lua/plugins/tabline.lua
Normal file
83
lua/plugins/tabline.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
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.opt.showtabline = 1
|
||||
vim.opt.tabline = "%!v:lua.require'plugins.tabline'.tabline()"
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user