cashmere

cashmere

nvim sane defaults

-----------------------
-- Globals & Options --
-----------------------
vim.g.mapleader = " "

-- UI & Visuals
vim.o.number = false
vim.o.signcolumn = "yes"
vim.o.colorcolumn = "80"
vim.o.showtabline = 1
vim.o.wrap = false
vim.o.termguicolors = true
pcall(vim.cmd.colorscheme, "eink")

-- Indentation
vim.o.tabstop = 4
vim.o.shiftwidth = 4
vim.o.softtabstop = 4
vim.o.expandtab = false
vim.o.smartindent = true

-- Search
vim.o.hlsearch = false
vim.o.incsearch = true

-- File handling
vim.o.backup = false
vim.o.swapfile = false
vim.o.undofile = true
vim.o.undodir = os.getenv("HOME") .. "/.vim/undodir"
vim.o.updatetime = 250

-- Diagnostics Config
vim.diagnostic.config({
    signs = {
        text = {
            [vim.diagnostic.severity.ERROR] = "E",
            [vim.diagnostic.severity.WARN] = "W",
            [vim.diagnostic.severity.INFO] = "I",
            [vim.diagnostic.severity.HINT] = "H",
        },
    },
    virtual_text = true,
})

--------------
-- Packages --
--------------
vim.pack.add({
    -- Terminal
    { src = "https://github.com/akinsho/toggleterm.nvim" },
    -- Telescope
    { src = "https://github.com/nvim-lua/plenary.nvim" },
    { src = "https://github.com/nvim-telescope/telescope.nvim" },
    -- LSP & Mason
    { src = "https://github.com/neovim/nvim-lspconfig" },
    { src = "https://github.com/mason-org/mason.nvim" },
    { src = "https://github.com/mason-org/mason-lspconfig.nvim" },
    -- Autocomplete
    { src = "https://github.com/hrsh7th/cmp-nvim-lsp" },
    { src = "https://github.com/hrsh7th/cmp-buffer" },
    { src = "https://github.com/hrsh7th/cmp-path" },
    { src = "https://github.com/hrsh7th/cmp-cmdline" },
    { src = "https://github.com/hrsh7th/nvim-cmp" },
})

---------------------------
-- Plugin Configurations --
---------------------------

-- Mason & LSP Config
require("mason").setup()
require("mason-lspconfig").setup()

-- ToggleTerm
local status_ok, toggleterm = pcall(require, "toggleterm")
if status_ok then
    toggleterm.setup({
        size = 20,
        open_mapping = [[<c-\>]],
        hide_numbers = true,
        direction = "float",
        float_opts = { border = "curved" },
    })
end

-- CMP (Autocomplete)
local cmp = require("cmp")
cmp.setup({
    snippet = {
        expand = function(args)
            vim.snippet.expand(args.body)
        end,
    },
    window = {
        completion = { winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,Search:None" },
        documentation = { winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,Search:None" },
    },
    mapping = cmp.mapping.preset.insert({
        ['<Tab>'] = cmp.mapping(function(fallback)
            if cmp.visible() then cmp.select_next_item() else fallback() end
        end, { 'i', 's' }),
        ['<S-Tab>'] = cmp.mapping(function(fallback)
            if cmp.visible() then cmp.select_prev_item() else fallback() end
        end, { 'i', 's' }),
        ['<CR>'] = cmp.mapping.confirm({ select = true }),
    }),
    sources = cmp.config.sources({ { name = "nvim_lsp" }, }, { { name = "buffer" } }, { { name = "path" } })
})

--------------------------------
-- Autocommands & LSP Keymaps --
--------------------------------

-- FileType specific indentation
vim.api.nvim_create_autocmd("FileType", {
    pattern = { "javascript", "typescript", "javascriptreact", "typescriptreact" },
    callback = function()
        vim.opt_local.tabstop = 2
        vim.opt_local.softtabstop = 2
        vim.opt_local.shiftwidth = 2
    end,
})

-- LSP Attach
vim.api.nvim_create_autocmd("LspAttach", {
    group = vim.api.nvim_create_augroup("UserLspConfig", {}),
    callback = function(ev)
        -- Enable completion triggered by <c-x><c-o>
        vim.bo[ev.buf].omnifunc = 'v:lua.vim.lsp.omnifunc'

        -- Helper function to make mapping easier
        local function map(keys, func, desc, mode)
            mode = mode or "n"
            vim.keymap.set(mode, keys, func, { buffer = ev.buf, desc = desc })
        end

        map("gd", vim.lsp.buf.definition, "Go to definition")
        map("<leader>f", vim.lsp.buf.format, "Format code using LSP")
        map("K", function() vim.lsp.buf.hover({ border = "rounded" }) end, "LSP hover")
        map("<leader>vd", vim.diagnostic.open_float, "Show diagnostics")
        map("<leader>vca", vim.lsp.buf.code_action, "Code actions")
        map("<leader>vrr", vim.lsp.buf.references, "Show references")
        map("<leader>vrn", vim.lsp.buf.rename, "Rename symbol")
        map("<C-h>", vim.lsp.buf.signature_help, "Signature help", "i")
    end,
})

-----------------------------
-- General Keymaps & Utils --
-----------------------------

-- Telescope
local builtin = require("telescope.builtin")
vim.keymap.set("n", "<leader>ff", builtin.find_files, {})
vim.keymap.set("n", "<leader>F", function()
    builtin.grep_string({ search = vim.fn.input("Grep > ") });
end)
vim.keymap.set('n', '<leader>fr', builtin.lsp_references)

-- Standard operations
vim.keymap.set("n", "<leader>tn", vim.cmd.tabnew, { desc = "Open new tab" })
vim.keymap.set("n", "<leader>pf", vim.cmd.Ex, { desc = "Open file explorer" })
vim.keymap.set("n", "<C-d>", "<C-d>zz", { desc = "Scroll down and center" })
vim.keymap.set("n", "<C-u>", "<C-u>zz", { desc = "Scroll up and center" })
vim.keymap.set("n", "n", "nzzzv", { desc = "Find next search result and center" })
vim.keymap.set("n", "N", "Nzzzv", { desc = "Find previous search result and center" })
vim.keymap.set("x", "<leader>p", [["_dP]], { desc = "Paste without overwriting clipboard" })
vim.keymap.set({ "n", "v" }, "<leader>y", [["+y]], { desc = "Copy to system clipboard" })
vim.keymap.set("n", "Q", "<nop>", { desc = "Disable Q" })
vim.keymap.set("v", "<", "<gv", { desc = "Indent left and stay in visual mode" })
vim.keymap.set("v", ">", ">gv", { desc = "Indent right and stay in visual mode" })
vim.keymap.set("n", "<leader>s", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]],
    { desc = "Search and replace word under cursor" }
)

-- Utility: Auto-assign global marks
local function set_next_global_mark()
    local marks = vim.fn.getmarklist()
    local used_marks = {}

    for _, mark in pairs(marks) do
        if mark.mark:match("^'[A-Z]$") then
            used_marks[mark.mark:sub(2)] = true
        end
    end

    for i = 65, 90 do -- ASCII values for 'A' to 'Z'
        local mark = string.char(i)
        if not used_marks[mark] then
            vim.cmd("mark " .. mark)
            print("Mark set: " .. mark)
            return
        end
    end

    print("No available global marks")
end
vim.keymap.set("n", "<leader>a", set_next_global_mark,
    { desc = "Set next available global mark" }
)
vim.keymap.set("n", "<C-ESC><C-ESC>", ":delmarks ABCDEFGHIJKLMNOPQRSTUVWXYZ<CR>",
    { desc = "Remove all global marks" }
)
vim.keymap.set("n", "<C-E>", ":marks ABCDEFGHIJKLMNOPQRSTUVWXYZ<CR>",
    { desc = "Show all assigned global marks" }
)