added modularity: separated files for keymaps, telescope. added undotree plugin. small features like relative line numbers.

This commit is contained in:
psychhim
2025-10-06 16:38:18 +05:30
parent 01d86ce310
commit 289772ff82
8 changed files with 719 additions and 679 deletions

527
init.lua
View File

@@ -86,6 +86,10 @@ require('lazy').setup({
lsp_format = 'fallback', lsp_format = 'fallback',
}, },
}, },
-- format without saving
vim.keymap.set('n', '<leader>f', function()
require('conform').format { async = true }
end, { desc = 'Format buffer' }),
}, },
-- Install formatters automatically -- Install formatters automatically
@@ -125,7 +129,7 @@ require('lazy').setup({
}, },
-- Useful plugin to show you pending keybinds. -- Useful plugin to show you pending keybinds.
{ 'folke/which-key.nvim', opts = {} }, { 'folke/which-key.nvim', opts = {} },
{ {
-- Adds git related signs to the gutter, as well as utilities for managing changes -- Adds git related signs to the gutter, as well as utilities for managing changes
'lewis6991/gitsigns.nvim', 'lewis6991/gitsigns.nvim',
@@ -227,31 +231,8 @@ require('lazy').setup({
main = 'ibl', main = 'ibl',
opts = {}, opts = {},
}, },
-- "gc" to comment visual regions/lines -- "gc" to comment visual regions/lines
{ 'numToStr/Comment.nvim', opts = {} }, { 'numToStr/Comment.nvim', opts = {} },
-- Fuzzy Finder (files, lsp, etc)
{
'nvim-telescope/telescope.nvim',
branch = '0.1.x',
dependencies = {
'nvim-lua/plenary.nvim',
-- Fuzzy Finder Algorithm which requires local dependencies to be built.
-- Only load if `make` is available. Make sure you have the system
-- requirements installed.
{
'nvim-telescope/telescope-fzf-native.nvim',
-- NOTE: If you are having trouble with this installation,
-- refer to the README for telescope-fzf-native for more instructions.
build = 'make',
cond = function()
return vim.fn.executable 'make' == 1
end,
},
},
},
{ {
-- Highlight, edit, and navigate code -- Highlight, edit, and navigate code
'nvim-treesitter/nvim-treesitter', 'nvim-treesitter/nvim-treesitter',
@@ -282,21 +263,28 @@ require('lazy').setup({
{ import = 'custom.plugins' }, { import = 'custom.plugins' },
}, {}) }, {})
-- custom/keymaps.lua file
require 'keymaps'
-- [[ Setting options ]] -- [[ Setting options ]]
-- See `:help vim.o` -- See `:help vim.o`
-- NOTE: You can change these options as you wish!
-- Set highlight on search -- Set highlight on search
vim.o.hlsearch = false vim.o.hlsearch = false
-- Make line numbers default -- Make line numbers default
vim.wo.number = true vim.opt.number = true
-- Relative line number
vim.opt.relativenumber = true
-- Enable mouse mode -- Enable mouse mode
vim.o.mouse = '' vim.o.mouse = ''
-- Always keep a distance of the cursor to the top and bottom of the screen
vim.opt.scrolloff = 8
-- Sync clipboard between OS and Neovim. -- Sync clipboard between OS and Neovim.
-- Remove this option if you want your OS clipboard to remain independent.
-- See `:help 'clipboard'` -- See `:help 'clipboard'`
-- vim.o.clipboard = 'unnamedplus' -- vim.o.clipboard = 'unnamedplus'
@@ -324,246 +312,12 @@ vim.o.completeopt = 'menuone,noselect'
vim.o.termguicolors = true vim.o.termguicolors = true
-- Tabs and indentation -- Tabs and indentation
vim.o.expandtab = false -- use spaces instead of tabs vim.o.expandtab = false -- use spaces instead of tabs
vim.o.shiftwidth = 4 -- number of spaces for autoindent vim.o.shiftwidth = 4 -- number of spaces for autoindent
vim.o.softtabstop = 0 -- number of spaces per Tab in insert mode vim.o.softtabstop = 0 -- number of spaces per Tab in insert mode
vim.o.tabstop = 4 -- number of spaces a Tab counts for vim.o.tabstop = 4 -- number of spaces a Tab counts for
vim.o.smartindent = true -- auto-indent new lines vim.o.smartindent = true -- auto-indent new lines
-- [[ Basic Keymaps ]]
-- Keymaps for better default experience
-- See `:help vim.keymap.set()`
vim.keymap.set({ 'n', 'v' }, '<Space>', '<Nop>', { silent = true })
-- Remap for dealing with word wrap
vim.keymap.set('n', 'k', "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true })
vim.keymap.set('n', 'j', "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true })
-- Diagnostic keymaps
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, { desc = 'Go to previous diagnostic message' })
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, { desc = 'Go to next diagnostic message' })
vim.keymap.set('n', '<leader>de', vim.diagnostic.open_float, { desc = 'Diagnostics: floating message' })
vim.keymap.set('n', '<leader>dq', vim.diagnostic.setloclist, { desc = 'Diagnostics: location list' })
-- [[ Custom keymaps ]]
-- Neo-tree sync to current directory & toggle
vim.keymap.set('n', '<leader>n', '<Cmd>cd %:p:h | Neotree toggle float<CR>')
-- Terminal open in new tab
vim.keymap.set('n', '<leader>t', '<Cmd>tabnew +term<CR>i')
-- Create an empty buffer in a new tab
vim.keymap.set('n', '<Leader>e', function()
vim.cmd 'tabnew' -- create a new tab
vim.cmd 'enew' -- create a new empty buffer in it
end, { noremap = true, silent = true })
-- Horizontal split with new empty buffer below
vim.keymap.set('n', '<leader>sv', function()
vim.cmd 'split' -- create horizontal split (above by default)
vim.cmd 'wincmd j' -- move to the new split below
vim.cmd 'enew' -- open new empty buffer
end, { desc = 'New buffer in horizontal split (below)' })
-- Vertical split with new empty buffer to the right
vim.keymap.set('n', '<leader>sh', function()
vim.cmd 'vsplit' -- create vertical split (left by default)
vim.cmd 'wincmd l' -- move to the new split to the right
vim.cmd 'enew' -- open new empty buffer
end, { desc = 'New buffer in vertical split (right)' })
-- Save current buffer (asks for filename if new/unsaved)
vim.keymap.set('n', '<leader>w', function()
if vim.api.nvim_buf_get_name(0) == '' then
-- Ask user for a filename
local filename = vim.fn.input('Save as: ', '', 'file')
if filename ~= '' then
vim.cmd('saveas ' .. vim.fn.fnameescape(filename))
else
print 'Save cancelled'
end
else
vim.cmd 'w'
end
end, { desc = 'Save buffer (prompt if new file)' })
-- Close current window (asks if buffer is unsaved)
vim.keymap.set('n', '<leader>q', function()
if vim.bo.modified then
local choice = vim.fn.input 'Buffer modified! Save (y), Discard (n), Cancel (any other key)? '
if choice:lower() == 'y' then
if vim.api.nvim_buf_get_name(0) == '' then
local filename = vim.fn.input('Save as: ', '', 'file')
if filename ~= '' then
vim.cmd('saveas ' .. vim.fn.fnameescape(filename))
vim.cmd 'q'
else
print 'Save cancelled'
end
else
vim.cmd 'wq'
end
elseif choice:lower() == 'n' then
vim.cmd 'q!'
else
print 'Quit cancelled'
end
else
vim.cmd 'q'
end
end, { desc = 'Close buffer (prompt if modified)' })
-- Save changes and close current window (asks for filename if new/unsaved)
vim.keymap.set('n', '<leader>qy', function()
if vim.api.nvim_buf_get_name(0) == '' then
-- Ask user for a filename
local filename = vim.fn.input('Save as: ', '', 'file')
if filename ~= '' then
vim.cmd('saveas ' .. vim.fn.fnameescape(filename))
vim.cmd 'q'
else
print 'Save cancelled'
end
else
vim.cmd 'wq'
end
end, { desc = 'Save & quit (prompt if new file)' })
-- Discard changes and Close current window
vim.keymap.set('n', '<leader>qn', '<Cmd>q!<CR>')
-- Switch below/right split windows
vim.keymap.set('n', '<leader><Tab>', '<C-W><C-W>')
-- Switch above/left split windows
vim.keymap.set('n', '<Tab>', '<C-W>W')
-- Select all
vim.keymap.set('n', '<leader>ll', 'ggVG')
-- Select all and copy to clipboard
vim.keymap.set('n', '<leader>lY', 'ggVG"+y')
-- Copy to clipboard a single line
vim.keymap.set('n', 'Y', '"+yy')
-- Copy to clipboard selected text in Visual mode
vim.keymap.set('v', 'Y', '"+y')
-- Paste from clipboard
vim.keymap.set('n', '<leader>p', '"+p')
-- Redo
vim.keymap.set('n', 'U', '<C-r>')
-- Smart Open current buffers for Telescope (switch to already open buffer)
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local builtin = require 'telescope.builtin'
local function smart_open_buffer()
builtin.buffers {
attach_mappings = function(_, map)
local function open_selected(prompt_bufnr)
local entry = action_state.get_selected_entry()
if not entry then
return
end
actions.close(prompt_bufnr)
local bufname = vim.api.nvim_buf_get_name(entry.bufnr)
if bufname == '' then
return
end
-- Check all windows in current tab
local current_tab = vim.api.nvim_get_current_tabpage()
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(current_tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == bufname then
vim.api.nvim_set_current_win(win)
return
end
end
-- Check other tabs
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
if tab ~= current_tab then
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == bufname then
-- Switch tab first, then window
vim.api.nvim_set_current_tabpage(tab)
vim.api.nvim_set_current_win(win)
return
end
end
end
end
-- Not open anywhere → open in current window
vim.cmd('buffer ' .. entry.bufnr)
end
map('i', '<CR>', open_selected)
map('n', '<CR>', open_selected)
return true
end,
}
end
-- Map to <leader><leader>
vim.keymap.set('n', '<leader><leader>', smart_open_buffer, { desc = 'Switch to Open Buffers' })
-- which-key, register it to show a description
require('which-key').register {
['<leader><leader>'] = { smart_open_buffer, 'Switch to Open Buffers' },
}
-- Smart open a file path, reusing empty buffers or tabs if possible
local function smart_open_file(path)
if not path or path == '' then
return
end
path = vim.fn.fnamemodify(path, ':p') -- make absolute
-- 1. If file is already open → jump to it
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == path then
vim.api.nvim_set_current_tabpage(tab)
vim.api.nvim_set_current_win(win)
return
end
end
end
-- 2. If current tab has an empty "No Name" buffer → reuse it
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local buf = vim.api.nvim_win_get_buf(win)
local name = vim.api.nvim_buf_get_name(buf)
local buftype = vim.api.nvim_buf_get_option(buf, 'buftype')
local modified = vim.api.nvim_buf_get_option(buf, 'modified')
if name == '' and buftype == '' and not modified then
vim.api.nvim_set_current_win(win)
vim.cmd('edit ' .. vim.fn.fnameescape(path))
return
end
end
-- 3. Otherwise → open in a new tab
vim.cmd('tabedit ' .. vim.fn.fnameescape(path))
end
-- Remap gf to use smart_open_file
vim.keymap.set('n', 'gf', function()
local path = vim.fn.expand '<cfile>' -- get file under cursor
smart_open_file(path)
end, { desc = 'Smart gf: open file under cursor in new tab or reuse buffer' })
-- [[ Highlight on yank ]] -- [[ Highlight on yank ]]
-- See `:help vim.highlight.on_yank()` -- See `:help vim.highlight.on_yank()`
local highlight_group = vim.api.nvim_create_augroup('YankHighlight', { clear = true }) local highlight_group = vim.api.nvim_create_augroup('YankHighlight', { clear = true })
@@ -575,240 +329,6 @@ vim.api.nvim_create_autocmd('TextYankPost', {
pattern = '*', pattern = '*',
}) })
-- [[ Configure Telescope ]]
-- See `:help telescope` and `:help telescope.setup()`
require('telescope').setup {
defaults = {
mappings = {
i = {
['<C-u>'] = false,
['<C-d>'] = false,
},
},
},
}
-- Enable telescope fzf native, if installed
pcall(require('telescope').load_extension, 'fzf')
-- smart_open function for Telescope to check if the current tab has an empty "No Name" buffer. If it has, it replaces the empty buffer and open a file in the same tab
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local function smart_open(prompt_bufnr)
local entry = action_state.get_selected_entry()
if not entry then
return
end
local path = entry.path or entry.filename
if not path then
return
end
if prompt_bufnr and vim.api.nvim_buf_is_valid(prompt_bufnr) then
pcall(actions.close, prompt_bufnr)
end
-- 1. If file is already open → jump to it
local tabpages = vim.api.nvim_list_tabpages()
for _, tab in ipairs(tabpages) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == path then
vim.api.nvim_set_current_tabpage(tab) -- jump to tab
vim.api.nvim_set_current_win(win) -- jump to window
return
end
end
end
-- 2. If current tab has an empty "No Name" buffer → reuse it
local wins = vim.api.nvim_tabpage_list_wins(0)
for _, win in ipairs(wins) do
local buf = vim.api.nvim_win_get_buf(win)
local name = vim.api.nvim_buf_get_name(buf)
local buftype = vim.api.nvim_buf_get_option(buf, 'buftype')
local modified = vim.api.nvim_buf_get_option(buf, 'modified')
if name == '' and buftype == '' and not modified then
vim.api.nvim_set_current_win(win)
vim.cmd('edit ' .. vim.fn.fnameescape(path))
return
end
end
-- 3. Otherwise → open in a new tab
vim.cmd('tabnew ' .. vim.fn.fnameescape(path))
end
-- Split option in Telescope file picker with smart_open
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local function smart_open_split(prompt_bufnr, split_type)
local entry = action_state.get_selected_entry()
if not entry then
return
end
local path = entry.path or entry.filename
if not path then
return
end
if prompt_bufnr and vim.api.nvim_buf_is_valid(prompt_bufnr) then
pcall(actions.close, prompt_bufnr)
end
-- Check if file is already open
local open_tab, open_win
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == path then
open_tab = tab
open_win = win
break
end
end
if open_tab then
break
end
end
if open_tab then
local choice = vim.fn.confirm('File is already open in a tab. Open split here anyway?', '&Yes\n&No', 2)
if choice ~= 1 then
-- User chose No → jump to the tab where file is open
vim.api.nvim_set_current_tabpage(open_tab)
vim.api.nvim_set_current_win(open_win)
return
end
-- Else user chose Yes → continue to open split in current tab
end
-- Open in split
if split_type == 'v' then
-- horizontal → always below
vim.cmd('belowright split ' .. vim.fn.fnameescape(path))
elseif split_type == 'h' then
-- vertical → always right
vim.cmd('vertical rightbelow split ' .. vim.fn.fnameescape(path))
end
end
-- Telescope keymap using Smart Open
vim.keymap.set('n', '<leader>sf', function()
require('telescope.builtin').find_files {
attach_mappings = function(_, map)
map('i', '<CR>', function(prompt_bufnr)
smart_open(prompt_bufnr)
end)
map('n', '<CR>', function(prompt_bufnr)
smart_open(prompt_bufnr)
end)
-- Horizontal split with 'h'
map('n', 'h', function(prompt_bufnr)
smart_open_split(prompt_bufnr, 'h')
end)
-- Vertical split with 'v'
map('n', 'v', function(prompt_bufnr)
smart_open_split(prompt_bufnr, 'v')
end)
return true
end,
}
end, { desc = '[S]earch [F]iles (Smart Open)' })
-- Telescope live_grep in git root
-- Function to find the git root directory based on the current buffer's path
local function find_git_root()
-- Use the current buffer's path as the starting point for the git search
local current_file = vim.api.nvim_buf_get_name(0)
local current_dir
local cwd = vim.fn.getcwd()
-- If the buffer is not associated with a file, return nil
if current_file == '' then
current_dir = cwd
else
-- Extract the directory from the current file's path
current_dir = vim.fn.fnamemodify(current_file, ':h')
end
-- Find the Git root directory from the current file's path
local git_root = vim.fn.systemlist('git -C ' .. vim.fn.escape(current_dir, ' ') .. ' rev-parse --show-toplevel')[1]
if vim.v.shell_error ~= 0 then
print 'Not a git repository. Searching on current working directory'
return cwd
end
return git_root
end
-- Custom live_grep function to search in git root
local function live_grep_git_root()
local git_root = find_git_root()
if git_root then
require('telescope.builtin').live_grep {
search_dirs = { git_root },
}
end
end
vim.api.nvim_create_user_command('LiveGrepGitRoot', live_grep_git_root, {})
-- See `:help telescope.builtin`
vim.keymap.set('n', '<leader>?', require('telescope.builtin').oldfiles, { desc = '[?] Find recently opened files' })
vim.keymap.set('n', '<leader><space>', require('telescope.builtin').buffers, { desc = '[ ] Find existing buffers' })
vim.keymap.set('n', '<leader>/', function()
-- You can pass additional configuration to telescope to change theme, layout, etc.
require('telescope.builtin').current_buffer_fuzzy_find(require('telescope.themes').get_dropdown {
winblend = 10,
previewer = false,
})
end, { desc = '[/] Fuzzily search in current buffer' })
local function telescope_live_grep_open_files()
require('telescope.builtin').live_grep {
grep_open_files = true,
prompt_title = 'Live Grep in Open Files',
}
end
vim.keymap.set('n', '<leader>s/', telescope_live_grep_open_files, { desc = '[S]earch [/] in Open Files' })
vim.keymap.set('n', '<leader>ss', require('telescope.builtin').builtin, { desc = '[S]earch [S]elect Telescope' })
vim.keymap.set('n', '<leader>gf', function()
local is_git_dir = vim.fn.system('git rev-parse --is-inside-work-tree'):gsub('%s+', '') == 'true'
if not is_git_dir then
vim.notify('Not a git repository', vim.log.levels.WARN, { title = 'Telescope Git Files' })
return
end
require('telescope.builtin').git_files {
attach_mappings = function(_, map)
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local function open_smart(prompt_bufnr)
local entry = action_state.get_selected_entry()
if not entry then
return
end
pcall(actions.close, prompt_bufnr)
smart_open(prompt_bufnr)
end
map('i', '<CR>', open_smart)
map('n', '<CR>', open_smart)
return true
end,
}
end, { desc = 'Search [G]it [F]iles (Smart Open)' })
vim.keymap.set('n', '<leader>si', require('telescope.builtin').help_tags, { desc = '[S]earch [I]nfo' })
vim.keymap.set('n', '<leader>sw', require('telescope.builtin').grep_string, { desc = '[S]earch current [W]ord' })
vim.keymap.set('n', '<leader>sg', require('telescope.builtin').live_grep, { desc = '[S]earch by [G]rep' })
vim.keymap.set('n', '<leader>sG', ':LiveGrepGitRoot<cr>', { desc = '[S]earch by [G]rep on Git Root' })
vim.keymap.set('n', '<leader>sd', require('telescope.builtin').diagnostics, { desc = '[S]earch [D]iagnostics' })
vim.keymap.set('n', '<leader>sr', require('telescope.builtin').resume, { desc = '[S]earch [R]esume' })
-- [[ Configure Treesitter ]] -- [[ Configure Treesitter ]]
-- See `:help nvim-treesitter` -- See `:help nvim-treesitter`
-- Defer Treesitter setup after first render to improve startup time of 'nvim {filename}' -- Defer Treesitter setup after first render to improve startup time of 'nvim {filename}'
@@ -948,10 +468,8 @@ require('mason-lspconfig').setup()
-- Enable the following language servers -- Enable the following language servers
-- Feel free to add/remove any LSPs that you want here. They will automatically be installed. -- Feel free to add/remove any LSPs that you want here. They will automatically be installed.
--
-- Add any additional override configuration in the following tables. They will be passed to -- Add any additional override configuration in the following tables. They will be passed to
-- the `settings` field of the server config. You must look up that documentation yourself. -- the `settings` field of the server config. You must look up that documentation yourself.
--
-- If you want to override the default filetypes that your language server will attach to you can -- If you want to override the default filetypes that your language server will attach to you can
-- define the property 'filetypes' to the map in question. -- define the property 'filetypes' to the map in question.
local servers = { local servers = {
@@ -982,10 +500,6 @@ capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
-- Ensure the servers above are installed -- Ensure the servers above are installed
local mason_lspconfig = require 'mason-lspconfig' local mason_lspconfig = require 'mason-lspconfig'
mason_lspconfig.setup {
ensure_installed = vim.tbl_keys(servers),
}
mason_lspconfig.setup { mason_lspconfig.setup {
ensure_installed = vim.tbl_keys(servers), ensure_installed = vim.tbl_keys(servers),
handlers = { handlers = {
@@ -1213,8 +727,7 @@ end
-- Auto-clear messages on most user actions -- Auto-clear messages on most user actions
local clear_msg_grp = vim.api.nvim_create_augroup('AutoClearMessages', { clear = true }) local clear_msg_grp = vim.api.nvim_create_augroup('AutoClearMessages', { clear = true })
vim.api.nvim_create_autocmd( vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'InsertEnter', 'InsertLeave', 'TextChanged', 'TextChangedI' }, {
{ 'CursorMoved', 'CursorMovedI', 'InsertEnter', 'InsertLeave', 'TextChanged', 'TextChangedI' }, {
group = clear_msg_grp, group = clear_msg_grp,
callback = function() callback = function()
vim.cmd 'echo ""' -- Clear the message/command line vim.cmd 'echo ""' -- Clear the message/command line

View File

@@ -27,6 +27,7 @@
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
"telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" }, "telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" },
"telescope.nvim": { "branch": "0.1.x", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" }, "telescope.nvim": { "branch": "0.1.x", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" },
"undotree": { "branch": "master", "commit": "0f1c9816975b5d7f87d5003a19c53c6fd2ff6f7f" },
"vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" }, "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" },
"vim-rhubarb": { "branch": "master", "commit": "5496d7c94581c4c9ad7430357449bb57fc59f501" }, "vim-rhubarb": { "branch": "master", "commit": "5496d7c94581c4c9ad7430357449bb57fc59f501" },
"which-key.nvim": { "branch": "main", "commit": "904308e6885bbb7b60714c80ab3daf0c071c1492" } "which-key.nvim": { "branch": "main", "commit": "904308e6885bbb7b60714c80ab3daf0c071c1492" }

View File

@@ -2,181 +2,181 @@
vim.cmd [[ let g:neo_tree_remove_legacy_commands = 1 ]] vim.cmd [[ let g:neo_tree_remove_legacy_commands = 1 ]]
return { return {
'nvim-neo-tree/neo-tree.nvim', 'nvim-neo-tree/neo-tree.nvim',
version = '*', version = '*',
dependencies = { dependencies = {
'nvim-lua/plenary.nvim', 'nvim-lua/plenary.nvim',
'nvim-tree/nvim-web-devicons', 'nvim-tree/nvim-web-devicons',
'MunifTanjim/nui.nvim', 'MunifTanjim/nui.nvim',
}, },
config = function() config = function()
local function smart_open(state) local function smart_open(state)
local node = state.tree:get_node() local node = state.tree:get_node()
if not node then if not node then
return return
end end
local path = node:get_id() local path = node:get_id()
-- If the node is a directory, just toggle expand/collapse -- If the node is a directory, just toggle expand/collapse
if node.type == 'directory' then if node.type == 'directory' then
state.commands.toggle_node(state, node) state.commands.toggle_node(state, node)
return return
end end
-- --- File handling starts here --- -- --- File handling starts here ---
-- Reuse already open buffer in any tab safely -- Reuse already open buffer in any tab safely
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
if vim.api.nvim_tabpage_is_valid(tab) then if vim.api.nvim_tabpage_is_valid(tab) then
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
if vim.api.nvim_win_is_valid(win) then if vim.api.nvim_win_is_valid(win) then
local buf = vim.api.nvim_win_get_buf(win) local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_name(buf) == path then if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_name(buf) == path then
vim.api.nvim_set_current_tabpage(tab) vim.api.nvim_set_current_tabpage(tab)
vim.api.nvim_set_current_win(win) vim.api.nvim_set_current_win(win)
-- close Neo-tree if open -- close Neo-tree if open
for _, w in ipairs(vim.api.nvim_list_wins()) do for _, w in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_is_valid(w) then if vim.api.nvim_win_is_valid(w) then
local b = vim.api.nvim_win_get_buf(w) local b = vim.api.nvim_win_get_buf(w)
if vim.api.nvim_buf_is_valid(b) and vim.api.nvim_buf_get_option(b, 'filetype') == 'neo-tree' then if vim.api.nvim_buf_is_valid(b) and vim.api.nvim_buf_get_option(b, 'filetype') == 'neo-tree' then
vim.api.nvim_win_close(w, true) vim.api.nvim_win_close(w, true)
end end
end end
end end
return return
end end
end end
end end
end end
end end
-- Reuse empty buffer in current tab -- Reuse empty buffer in current tab
local wins = vim.api.nvim_tabpage_list_wins(0) local wins = vim.api.nvim_tabpage_list_wins(0)
local empty_buf = nil local empty_buf = nil
for _, win in ipairs(wins) do for _, win in ipairs(wins) do
if vim.api.nvim_win_is_valid(win) then if vim.api.nvim_win_is_valid(win) then
local buf = vim.api.nvim_win_get_buf(win) local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_is_valid(buf) then if vim.api.nvim_buf_is_valid(buf) then
local bufname = vim.api.nvim_buf_get_name(buf) local bufname = vim.api.nvim_buf_get_name(buf)
local buftype = vim.api.nvim_buf_get_option(buf, 'buftype') local buftype = vim.api.nvim_buf_get_option(buf, 'buftype')
local modified = vim.api.nvim_buf_get_option(buf, 'modified') local modified = vim.api.nvim_buf_get_option(buf, 'modified')
if bufname == '' and buftype == '' and not modified then if bufname == '' and buftype == '' and not modified then
empty_buf = buf empty_buf = buf
vim.api.nvim_set_current_win(win) vim.api.nvim_set_current_win(win)
break break
end end
end end
end end
end end
if empty_buf then if empty_buf then
vim.cmd('edit ' .. vim.fn.fnameescape(path)) vim.cmd('edit ' .. vim.fn.fnameescape(path))
else else
vim.cmd('tabnew ' .. vim.fn.fnameescape(path)) vim.cmd('tabnew ' .. vim.fn.fnameescape(path))
end end
-- Always close Neo-tree window if open -- Always close Neo-tree window if open
for _, win in ipairs(vim.api.nvim_list_wins()) do for _, win in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_is_valid(win) then if vim.api.nvim_win_is_valid(win) then
local buf = vim.api.nvim_win_get_buf(win) local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_option(buf, 'filetype') == 'neo-tree' then if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_option(buf, 'filetype') == 'neo-tree' then
vim.api.nvim_win_close(win, true) vim.api.nvim_win_close(win, true)
end end
end end
end end
end end
-- Smart_open_split for split windows -- Smart_open_split for split windows
local function smart_open_split(state, direction) local function smart_open_split(state, direction)
local node = state.tree:get_node() local node = state.tree:get_node()
if not node then if not node then
return return
end end
local path = node:get_id() local path = node:get_id()
if node.type == 'directory' then if node.type == 'directory' then
state.commands.toggle_node(state, node) state.commands.toggle_node(state, node)
return return
end end
-- Check if file is open in another tab -- Check if file is open in another tab
local open_tab, open_win local open_tab, open_win
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win) local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_name(buf) == path then if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_name(buf) == path then
open_tab = tab open_tab = tab
open_win = win open_win = win
break break
end end
end end
if open_tab then if open_tab then
break break
end end
end end
if open_tab then if open_tab then
local choice = vim.fn.confirm('File is already open in a tab. Open split here anyway?', '&Yes\n&No', 2) local choice = vim.fn.confirm('File is already open in a tab. Open split here anyway?', '&Yes\n&No', 2)
if choice ~= 1 then if choice ~= 1 then
-- User chose No → jump to the tab where file is open -- User chose No → jump to the tab where file is open
vim.api.nvim_set_current_tabpage(open_tab) vim.api.nvim_set_current_tabpage(open_tab)
vim.api.nvim_set_current_win(open_win) vim.api.nvim_set_current_win(open_win)
return return
end end
-- Else user chose Yes → continue to open split in current tab -- Else user chose Yes → continue to open split in current tab
end end
-- Open split in current tab -- Open split in current tab
if direction == 'v' then if direction == 'v' then
state.commands.open_vsplit(state) state.commands.open_vsplit(state)
vim.schedule(function() vim.schedule(function()
vim.cmd 'wincmd L' vim.cmd 'wincmd L'
end) end)
else else
state.commands.open_split(state) state.commands.open_split(state)
vim.schedule(function() vim.schedule(function()
vim.cmd 'wincmd J' vim.cmd 'wincmd J'
end) end)
end end
vim.cmd('edit ' .. vim.fn.fnameescape(path)) vim.cmd('edit ' .. vim.fn.fnameescape(path))
end end
require('neo-tree').setup { require('neo-tree').setup {
close_if_last_window = true, close_if_last_window = true,
popup_border_style = 'rounded', popup_border_style = 'rounded',
enable_git_status = true, enable_git_status = true,
enable_diagnostics = true, enable_diagnostics = true,
default_component_configs = { default_component_configs = {
indent = { padding = 1, indent_size = 2 }, indent = { padding = 1, indent_size = 2 },
icon = { folder_closed = '', folder_open = '', folder_empty = '' }, icon = { folder_closed = '', folder_open = '', folder_empty = '' },
}, },
window = { window = {
position = 'float', position = 'float',
width = 40, width = 40,
mapping_options = { noremap = true, nowait = true }, mapping_options = { noremap = true, nowait = true },
mappings = { mappings = {
['<cr>'] = smart_open, ['<cr>'] = smart_open,
['v'] = function(state) ['v'] = function(state)
smart_open_split(state, 'h') smart_open_split(state, 'h')
end, end,
['h'] = function(state) ['h'] = function(state)
smart_open_split(state, 'v') smart_open_split(state, 'v')
end, end,
['t'] = 'noop', ['t'] = 'noop',
}, },
}, },
filesystem = { filesystem = {
follow_current_file = { follow_current_file = {
enabled = true, -- updated to table format enabled = true, -- updated to table format
}, },
use_libuv_file_watcher = true, use_libuv_file_watcher = true,
hijack_netrw_behavior = 'open_default', hijack_netrw_behavior = 'open_default',
filtered_items = { filtered_items = {
visible = true, visible = true,
hide_dotfiles = false, hide_dotfiles = false,
hide_gitignored = true, hide_gitignored = true,
}, },
}, },
} }
end, end,
} }

View File

@@ -1,7 +0,0 @@
-- You can add your own plugins here or in other files in this directory!
-- I promise not to create any merge conflicts in this directory :)
--
-- See the kickstart.nvim README for more information
return {
}

View File

@@ -0,0 +1,255 @@
return {
-- Fuzzy Finder (files, lsp, etc)
'nvim-telescope/telescope.nvim',
branch = '0.1.x',
dependencies = {
'nvim-lua/plenary.nvim',
-- Fuzzy Finder Algorithm which requires local dependencies to be built.
-- Only load if `make` is available. Make sure you have the system
-- requirements installed.
{
'nvim-telescope/telescope-fzf-native.nvim',
-- NOTE: If you are having trouble with this installation,
-- refer to the README for telescope-fzf-native for more instructions.
build = 'make',
cond = function()
return vim.fn.executable 'make' == 1
end,
},
},
config = function()
-- [[ Configure Telescope ]]
-- See `:help telescope` and `:help telescope.setup()`
require('telescope').setup {
defaults = {
mappings = {
i = {
['<C-u>'] = false,
['<C-d>'] = false,
},
},
},
}
-- Enable telescope fzf native, if installed
pcall(require('telescope').load_extension, 'fzf')
-- smart_open function for Telescope to check if the current tab has an empty "No Name" buffer. If it has, it replaces the empty buffer and open a file in the same tab
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local function smart_open(prompt_bufnr)
local entry = action_state.get_selected_entry()
if not entry then
return
end
local path = entry.path or entry.filename
if not path then
return
end
if prompt_bufnr and vim.api.nvim_buf_is_valid(prompt_bufnr) then
pcall(actions.close, prompt_bufnr)
end
-- 1. If file is already open → jump to it
local tabpages = vim.api.nvim_list_tabpages()
for _, tab in ipairs(tabpages) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == path then
vim.api.nvim_set_current_tabpage(tab) -- jump to tab
vim.api.nvim_set_current_win(win) -- jump to window
return
end
end
end
-- 2. If current tab has an empty "No Name" buffer → reuse it
local wins = vim.api.nvim_tabpage_list_wins(0)
for _, win in ipairs(wins) do
local buf = vim.api.nvim_win_get_buf(win)
local name = vim.api.nvim_buf_get_name(buf)
local buftype = vim.api.nvim_buf_get_option(buf, 'buftype')
local modified = vim.api.nvim_buf_get_option(buf, 'modified')
if name == '' and buftype == '' and not modified then
vim.api.nvim_set_current_win(win)
vim.cmd('edit ' .. vim.fn.fnameescape(path))
return
end
end
-- 3. Otherwise → open in a new tab
vim.cmd('tabnew ' .. vim.fn.fnameescape(path))
end
-- Split option in Telescope file picker with smart_open
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local function smart_open_split(prompt_bufnr, split_type)
local entry = action_state.get_selected_entry()
if not entry then
return
end
local path = entry.path or entry.filename
if not path then
return
end
if prompt_bufnr and vim.api.nvim_buf_is_valid(prompt_bufnr) then
pcall(actions.close, prompt_bufnr)
end
-- Check if file is already open
local open_tab, open_win
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == path then
open_tab = tab
open_win = win
break
end
end
if open_tab then
break
end
end
if open_tab then
local choice = vim.fn.confirm('File is already open in a tab. Open split here anyway?', '&Yes\n&No', 2)
if choice ~= 1 then
-- User chose No → jump to the tab where file is open
vim.api.nvim_set_current_tabpage(open_tab)
vim.api.nvim_set_current_win(open_win)
return
end
-- Else user chose Yes → continue to open split in current tab
end
-- Open in split
if split_type == 'v' then
-- horizontal → always below
vim.cmd('belowright split ' .. vim.fn.fnameescape(path))
elseif split_type == 'h' then
-- vertical → always right
vim.cmd('vertical rightbelow split ' .. vim.fn.fnameescape(path))
end
end
-- Telescope keymap using Smart Open
vim.keymap.set('n', '<leader>sf', function()
require('telescope.builtin').find_files {
attach_mappings = function(_, map)
map('i', '<CR>', function(prompt_bufnr)
smart_open(prompt_bufnr)
end)
map('n', '<CR>', function(prompt_bufnr)
smart_open(prompt_bufnr)
end)
-- Horizontal split with 'h'
map('n', 'h', function(prompt_bufnr)
smart_open_split(prompt_bufnr, 'h')
end)
-- Vertical split with 'v'
map('n', 'v', function(prompt_bufnr)
smart_open_split(prompt_bufnr, 'v')
end)
return true
end,
}
end, { desc = '[S]earch [F]iles (Smart Open)' })
-- Telescope live_grep in git root
-- Function to find the git root directory based on the current buffer's path
local function find_git_root()
-- Use the current buffer's path as the starting point for the git search
local current_file = vim.api.nvim_buf_get_name(0)
local current_dir
local cwd = vim.fn.getcwd()
-- If the buffer is not associated with a file, return nil
if current_file == '' then
current_dir = cwd
else
-- Extract the directory from the current file's path
current_dir = vim.fn.fnamemodify(current_file, ':h')
end
-- Find the Git root directory from the current file's path
local git_root = vim.fn.systemlist('git -C ' .. vim.fn.escape(current_dir, ' ') .. ' rev-parse --show-toplevel')[1]
if vim.v.shell_error ~= 0 then
print 'Not a git repository. Searching on current working directory'
return cwd
end
return git_root
end
-- Custom live_grep function to search in git root
local function live_grep_git_root()
local git_root = find_git_root()
if git_root then
require('telescope.builtin').live_grep {
search_dirs = { git_root },
}
end
end
vim.api.nvim_create_user_command('LiveGrepGitRoot', live_grep_git_root, {})
-- See `:help telescope.builtin`
vim.keymap.set('n', '<leader>?', require('telescope.builtin').oldfiles, { desc = '[?] Find recently opened files' })
vim.keymap.set('n', '<leader><space>', require('telescope.builtin').buffers, { desc = '[ ] Find existing buffers' })
vim.keymap.set('n', '<leader>/', function()
-- You can pass additional configuration to telescope to change theme, layout, etc.
require('telescope.builtin').current_buffer_fuzzy_find(require('telescope.themes').get_dropdown {
winblend = 10,
previewer = false,
})
end, { desc = '[/] Fuzzily search in current buffer' })
local function telescope_live_grep_open_files()
require('telescope.builtin').live_grep {
grep_open_files = true,
prompt_title = 'Live Grep in Open Files',
}
end
vim.keymap.set('n', '<leader>s/', telescope_live_grep_open_files, { desc = '[S]earch [/] in Open Files' })
vim.keymap.set('n', '<leader>ss', require('telescope.builtin').builtin, { desc = '[S]earch [S]elect Telescope' })
vim.keymap.set('n', '<leader>gf', function()
local is_git_dir = vim.fn.system('git rev-parse --is-inside-work-tree'):gsub('%s+', '') == 'true'
if not is_git_dir then
vim.notify('Not a git repository', vim.log.levels.WARN, { title = 'Telescope Git Files' })
return
end
require('telescope.builtin').git_files {
attach_mappings = function(_, map)
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local function open_smart(prompt_bufnr)
local entry = action_state.get_selected_entry()
if not entry then
return
end
pcall(actions.close, prompt_bufnr)
smart_open(prompt_bufnr)
end
map('i', '<CR>', open_smart)
map('n', '<CR>', open_smart)
return true
end,
}
end, { desc = 'Search [G]it [F]iles (Smart Open)' })
vim.keymap.set('n', '<leader>si', require('telescope.builtin').help_tags, { desc = '[S]earch [I]nfo' })
vim.keymap.set('n', '<leader>sw', require('telescope.builtin').grep_string, { desc = '[S]earch current [W]ord' })
vim.keymap.set('n', '<leader>sg', require('telescope.builtin').live_grep, { desc = '[S]earch by [G]rep' })
vim.keymap.set('n', '<leader>sG', ':LiveGrepGitRoot<cr>', { desc = '[S]earch by [G]rep on Git Root' })
vim.keymap.set('n', '<leader>sd', require('telescope.builtin').diagnostics, { desc = '[S]earch [D]iagnostics' })
vim.keymap.set('n', '<leader>sr', require('telescope.builtin').resume, { desc = '[S]earch [R]esume' })
end,
}

View File

@@ -0,0 +1,14 @@
-- lua/custom/plugins/undotree.lua
return {
'mbbill/undotree',
keys = {
{ '<leader>u', vim.cmd.UndotreeToggle, desc = 'Toggle UndoTree' },
},
config = function()
-- Optional settings
vim.g.undotree_WindowLayout = 2 -- vertical split
vim.g.undotree_SplitWidth = 30 -- width of undo tree window
vim.g.undotree_SetFocusWhenToggle = 1
vim.g.undotree_EnableDiff = 1
end,
}

259
lua/keymaps.lua Normal file
View File

@@ -0,0 +1,259 @@
-- [[ Few useful keymaps]]
vim.keymap.set('v', 'J', ":m '<+1<CR>gv=gv")
vim.keymap.set('v', 'K', ":m '<-2<CR>gv=gv")
vim.keymap.set('n', 'J', 'mzJ`z')
vim.keymap.set('n', 'J', 'mzJ`z')
vim.keymap.set('n', '<leader>j', '<C-d>zz', { desc = 'Scroll down and center cursor' })
vim.keymap.set('n', '<leader>k', '<C-u>zz', { desc = 'Scroll up and center cursor' })
vim.keymap.set('n', 'n', 'nzzzv')
vim.keymap.set('n', 'N', 'Nzzzv')
vim.keymap.set('x', '<leader>p', '"_dP', { desc = 'Paste over selection without yanking' })
-- Replace all occurrences of the word under cursor
vim.keymap.set('n', '<leader>F', function()
-- Save current cursor position
local pos = vim.api.nvim_win_get_cursor(0)
-- Get the word under the cursor
local word = vim.fn.expand '<cword>'
-- Ask user for the replacement
local replacement = vim.fn.input("Replace '" .. word .. "' with: ")
-- If user typed something, do the substitution
if replacement ~= '' then
-- %%s/.../.../gI = substitute globally, case-insensitive
vim.cmd(string.format('%%s/\\<%s\\>/%s/gI', word, replacement))
end
-- Restore cursor position
vim.api.nvim_win_set_cursor(0, pos)
end, { desc = 'Replace all occurrences of word under cursor' })
-- [[ Basic Keymaps ]]
-- Keymaps for better default experience
-- See `:help vim.keymap.set()`
vim.keymap.set({ 'n', 'v' }, '<Space>', '<Nop>', { silent = true })
-- Remap for dealing with word wrap
vim.keymap.set('n', 'k', "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true })
vim.keymap.set('n', 'j', "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true })
-- Diagnostic keymaps
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, { desc = 'Go to previous diagnostic message' })
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, { desc = 'Go to next diagnostic message' })
vim.keymap.set('n', '<leader>de', vim.diagnostic.open_float, { desc = 'Diagnostics: floating message' })
vim.keymap.set('n', '<leader>dq', vim.diagnostic.setloclist, { desc = 'Diagnostics: location list' })
-- [[ Custom keymaps ]]
-- Neo-tree sync to current directory & toggle
vim.keymap.set('n', '<leader>n', '<Cmd>cd %:p:h | Neotree toggle float<CR>')
-- Terminal open in new tab
vim.keymap.set('n', '<leader>t', '<Cmd>tabnew +term<CR>i')
-- Create an empty buffer in a new tab
vim.keymap.set('n', '<Leader>e', function()
vim.cmd 'tabnew' -- create a new tab
vim.cmd 'enew' -- create a new empty buffer in it
end, { noremap = true, silent = true })
-- Horizontal split with new empty buffer below
vim.keymap.set('n', '<leader>sv', function()
vim.cmd 'split' -- create horizontal split (above by default)
vim.cmd 'wincmd j' -- move to the new split below
vim.cmd 'enew' -- open new empty buffer
end, { desc = 'New buffer in horizontal split (below)' })
-- Vertical split with new empty buffer to the right
vim.keymap.set('n', '<leader>sh', function()
vim.cmd 'vsplit' -- create vertical split (left by default)
vim.cmd 'wincmd l' -- move to the new split to the right
vim.cmd 'enew' -- open new empty buffer
end, { desc = 'New buffer in vertical split (right)' })
-- Save current buffer (asks for filename if new/unsaved)
vim.keymap.set('n', '<leader>w', function()
if vim.api.nvim_buf_get_name(0) == '' then
-- Ask user for a filename
local filename = vim.fn.input('Save as: ', '', 'file')
if filename ~= '' then
vim.cmd('saveas ' .. vim.fn.fnameescape(filename))
else
print 'Save cancelled'
end
else
vim.cmd 'w'
end
end, { desc = 'Save buffer (prompt if new file)' })
-- Close current window (asks if buffer is unsaved)
vim.keymap.set('n', '<leader>q', function()
if vim.bo.modified then
local choice = vim.fn.input 'Buffer modified! Save (y), Discard (n), Cancel (any other key)? '
if choice:lower() == 'y' then
if vim.api.nvim_buf_get_name(0) == '' then
local filename = vim.fn.input('Save as: ', '', 'file')
if filename ~= '' then
vim.cmd('saveas ' .. vim.fn.fnameescape(filename))
vim.cmd 'q'
else
print 'Save cancelled'
end
else
vim.cmd 'wq'
end
elseif choice:lower() == 'n' then
vim.cmd 'q!'
else
print 'Quit cancelled'
end
else
vim.cmd 'q'
end
end, { desc = 'Close buffer (prompt if modified)' })
-- Save changes and close current window (asks for filename if new/unsaved)
vim.keymap.set('n', '<leader>qy', function()
if vim.api.nvim_buf_get_name(0) == '' then
-- Ask user for a filename
local filename = vim.fn.input('Save as: ', '', 'file')
if filename ~= '' then
vim.cmd('saveas ' .. vim.fn.fnameescape(filename))
vim.cmd 'q'
else
print 'Save cancelled'
end
else
vim.cmd 'wq'
end
end, { desc = 'Save & quit (prompt if new file)' })
-- Discard changes and Close current window
vim.keymap.set('n', '<leader>qn', '<Cmd>q!<CR>')
-- Switch below/right split windows
vim.keymap.set('n', '<leader><Tab>', '<C-W><C-W>')
-- Switch above/left split windows
vim.keymap.set('n', '<Tab>', '<C-W>W')
-- Select all
vim.keymap.set('n', '<leader>ll', 'ggVG')
-- Select all and copy to clipboard
vim.keymap.set('n', '<leader>lY', 'ggVG"+y')
-- Copy to clipboard a single line
vim.keymap.set('n', 'Y', '"+yy')
-- Copy to clipboard selected text in Visual mode
vim.keymap.set('v', 'Y', '"+y')
-- Paste from clipboard
vim.keymap.set('n', '<leader>P', '"+p')
-- Redo
vim.keymap.set('n', 'U', '<C-r>')
-- Smart Open current buffers for Telescope (switch to already open buffer)
local actions = require 'telescope.actions'
local action_state = require 'telescope.actions.state'
local builtin = require 'telescope.builtin'
local function smart_open_buffer()
builtin.buffers {
attach_mappings = function(_, map)
local function open_selected(prompt_bufnr)
local entry = action_state.get_selected_entry()
if not entry then
return
end
actions.close(prompt_bufnr)
local bufname = vim.api.nvim_buf_get_name(entry.bufnr)
if bufname == '' then
return
end
-- Check all windows in current tab
local current_tab = vim.api.nvim_get_current_tabpage()
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(current_tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == bufname then
vim.api.nvim_set_current_win(win)
return
end
end
-- Check other tabs
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
if tab ~= current_tab then
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == bufname then
-- Switch tab first, then window
vim.api.nvim_set_current_tabpage(tab)
vim.api.nvim_set_current_win(win)
return
end
end
end
end
-- Not open anywhere → open in current window
vim.cmd('buffer ' .. entry.bufnr)
end
map('i', '<CR>', open_selected)
map('n', '<CR>', open_selected)
return true
end,
}
end
-- Map it to <leader><leader>
vim.keymap.set('n', '<leader><leader>', smart_open_buffer, { desc = 'Switch to Open Buffers' })
-- which-key, register it to show a description
require('which-key').register {
['<leader><leader>'] = { smart_open_buffer, 'Switch to Open Buffers' },
}
-- Smart open a file path, reusing empty buffers or tabs if possible
local function smart_open_file(path)
if not path or path == '' then
return
end
path = vim.fn.fnamemodify(path, ':p') -- make absolute
-- 1. If file is already open → jump to it
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if vim.api.nvim_buf_get_name(buf) == path then
vim.api.nvim_set_current_tabpage(tab)
vim.api.nvim_set_current_win(win)
return
end
end
end
-- 2. If current tab has an empty "No Name" buffer → reuse it
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local buf = vim.api.nvim_win_get_buf(win)
local name = vim.api.nvim_buf_get_name(buf)
local buftype = vim.api.nvim_buf_get_option(buf, 'buftype')
local modified = vim.api.nvim_buf_get_option(buf, 'modified')
if name == '' and buftype == '' and not modified then
vim.api.nvim_set_current_win(win)
vim.cmd('edit ' .. vim.fn.fnameescape(path))
return
end
end
-- 3. Otherwise → open in a new tab
vim.cmd('tabedit ' .. vim.fn.fnameescape(path))
end
-- Remap gf to use smart_open_file
vim.keymap.set('n', 'gf', function()
local path = vim.fn.expand '<cfile>' -- get file under cursor
smart_open_file(path)
end, { desc = 'Smart gf: open file under cursor in new tab or reuse buffer' })

View File

@@ -6,6 +6,11 @@
return { return {
'neovim/nvim-lspconfig', 'neovim/nvim-lspconfig',
config = function() config = function()
-- Skip setting up LSP autoformatting if conform.nvim is being used
if package.loaded['conform'] then
return
end
-- Switch for controlling whether you want autoformatting. -- Switch for controlling whether you want autoformatting.
-- Use :KickstartFormatToggle to toggle autoformatting on or off -- Use :KickstartFormatToggle to toggle autoformatting on or off
local format_is_enabled = true local format_is_enabled = true