Update: Modularizing some features and customizations.

This commit is contained in:
psychhim
2025-10-14 01:15:22 +05:30
parent 15c17dd028
commit 1152ff8229
5 changed files with 261 additions and 223 deletions

230
init.lua
View File

@@ -347,235 +347,19 @@ vim.api.nvim_create_autocmd('TextYankPost', {
require('kanagawa').setup { transparent = true }
vim.cmd [[colorscheme kanagawa]]
-- Custom native-looking tabline that matches Kanagawa theme
-- Custom native-looking tabline
vim.o.showtabline = 1
_G.tab_offset = 1
local max_visible_tabs = 8 -- maximum number of tabs visible at once
function _G.Tabline()
local current_tab = vim.fn.tabpagenr()
local tab_count = vim.fn.tabpagenr '$'
local win_width = vim.o.columns
local tab_width = math.floor(win_width / max_visible_tabs) -- FIXED width
local tabs = {}
for i = 1, tab_count do
local buflist = vim.fn.tabpagebuflist(i)
local winnr = vim.fn.tabpagewinnr(i)
local bufname = vim.fn.bufname(buflist[winnr])
if bufname == '' then
bufname = '[No Name]'
end
local modified = vim.fn.getbufvar(buflist[winnr], '&mod') == 1 and '' or ''
local label = i .. ': ' .. vim.fn.fnamemodify(bufname, ':t') .. modified
-- truncate/pad to fixed tab_width
if #label > tab_width - 2 then
label = label:sub(1, tab_width - 3) .. ''
end
label = label .. string.rep(' ', tab_width - #label)
tabs[i] = (i == current_tab and '%#TabLineSel#' or '%#TabLine#') .. label
end
-- scrolling logic to keep active tab visible
local start_index = _G.tab_offset
if current_tab < start_index then
start_index = current_tab
elseif current_tab >= start_index + max_visible_tabs then
start_index = current_tab - max_visible_tabs + 1
end
_G.tab_offset = start_index
-- select visible tabs
local visible_tabs = {}
for i = start_index, math.min(start_index + max_visible_tabs - 1, tab_count) do
table.insert(visible_tabs, tabs[i])
end
-- scrolling arrows
local left_arrow = start_index > 1 and '< ' or ' '
local right_arrow = start_index + max_visible_tabs - 1 < tab_count and ' >' or ' '
return '%#TabLine#' .. left_arrow .. table.concat(visible_tabs, '') .. right_arrow .. '%#TabLineFill#'
end
vim.o.tabline = '%!v:lua.Tabline()'
-- adjust offset if tabs are closed
vim.api.nvim_create_autocmd({ 'TabClosed' }, {
callback = function()
local tab_count = vim.fn.tabpagenr '$'
if _G.tab_offset > tab_count then
_G.tab_offset = math.max(tab_count - max_visible_tabs + 1, 1)
end
end,
})
local custom_tabline = require 'custom_tabline'
vim.o.tabline = '%!v:lua.require("custom_tabline").tabline()'
-- Auto-create config files for formatters (cross-platform)
local uv = vim.loop
local home = uv.os_homedir()
local os_name = uv.os_uname().sysname
local appdata = os.getenv 'APPDATA' or (home .. '/AppData/Roaming')
require('formatters_auto_config').setup()
-- List of formatter configs
local formatters = {
{
name = 'clang-format',
path = (os_name:match 'Windows' and home .. '\\.clang-format' or home .. '/.clang-format'),
content = [[
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Always
]],
},
{
name = 'prettier',
path = (os_name:match 'Windows' and appdata .. '\\Prettier\\.prettierrc' or home .. '/.prettierrc'),
content = [[
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
}
]],
},
{
name = 'yapf',
path = (os_name:match 'Windows' and home .. '\\.style.yapf' or home .. '/.style.yapf'),
content = [[
[style]
based_on_style = pep8
indent_width = 4
use_tabs = True
]],
},
{
name = 'isort',
path = (os_name:match 'Windows' and home .. '\\.isort.cfg' or home .. '/.isort.cfg'),
content = [[
[settings]
profile = black
force_single_line = true
]],
},
}
-- Helper to create file if it doesn't exist
local function ensure_file(path, content)
if not path then
print 'Invalid path, skipping file creation'
return
end
local stat = uv.fs_stat(path)
if not stat then
-- Make parent directory if needed
local dir = vim.fn.fnamemodify(path, ':h')
if vim.fn.isdirectory(dir) == 0 then
vim.fn.mkdir(dir, 'p') -- recursively create directories
print('Created directory: ' .. dir)
end
-- Write the file
local fd = uv.fs_open(path, 'w', 420) -- 0644
if fd then
uv.fs_write(fd, content, -1)
uv.fs_close(fd)
print('Created file: ' .. path)
else
print('Failed to create file: ' .. path)
end
end
end
-- Loop through all formatter configs
for _, fmt in ipairs(formatters) do
ensure_file(fmt.path, fmt.content)
end
-- Auto-clear messages on most user actions
local clear_msg_grp = vim.api.nvim_create_augroup('AutoClearMessages', { clear = true })
vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'InsertEnter', 'InsertLeave', 'TextChanged', 'TextChangedI' }, {
group = clear_msg_grp,
callback = function()
vim.cmd 'echo ""' -- Clear the message/command line
end,
})
-- Auto-clear messages on pressing ESC
vim.keymap.set('n', '<Esc>', '<Esc><Cmd>echo ""<CR>', { noremap = true, silent = true })
vim.keymap.set('v', '<Esc>', '<Esc><Cmd>echo ""<CR>', { noremap = true, silent = true })
-- Auto-clear command line messages on most user actions and on pressing ESC
require('message_auto_clear').setup()
-- When a file is deleted externally, rename all its buffers to "[file]: file removed"
-- List of buffer names or filetypes to skip (UndoTree, Neo-tree, etc.)
local skip_buffers = { 'undotree', 'neo-tree' }
-- Helper function to determine if a buffer should be skipped in future
local function should_skip(buf)
if not vim.api.nvim_buf_is_valid(buf) then
return true
end
local ft = vim.api.nvim_buf_get_option(buf, 'filetype')
local bufname = vim.api.nvim_buf_get_name(buf)
if bufname == '' then
return true
end
for _, v in ipairs(skip_buffers) do
if ft == v or bufname:match(v) then
return true
end
end
return false
end
vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost' }, {
callback = function()
local current_buf = vim.api.nvim_get_current_buf()
if should_skip(current_buf) then
return
end
local bufname = vim.api.nvim_buf_get_name(current_buf)
-- If this file no longer exists, mark all buffers showing it
if vim.fn.filereadable(bufname) == 0 then
local filename = vim.fn.fnamemodify(bufname, ':t')
local new_name = string.format('[%s]: file removed', filename)
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buf) then
local name = vim.api.nvim_buf_get_name(buf)
if name == bufname and not name:match 'file removed' then
-- Temporarily unlist so renaming works cleanly
vim.api.nvim_buf_set_option(buf, 'buflisted', false)
vim.api.nvim_buf_set_name(buf, new_name)
vim.api.nvim_buf_set_option(buf, 'buflisted', true)
end
end
end
end
end,
})
-- Automatically wipe "file removed" buffers when they are closed
vim.api.nvim_create_autocmd('BufWinLeave', {
callback = function(args)
local buf = args.buf
if should_skip(buf) then
return
end
local bufname = vim.api.nvim_buf_get_name(buf)
-- If the buffer was marked for wipe or name indicates it's deleted
local marked = pcall(vim.api.nvim_buf_get_var, buf, 'marked_for_wipe') and vim.api.nvim_buf_get_var(buf, 'marked_for_wipe')
if marked or (bufname ~= '' and bufname:match 'file removed') then
vim.schedule(function()
if vim.api.nvim_buf_is_valid(buf) then
vim.cmd('bwipeout! ' .. buf)
end
end)
end
end,
})
require 'buffer_deleted'
-- The line beneath this is called `modeline`. See `:help modeline`
-- vim: ts=2 sts=2 sw=2 et

73
lua/buffer_deleted.lua Normal file
View File

@@ -0,0 +1,73 @@
-- When a file is deleted externally, rename all its buffers to "[file]: file removed"
local M = {}
-- List of buffer names or filetypes to skip (UndoTree, Neo-tree, etc.)
local skip_buffers = { 'undotree', 'neo-tree' }
-- Helper function to determine if a buffer should be skipped in future
local function should_skip(buf)
if not vim.api.nvim_buf_is_valid(buf) then
return true
end
local ft = vim.api.nvim_buf_get_option(buf, 'filetype')
local bufname = vim.api.nvim_buf_get_name(buf)
if bufname == '' then
return true
end
for _, v in ipairs(skip_buffers) do
if ft == v or bufname:match(v) then
return true
end
end
return false
end
-- Detect deleted files and rename their buffers
vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost' }, {
callback = function()
local current_buf = vim.api.nvim_get_current_buf()
if should_skip(current_buf) then
return
end
local bufname = vim.api.nvim_buf_get_name(current_buf)
-- If this file no longer exists, mark all buffers showing it
if vim.fn.filereadable(bufname) == 0 then
local filename = vim.fn.fnamemodify(bufname, ':t')
local new_name = string.format('[%s]: file removed', filename)
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buf) then
local name = vim.api.nvim_buf_get_name(buf)
if name == bufname and not name:match 'file removed' then
-- Temporarily unlist so renaming works cleanly
vim.api.nvim_buf_set_option(buf, 'buflisted', false)
vim.api.nvim_buf_set_name(buf, new_name)
vim.api.nvim_buf_set_option(buf, 'buflisted', true)
end
end
end
end
end,
})
-- Automatically wipe "file removed" buffers when they are closed
vim.api.nvim_create_autocmd('BufWinLeave', {
callback = function(args)
local buf = args.buf
if should_skip(buf) then
return
end
local bufname = vim.api.nvim_buf_get_name(buf)
-- If the buffer was marked for wipe or name indicates it's deleted
local marked = pcall(vim.api.nvim_buf_get_var, buf, 'marked_for_wipe') and vim.api.nvim_buf_get_var(buf, 'marked_for_wipe')
if marked or (bufname ~= '' and bufname:match 'file removed') then
vim.schedule(function()
if vim.api.nvim_buf_is_valid(buf) then
vim.cmd('bwipeout! ' .. buf)
end
end)
end
end,
})
return M

66
lua/custom_tabline.lua Normal file
View File

@@ -0,0 +1,66 @@
-- Custom native-looking tabline that matches Kanagawa theme
-- This module creates a fixed-width, scrollable tabline that
local M = {}
local max_visible_tabs = 8 -- maximum number of tabs visible at once
local tab_offset = 1
function M.tabline()
local current_tab = vim.fn.tabpagenr()
local tab_count = vim.fn.tabpagenr '$'
local win_width = vim.o.columns
local tab_width = math.floor(win_width / max_visible_tabs) -- FIXED width
local tabs = {}
for i = 1, tab_count do
local buflist = vim.fn.tabpagebuflist(i)
local winnr = vim.fn.tabpagewinnr(i)
local bufname = vim.fn.bufname(buflist[winnr])
if bufname == '' then
bufname = '[No Name]'
end
local modified = vim.fn.getbufvar(buflist[winnr], '&mod') == 1 and '' or ''
local label = i .. ': ' .. vim.fn.fnamemodify(bufname, ':t') .. modified
-- truncate/pad to fixed tab_width
if #label > tab_width - 2 then
label = label:sub(1, tab_width - 3) .. ''
end
label = label .. string.rep(' ', tab_width - #label)
tabs[i] = (i == current_tab and '%#TabLineSel#' or '%#TabLine#') .. label
end
-- scrolling logic to keep active tab visible
local start_index = tab_offset
if current_tab < start_index then
start_index = current_tab
elseif current_tab >= start_index + max_visible_tabs then
start_index = current_tab - max_visible_tabs + 1
end
tab_offset = start_index
-- select visible tabs
local visible_tabs = {}
for i = start_index, math.min(start_index + max_visible_tabs - 1, tab_count) do
table.insert(visible_tabs, tabs[i])
end
-- scrolling arrows
local left_arrow = start_index > 1 and '< ' or ' '
local right_arrow = start_index + max_visible_tabs - 1 < tab_count and ' >' or ' '
return '%#TabLine#' .. left_arrow .. table.concat(visible_tabs, '') .. right_arrow .. '%#TabLineFill#'
end
-- adjust offset if tabs are closed
vim.api.nvim_create_autocmd({ 'TabClosed' }, {
callback = function()
local tab_count = vim.fn.tabpagenr '$'
if tab_offset > tab_count then
tab_offset = math.max(tab_count - max_visible_tabs + 1, 1)
end
end,
})
return M

View File

@@ -0,0 +1,93 @@
-- Auto-create config files for formatters (cross-platform)
local M = {}
local uv = vim.loop
-- Determine OS paths
local home = uv.os_homedir()
local os_name = uv.os_uname().sysname
local appdata = os.getenv 'APPDATA' or (home .. '/AppData/Roaming')
-- List of formatter configs
local formatters = {
{
name = 'clang-format',
path = (os_name:match 'Windows' and home .. '\\.clang-format' or home .. '/.clang-format'),
content = [[
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Always
]],
},
{
name = 'prettier',
path = (os_name:match 'Windows' and appdata .. '\\Prettier\\.prettierrc' or home .. '/.prettierrc'),
content = [[
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
}
]],
},
{
name = 'yapf',
path = (os_name:match 'Windows' and home .. '\\.style.yapf' or home .. '/.style.yapf'),
content = [[
[style]
based_on_style = pep8
indent_width = 4
use_tabs = True
]],
},
{
name = 'isort',
path = (os_name:match 'Windows' and home .. '\\.isort.cfg' or home .. '/.isort.cfg'),
content = [[
[settings]
profile = black
force_single_line = true
]],
},
}
-- Helper function to create a file if it doesn't exist
local function ensure_file(path, content)
if not path then
print 'Invalid path, skipping file creation'
return
end
local stat = uv.fs_stat(path)
if not stat then
-- Make parent directory if needed
local dir = vim.fn.fnamemodify(path, ':h')
if vim.fn.isdirectory(dir) == 0 then
vim.fn.mkdir(dir, 'p') -- recursively create directories
print('Created directory: ' .. dir)
end
-- Write the file
local fd = uv.fs_open(path, 'w', 420) -- 0644
if fd then
uv.fs_write(fd, content, -1)
uv.fs_close(fd)
print('Created file: ' .. path)
else
print('Failed to create file: ' .. path)
end
end
end
-- Function to run the auto-creation for all formatters
function M.setup()
for _, fmt in ipairs(formatters) do
ensure_file(fmt.path, fmt.content)
end
end
return M

View File

@@ -0,0 +1,22 @@
-- Auto-clear command line messages on most user actions and on pressing ESC
local M = {}
function M.setup()
-- Create an augroup for auto-clearing messages
local clear_msg_grp = vim.api.nvim_create_augroup('AutoClearMessages', { clear = true })
-- Clear messages on common user actions
vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'InsertEnter', 'InsertLeave', 'TextChanged', 'TextChangedI' }, {
group = clear_msg_grp,
callback = function()
vim.cmd 'echo ""' -- Clear the message/command line
end,
})
-- Clear messages when pressing ESC in normal and visual modes
vim.keymap.set('n', '<Esc>', '<Esc><Cmd>echo ""<CR>', { noremap = true, silent = true })
vim.keymap.set('v', '<Esc>', '<Esc><Cmd>echo ""<CR>', { noremap = true, silent = true })
end
return M