diff --git a/init.lua b/init.lua index 243a33a..9db19c0 100644 --- a/init.lua +++ b/init.lua @@ -1,112 +1,11 @@ --- Map Leader -vim.g.mapleader = ' ' -vim.g.maplocalleader = ' ' - --- Disable built-in plugins -vim.loader.enable() -vim.g.loaded_gzip = 1 -vim.g.loaded_tar = 1 -vim.g.loaded_tarPlugin = 1 -vim.g.loaded_zip = 1 -vim.g.loaded_zipPlugin = 1 -vim.g.loaded_getscript = 1 -vim.g.loaded_getscriptPlugin = 1 -vim.g.loaded_vimball = 1 -vim.g.loaded_vimballPlugin = 1 -vim.g.loaded_matchit = 1 -vim.g.loaded_2html_plugin = 1 -vim.g.loaded_rrhelper = 1 -vim.g.loaded_matchparen = 1 -vim.g.loaded_tutor_mode_plugin = 1 -vim.g.loaded_spellfile_plugin = 1 -vim.g.loaded_logipat = 1 -vim.g.loaded_rplugin = 1 -vim.g.loaded_netrw = 1 -- use nvim-tree instead -vim.g.loaded_netrwPlugin = 1 - --- UI -vim.g.have_nerd_font = true -vim.opt.termguicolors = false - -vim.opt.textwidth = 100 -vim.opt.colorcolumn = '+0' - -vim.opt.signcolumn = 'no' -vim.opt.number = true -vim.opt.relativenumber = true -vim.opt.cursorline = true -vim.opt.ruler = false -vim.opt.winborder = 'rounded' -vim.opt.guicursor = 'n-v-i-c:block' - -vim.opt.laststatus = 3 -vim.opt.statusline = '── %f %h%w%m%r %= [%l,%c-%L] ──' -vim.opt.fillchars = { - stl = '─', - stlnc = '─', - horiz = '─', - horizdown = '─', - horizup = '─', - vert = '│', - vertleft = '│', - vertright = '│', - verthoriz = '│', -} - --- Wrap -vim.opt.wrap = false -vim.opt.linebreak = true -vim.opt.breakindent = true - --- Indent -vim.opt.shiftwidth = 2 -- Number of spaces to use for (auto)indent -vim.opt.tabstop = 2 -- Number of spaces that a in file counts for -vim.opt.softtabstop = 2 -- Number of spaces when pressing in insert mode -vim.opt.expandtab = true -- Use spaces instead of literal tab characters -vim.opt.autoindent = true -- Copy indent from the current line when starting a new one -vim.opt.smartindent = true -- Automatically inserts indents in code blocks (for C-like languages) - --- Scroll and mouse -vim.opt.scrolloff = 10 -vim.opt.sidescrolloff = 5 -vim.opt.mousescroll = 'hor:1,ver:5' -vim.opt.mouse = 'a' -- Enable mouse support - --- Search -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 = { 'fuzzy', 'menuone', 'popup', 'noselect' } - --- Splits -vim.opt.splitright = true -vim.opt.splitbelow = true - --- Persistence -vim.opt.undofile = true -vim.opt.swapfile = false - --- Tweaks -vim.opt.updatetime = 1000 -vim.opt.timeout = true -vim.opt.ttimeout = true -vim.opt.timeoutlen = 500 -vim.opt.ttimeoutlen = 10 - --- Clipboard -if vim.env.CONTAINER then - vim.g.clipboard = { - name = 'osc52', - copy = { - ['+'] = require('vim.ui.clipboard.osc52').copy('+'), - ['*'] = require('vim.ui.clipboard.osc52').copy('*'), - }, - paste = { - ['+'] = require('vim.ui.clipboard.osc52').paste('+'), - ['*'] = require('vim.ui.clipboard.osc52').paste('*'), - }, - } +if #vim.api.nvim_list_uis() == 0 then + print('--> Running neovim in headless mode') + require('setup') + return end --- require('custom.navigation') --- require('custom.tabline').setup() +vim.env.PATH = vim.fn.stdpath('data') .. '/mason/bin:' .. vim.env.PATH + +require('core.options') +require('core.keymaps') +require('core.events') diff --git a/plugin/events.lua b/lua/core/events.lua similarity index 55% rename from plugin/events.lua rename to lua/core/events.lua index ff11e0d..a8299e4 100644 --- a/plugin/events.lua +++ b/lua/core/events.lua @@ -1,6 +1,6 @@ local api = vim.api local au = api.nvim_create_autocmd -local group = api.nvim_create_augroup('triimd.events', { clear = true }) +local group = api.nvim_create_augroup('core.events', { clear = true }) -- Automatically create a scratch buffer if Neovim starts with no files au('VimEnter', { @@ -85,41 +85,6 @@ vim.lsp.handlers['textDocument/completion'] = function(err, result, ctx, config) return vim.lsp.completion._on_completion_result(err, result, ctx, config) end --- Custom terminal -au('TermOpen', { - group = group, - callback = function() - vim.opt_local.number = false - vim.opt_local.relativenumber = false - vim.opt_local.scrolloff = 0 - vim.bo.filetype = 'terminal' - vim.cmd.startinsert() - end, -}) - --- Insert when re-entering a terminal window (after switching back) -au('BufEnter', { - group = group, - pattern = 'term://*', - callback = function() - if vim.bo.buftype == 'terminal' and vim.fn.mode() ~= 'i' then - vim.cmd.startinsert() - end - end, -}) - --- Close all terminal buffers before quitting -au('QuitPre', { - group = group, - callback = function() - for _, buf in ipairs(vim.api.nvim_list_bufs()) do - if vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == 'terminal' then - vim.api.nvim_buf_delete(buf, { force = true }) - end - end - end, -}) - au('InsertEnter', { group = group, once = true, @@ -129,52 +94,14 @@ au('InsertEnter', { end, }) + require('modules.theme') au('UIEnter', { group = group, once = true, callback = function() vim.schedule(function() - -- start ----- - - local commands = { - TermDefault = function() - vim.cmd('terminal') - end, - - TermRelative = function() - local shell = vim.o.shell or 'zsh' - local dir = vim.fn.expand('%:p:h') - vim.cmd(string.format('edit term://%s//%s', dir, shell)) - end, - - TermSplit = function() - vim.cmd('new') - vim.cmd('wincmd J') - vim.api.nvim_win_set_height(0, 12) - vim.wo.winfixheight = true - vim.cmd('term') - end, - - TermVSplit = function() - vim.cmd('vsplit') - vim.cmd('term') - end, - } - - for name, fn in pairs(commands) do - vim.api.nvim_create_user_command(name, fn, {}) - end - - require('triimd.keymaps') - require('triimd.filetree') - require('triimd.syntax') - require('triimd.diagnostics') - - require('plugins.navigation') - require('plugins.tabline').setup() - - - -- end ----- + require('modules.navigation') + require('plugins.language-manager').setup() end) end, }) diff --git a/lua/triimd/keymaps.lua b/lua/core/keymaps.lua similarity index 98% rename from lua/triimd/keymaps.lua rename to lua/core/keymaps.lua index 54ef101..906891a 100644 --- a/lua/triimd/keymaps.lua +++ b/lua/core/keymaps.lua @@ -81,9 +81,8 @@ map('n', '[d', function() end) map('n', 'q', vim.diagnostic.setloclist) map('n', 'd', vim.diagnostic.open_float) -map('n', 's', vim.lsp.buf.signature_help) +map('n', 'K', vim.lsp.buf.hover) map('n', 'gd', vim.lsp.buf.definition) map('n', 'gr', vim.lsp.buf.references) -map('n', 'K', vim.lsp.buf.hover) map('n', '', vim.lsp.buf.signature_help) diff --git a/lua/core/options.lua b/lua/core/options.lua new file mode 100644 index 0000000..4c3d5ce --- /dev/null +++ b/lua/core/options.lua @@ -0,0 +1,109 @@ +-- Map Leader +vim.g.mapleader = ' ' +vim.g.maplocalleader = ' ' + +-- Disable built-in plugins +vim.loader.enable() +vim.g.loaded_gzip = 1 +vim.g.loaded_tar = 1 +vim.g.loaded_tarPlugin = 1 +vim.g.loaded_zip = 1 +vim.g.loaded_zipPlugin = 1 +vim.g.loaded_getscript = 1 +vim.g.loaded_getscriptPlugin = 1 +vim.g.loaded_vimball = 1 +vim.g.loaded_vimballPlugin = 1 +vim.g.loaded_matchit = 1 +vim.g.loaded_2html_plugin = 1 +vim.g.loaded_rrhelper = 1 +vim.g.loaded_matchparen = 1 +vim.g.loaded_tutor_mode_plugin = 1 +vim.g.loaded_spellfile_plugin = 1 +vim.g.loaded_logipat = 1 +vim.g.loaded_rplugin = 1 +vim.g.loaded_netrw = 1 -- use nvim-tree instead +vim.g.loaded_netrwPlugin = 1 + +-- UI +vim.g.have_nerd_font = true +vim.opt.termguicolors = false + +vim.opt.textwidth = 100 +vim.opt.colorcolumn = '+0' + +vim.opt.signcolumn = 'no' +vim.opt.number = true +vim.opt.relativenumber = true +vim.opt.cursorline = true +vim.opt.ruler = false +vim.opt.winborder = 'rounded' +vim.opt.guicursor = 'n-v-i-c:block' + +vim.opt.laststatus = 3 +vim.opt.statusline = '── %f %h%w%m%r %= [%l,%c-%L] ──' +vim.opt.fillchars = { + stl = '─', + stlnc = '─', + horiz = '─', + horizdown = '─', + horizup = '─', + vert = '│', + vertleft = '│', + vertright = '│', + verthoriz = '│', +} + +-- Wrap +vim.opt.wrap = false +vim.opt.linebreak = true +vim.opt.breakindent = true + +-- Indent +vim.opt.shiftwidth = 2 -- Number of spaces to use for (auto)indent +vim.opt.tabstop = 2 -- Number of spaces that a in file counts for +vim.opt.softtabstop = 2 -- Number of spaces when pressing in insert mode +vim.opt.expandtab = true -- Use spaces instead of literal tab characters +vim.opt.autoindent = true -- Copy indent from the current line when starting a new one +vim.opt.smartindent = true -- Automatically inserts indents in code blocks (for C-like languages) + +-- Scroll and mouse +vim.opt.scrolloff = 10 +vim.opt.sidescrolloff = 5 +vim.opt.mousescroll = 'hor:1,ver:5' +vim.opt.mouse = 'a' -- Enable mouse support + +-- Search +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 = { 'fuzzy', 'menuone', 'popup', 'noselect' } + +-- Splits +vim.opt.splitright = true +vim.opt.splitbelow = true + +-- Persistence +vim.opt.undofile = true +vim.opt.swapfile = false + +-- Tweaks +vim.opt.updatetime = 1000 +vim.opt.timeout = true +vim.opt.ttimeout = true +vim.opt.timeoutlen = 500 +vim.opt.ttimeoutlen = 10 + +-- Clipboard +if vim.env.CONTAINER then + vim.g.clipboard = { + name = 'osc52', + copy = { + ['+'] = require('vim.ui.clipboard.osc52').copy('+'), + ['*'] = require('vim.ui.clipboard.osc52').copy('*'), + }, + paste = { + ['+'] = require('vim.ui.clipboard.osc52').paste('+'), + ['*'] = require('vim.ui.clipboard.osc52').paste('*'), + }, + } +end diff --git a/lua/triimd/diagnostics.lua b/lua/modules/diagnostics.lua similarity index 100% rename from lua/triimd/diagnostics.lua rename to lua/modules/diagnostics.lua diff --git a/lua/triimd/syntax.lua b/lua/modules/language-specs.lua similarity index 81% rename from lua/triimd/syntax.lua rename to lua/modules/language-specs.lua index c1a2a46..5aa31b5 100644 --- a/lua/triimd/syntax.lua +++ b/lua/modules/language-specs.lua @@ -1,4 +1,7 @@ -local language_specs = { +local M = {} + +function M.get() + return { { ts = { 'yaml', 'toml', 'sql', 'diff', 'dockerfile', 'gitcommit', 'gitignore' } }, { ts = { 'c', 'cpp', 'go', 'rust', 'python' } }, @@ -15,12 +18,6 @@ local language_specs = { format = { 'prettierd', 'prettier' }, }, } +end -local lm = require('plugins.language-manager') - -lm.generate_specs(language_specs) - -lm.ts.setup() -lm.lsp.setup() -lm.lint.setup() -lm.format.setup() +return M diff --git a/lua/modules/navigation.lua b/lua/modules/navigation.lua new file mode 100644 index 0000000..e291a6f --- /dev/null +++ b/lua/modules/navigation.lua @@ -0,0 +1,2 @@ +require('plugins.finder') +require('plugins.filetree') diff --git a/lua/modules/terminal.lua b/lua/modules/terminal.lua new file mode 100644 index 0000000..c44f88e --- /dev/null +++ b/lua/modules/terminal.lua @@ -0,0 +1,67 @@ +local api = vim.api +local au = api.nvim_create_autocmd +local group = api.nvim_create_augroup('triimd.term', { clear = true }) + +-- Custom terminal +au('TermOpen', { + group = group, + callback = function() + vim.opt_local.number = false + vim.opt_local.relativenumber = false + vim.opt_local.scrolloff = 0 + vim.bo.filetype = 'terminal' + vim.cmd.startinsert() + end, +}) + +-- Insert when re-entering a terminal window (after switching back) +au('BufEnter', { + group = group, + pattern = 'term://*', + callback = function() + if vim.bo.buftype == 'terminal' and vim.fn.mode() ~= 'i' then + vim.cmd.startinsert() + end + end, +}) + +-- Close all terminal buffers before quitting +au('QuitPre', { + group = group, + callback = function() + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == 'terminal' then + vim.api.nvim_buf_delete(buf, { force = true }) + end + end + end, +}) + +local commands = { + TermDefault = function() + vim.cmd('terminal') + end, + + TermRelative = function() + local shell = vim.o.shell or 'zsh' + local dir = vim.fn.expand('%:p:h') + vim.cmd(string.format('edit term://%s//%s', dir, shell)) + end, + + TermSplit = function() + vim.cmd('new') + vim.cmd('wincmd J') + vim.api.nvim_win_set_height(0, 12) + vim.wo.winfixheight = true + vim.cmd('term') + end, + + TermVSplit = function() + vim.cmd('vsplit') + vim.cmd('term') + end, +} + +for name, fn in pairs(commands) do + vim.api.nvim_create_user_command(name, fn, {}) +end diff --git a/lua/modules/theme.lua b/lua/modules/theme.lua new file mode 100644 index 0000000..9ee7cfc --- /dev/null +++ b/lua/modules/theme.lua @@ -0,0 +1,32 @@ + +local function load_theme() + 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 }, + } + end, + }) + + vim.o.background = 'light' + vim.cmd.colorscheme('invero') +end + +vim.api.nvim_create_user_command('ReloadInvero', function() + require('invero').invalidate_cache() + load_theme() +end, {}) + +load_theme() + +require('plugins.tabline').setup() diff --git a/lua/triimd/filetree.lua b/lua/plugins/filetree.lua similarity index 96% rename from lua/triimd/filetree.lua rename to lua/plugins/filetree.lua index 0daf633..bc1ff69 100644 --- a/lua/triimd/filetree.lua +++ b/lua/plugins/filetree.lua @@ -1,7 +1,3 @@ -vim.pack.add({ - { src = 'https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua', version = 'master' }, -}) - local function my_on_attach(bufnr) local api = require('nvim-tree.api') local opts = { buffer = bufnr } diff --git a/lua/plugins/navigation.lua b/lua/plugins/finder.lua similarity index 100% rename from lua/plugins/navigation.lua rename to lua/plugins/finder.lua diff --git a/lua/plugins/language-manager.lua b/lua/plugins/language-manager.lua index a65a6f2..4d8c555 100644 --- a/lua/plugins/language-manager.lua +++ b/lua/plugins/language-manager.lua @@ -1,24 +1,16 @@ ---[[ -local lm = require('language_manager') -lm.install() or .install({'ts_parsers','language_servers','linters','formatters'}) - - treesitter.install specs.ts_parsers - - mason.install mason-registry.get_package(name):install() -lm.generate_specs() or {use_cache=true, only_installed=true} -lm.delete_cache() -lm.enable() ---]] - - -vim.pack.add({ 'https://github.com/mason-org/mason.nvim' }) - -local M = {} -M.lsp = {} -M.ts = {} -M.lint = {} -M.format = {} +local M = { + lsp = {}, + ts = {}, + lint = {}, + format = {}, +} M.group = vim.api.nvim_create_augroup('language-manager', { clear = true }) +-- ======== Helpers ======== + +local cache_path = vim.fn.stdpath('cache') .. '/language-manager.json' + local function wrap(item) if type(item) == 'string' then return { item } @@ -31,7 +23,7 @@ end local function create_set() local seen = {} - return function(ref, item) -- unique() + return function(ref, item) if not seen[ref] then seen[ref] = {} end @@ -43,6 +35,69 @@ local function create_set() 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() @@ -64,20 +119,15 @@ local function create_spec() 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 - if not pointer[group] then - pointer[group] = {} - end + pointer[group] = pointer[group] or {} table.insert(pointer[group], item) end end else - if not pointer[group] then - pointer[group] = {} - end + pointer[group] = pointer[group] or {} pointer = pointer[group] end end @@ -86,47 +136,189 @@ local function create_spec() return S end +-- ======== Spec Generation ======== + function M.generate_specs(specs_raw) + local install_spec = create_spec() local specs = create_spec() + -- ensure Mason is available + vim.cmd.packadd('mason.nvim') + require('mason').setup() + local registry = require('mason-registry') + + local lsp_map = {} + for _, spec in ipairs(specs_raw) do - -- -> { ft = } if type(spec) == 'string' then spec = { ft = spec } end - -- Filetype = TS Parser - if not spec.ts then - spec.ts = spec.ft - end - + spec.ts = spec.ts or spec.ft specs.add(spec.ts, 'ts_parsers') - specs.add(spec.lsp, 'language_servers') - for _, filetype in ipairs(wrap(spec.ft)) do - specs.add(spec.lint, 'linters_by_ft', filetype) - specs.add(spec.format, 'formatters_by_ft', filetype) + -- resolve LSP name using Mason registry + install_spec.add(spec.lsp, 'language_servers') + local resolved_lsps = {} + for _, lsp in ipairs(wrap(spec.lsp)) do + if registry.has_package(lsp) then + local pkg = registry.get_package(lsp) + local lspconfig = pkg.spec and pkg.spec.neovim and pkg.spec.neovim.lspconfig or lsp + table.insert(resolved_lsps, lspconfig) + lsp_map[lspconfig] = lsp + else + vim.notify('Unknown LSP: ' .. lsp, vim.log.levels.WARN) + 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 - M.general = specs.get() - return M.general + local result = specs.get() + result.lsp_map = lsp_map + return result, install_spec.get() +end + +-- ======== Cache Interface ======== + +function M.load_or_generate(specs_raw) + local hash = hash_spec(specs_raw) + local cache = load_cache() + if cache and cache.hash == hash then + M.general = cache.spec + else + M.general, M.mason = M.generate_specs(specs_raw) + save_cache({ hash = hash, spec = M.general }) + end + return M.general, M.mason +end + +function M.load_specs() + local cache = load_cache() + if cache then + M.general = cache.spec + else + local specs_raw = require('modules.language-specs').get() + local hash = hash_spec(specs_raw) + M.general, M.mason = M.generate_specs(specs_raw) + save_cache({ hash = hash, 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 + +-- ======== Setup Functions ======== + +local function on_ts_installed(parsers, cb) + local info = require('nvim-treesitter.info') + local timer = vim.loop.new_timer() + timer:start(0, 500, vim.schedule_wrap(function() + for _, lang in ipairs(parsers) do + if not vim.tbl_contains(info.installed_parsers(), lang) then + return + end + end + timer:stop() + timer:close() + cb() + end)) +end + +function M.ts.install() + local install = require('nvim-treesitter.install') + local parsers = M.general.ts_parsers or {} + install.ensure_installed(parsers) -- async + return parsers +end + +-- ======== Orchestration ======== + +function M.install() + local parsers = M.ts.install() + on_ts_installed(parsers, function() + M.mason_install() + end) +end + +local function ensure_registry_ready() + local registry = require('mason-registry') + if not registry.is_installed() then + registry.update() + vim.wait(10000, function() + return registry.is_installed() + end, 200) + end + return registry +end + +function M.mason_install() + vim.cmd.packadd('mason.nvim') + require('mason').setup() + local registry = ensure_registry_ready() + local list = vim.list_extend(M.mason.language_servers, M.mason.code_tools) + print(vim.inspect(list)) + + local function install_and_wait(name) + local pkg = registry.get_package(name) + if pkg:is_installed() then + vim.notify('Already installed ' .. name, vim.log.levels.INFO) + return + end + + vim.notify('Installing ' .. name, vim.log.levels.INFO) + local done = false + pkg:install():once('closed', function() + done = true + end) + + local ok = vim.wait(60 * 60 * 1000, function() + return done or pkg:is_installed() + end, 200) + + if not ok or not pkg:is_installed() then + vim.notify('Install failed: ' .. name, vim.log.levels.ERROR) + else + vim.notify('Installed ' .. name, vim.log.levels.INFO) + end + end + + for _, name in ipairs(list) do + if registry.has_package(name) then + install_and_wait(name) + else + vim.notify('Package not found in registry: ' .. name, vim.log.levels.WARN) + end + end + + -- force event loop to process any pending Mason async teardown + local settled = false + vim.defer_fn(function() + settled = true + end, 3000) + vim.wait(10 * 1000, function() + return settled + end, 100) end function M.lsp.setup() - require('mason').setup() - local registry = require('mason-registry') - - for _, lsp in ipairs(M.general.language_servers) 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 - - vim.lsp.enable(lsp_name) - else - vim.notify('Unknown LSP: ' .. lsp, vim.log.levels.WARN) - end + for _, lsp_name in ipairs(M.general.language_servers or {}) do + vim.lsp.enable(lsp_name) end end @@ -142,7 +334,7 @@ function M.lint.setup() group = M.group, callback = function() local lint = require('lint') - lint.linters_by_ft = M.general.linters_by_ft + lint.linters_by_ft = M.general.linters_by_ft or {} vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { group = vim.api.nvim_create_augroup('language-manager.lint', { clear = true }), callback = function() @@ -161,10 +353,18 @@ function M.format.setup() require('conform').setup({ format_on_save = { timeout_ms = 500, lsp_format = 'fallback' }, default_format_opts = { stop_after_first = true }, - formatters_by_ft = M.general.formatters_by_ft, + formatters_by_ft = M.general.formatters_by_ft or {}, }) end, }) end +function M.setup() + M.load_specs() + M.ts.setup() + M.lsp.setup() + M.lint.setup() + M.format.setup() +end + return M diff --git a/lua/plugins/tabline.lua b/lua/plugins/tabline.lua index 129273e..edefc5f 100644 --- a/lua/plugins/tabline.lua +++ b/lua/plugins/tabline.lua @@ -76,8 +76,8 @@ function M.tabline() end function M.setup() - vim.o.showtabline = 1 - vim.o.tabline = "%!v:lua.require'plugins.tabline'.tabline()" + vim.opt.showtabline = 1 + vim.opt.tabline = "%!v:lua.require'plugins.tabline'.tabline()" end return M diff --git a/lua/setup/init.lua b/lua/setup/init.lua index 8aa769a..5ced9a5 100644 --- a/lua/setup/init.lua +++ b/lua/setup/init.lua @@ -1 +1,91 @@ -vim.pack.add({ 'https://github.com/mason-org/mason.nvim' }) +local M = {} + +function M.install_pm() + local path = vim.fn.stdpath('data') .. '/site/pack/paqs/opt/paq-nvim' + if not vim.uv.fs_stat(path) then + local repo = 'https://github.com/savq/paq-nvim.git' + local cmd = { 'git', 'clone', '--depth=1', repo, path } + vim.system(cmd):wait() + print("Installed: paq") + end +end + +function M.install_packages() + print("Installing packages...") + vim.cmd.packadd('paq-nvim') + local packages = require('setup.packages').get() + local paq = require('paq') + + paq(packages) + + -- capture list of jobs before starting + local install_list = paq.install() + + -- if nothing to install, skip wait + if not install_list or vim.tbl_isempty(install_list) then + vim.cmd('packloadall') + vim.cmd('silent! helptags ALL') + return + end + + local done = false + vim.api.nvim_create_autocmd('User', { + pattern = 'PaqDoneInstall', + once = true, + callback = function() + vim.cmd('packloadall') + vim.cmd('silent! helptags ALL') + done = true + end, + }) + + -- wait until done or timeout + vim.wait(300000, function() + return done + end, 200) +end + + + +function M.install_languages() + local lm = require('plugins.language-manager') + lm.invalidate_cache() + lm.load_specs() + lm.install() +end + +vim.api.nvim_create_user_command('InstallAll', function() + M.install_pm() + M.install_packages() + M.install_languages() +end, {}) + +vim.api.nvim_create_user_command('FetchLspConfigs', function() + local base_url = 'https://raw.githubusercontent.com/neovim/nvim-lspconfig/master/lsp/' + + local lm = require('plugins.language-manager') + lm.invalidate_cache() + local general = lm.load_specs() + + local lsp_dir = vim.fs.joinpath(vim.fn.getcwd(), 'lsp') + vim.fn.mkdir(lsp_dir, 'p') + + for _, name in ipairs(general.language_servers) 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' }) + +return M diff --git a/lua/setup/lm-cmds.lua b/lua/setup/lm-cmds.lua deleted file mode 100644 index 868cc2f..0000000 --- a/lua/setup/lm-cmds.lua +++ /dev/null @@ -1,98 +0,0 @@ -local general = {} ---------------------------------------------------------------------- --- 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' }) diff --git a/lua/setup/packages.lua b/lua/setup/packages.lua new file mode 100644 index 0000000..ec36031 --- /dev/null +++ b/lua/setup/packages.lua @@ -0,0 +1,20 @@ +local M = {} + +function M.get() + return { + { 'savq/paq-nvim', opt = true }, + { 'https://github.com/mason-org/mason.nvim', opt = true }, + + { 'https://github.com/triimd/invero.nvim' }, + { 'https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua', version = 'master' }, + + { 'https://github.com/windwp/nvim-ts-autotag' }, + { 'https://github.com/windwp/nvim-autopairs' }, + + { 'https://github.com/nvim-treesitter/nvim-treesitter', version = 'master' }, + { 'https://github.com/mfussenegger/nvim-lint' }, + { 'https://github.com/stevearc/conform.nvim' }, + } +end + +return M diff --git a/nvim-pack-lock.json b/nvim-pack-lock.json index 2f2f13b..951e18a 100644 --- a/nvim-pack-lock.json +++ b/nvim-pack-lock.json @@ -1,43 +1,7 @@ { "plugins": { - "conform.nvim": { - "rev": "9fd3d5e", - "src": "https://github.com/stevearc/conform.nvim" - }, - "invero.nvim": { - "rev": "6acdefa", - "src": "https://github.com/triimd/invero.nvim" - }, - "mason.nvim": { - "rev": "ad7146a", - "src": "https://github.com/mason-org/mason.nvim" - }, - "nvim-autopairs": { - "rev": "7a2c97c", - "src": "https://github.com/windwp/nvim-autopairs" - }, - "nvim-lint": { - "rev": "9da1fb9", - "src": "https://github.com/mfussenegger/nvim-lint" - }, - "nvim-tree": { - "rev": "7c0f7e9", - "src": "https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua", - "version": "'master'" - }, "nvim-tree.lua": { - "rev": "7c0f7e9", - "src": "https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua", "version": "'master'" - }, - "nvim-treesitter": { - "rev": "42fc28ba", - "src": "https://github.com/nvim-treesitter/nvim-treesitter", - "version": "'master'" - }, - "nvim-ts-autotag": { - "rev": "c4ca798", - "src": "https://github.com/windwp/nvim-ts-autotag" } } } \ No newline at end of file diff --git a/plugin/colorscheme.lua b/plugin/colorscheme.lua deleted file mode 100644 index 9eb9dcd..0000000 --- a/plugin/colorscheme.lua +++ /dev/null @@ -1,29 +0,0 @@ -vim.pack.add({ 'https://github.com/triimd/invero.nvim' }) - -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 }, - } - end, -}) - -vim.o.background = 'light' -vim.cmd.colorscheme('invero') - -vim.api.nvim_create_user_command('ReloadInvero', function() - require('invero').invalidate_cache() - package.loaded['invero'] = nil - require('invero') - vim.cmd.colorscheme('invero') -end, {}) diff --git a/plugin/packages.lua b/plugin/packages.lua deleted file mode 100644 index ced32bd..0000000 --- a/plugin/packages.lua +++ /dev/null @@ -1,8 +0,0 @@ -vim.pack.add({ - 'https://github.com/windwp/nvim-ts-autotag', - 'https://github.com/windwp/nvim-autopairs', - - { src = 'https://github.com/nvim-treesitter/nvim-treesitter', version = 'master' }, - 'https://github.com/mfussenegger/nvim-lint', - 'https://github.com/stevearc/conform.nvim', -}) diff --git a/scripts/install_all b/scripts/install_all new file mode 100755 index 0000000..93357b0 --- /dev/null +++ b/scripts/install_all @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +nvim --headless '+lua =require("setup").install_pm()' +qa +nvim --headless '+lua =require("setup").install_packages()' +qa +nvim --headless '+lua =require("setup").install_languages()' +qa