Skip to content

Instantly share code, notes, and snippets.

@wkurth-dev
Created August 11, 2025 06:23
Show Gist options
  • Select an option

  • Save wkurth-dev/b89c5ea94dbffe5c47e05d22e9e93f57 to your computer and use it in GitHub Desktop.

Select an option

Save wkurth-dev/b89c5ea94dbffe5c47e05d22e9e93f57 to your computer and use it in GitHub Desktop.
nevim config for mac
-- ~/.config/nvim/init.lua
-- Modern Neovim configuration following best practices
-- ============================================================================
-- LEADER KEYS - Set these first, before any plugins or mappings
-- ============================================================================
vim.g.mapleader = " " -- Use space as leader key
vim.g.maplocalleader = " " -- Use space as local leader too
-- ============================================================================
-- CORE EDITOR SETTINGS
-- ============================================================================
local opt = vim.opt
-- Line numbers and visual aids
opt.number = true -- Show absolute line numbers
opt.relativenumber = false -- Disabled - change to true if you prefer relative numbers
opt.cursorline = true -- Highlight current line
opt.signcolumn = "yes" -- Always show sign column (prevents text jumping)
-- Colors and UI
opt.termguicolors = true -- Enable 24-bit RGB colors
opt.background = "dark" -- Tell Neovim we're using a dark theme
-- Mouse and clipboard
opt.mouse = "a" -- Enable mouse support in all modes
opt.clipboard = "unnamedplus" -- Use system clipboard
-- Indentation (your preferences - will be overridden by editorconfig/sleuth)
opt.tabstop = 2 -- Visual width of tab character
opt.shiftwidth = 2 -- Spaces for autoindent
opt.expandtab = true -- Convert tabs to spaces
opt.softtabstop = 2 -- Spaces inserted when pressing Tab
opt.smartindent = true -- Smart autoindenting
-- Search behavior
opt.ignorecase = true -- Ignore case in search
opt.smartcase = true -- Unless search contains uppercase
opt.hlsearch = false -- Don't highlight search results
opt.incsearch = true -- Show search matches as you type
-- Better editing experience
opt.wrap = false -- Don't wrap long lines
opt.scrolloff = 8 -- Keep 8 lines above/below cursor
opt.sidescrolloff = 8 -- Keep 8 columns left/right of cursor
opt.updatetime = 300 -- Faster completion and diagnostics
-- ============================================================================
-- BOOTSTRAP LAZY.NVIM PLUGIN MANAGER
-- ============================================================================
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
opt.rtp:prepend(lazypath)
-- ============================================================================
-- PLUGIN SPECIFICATIONS
-- ============================================================================
require("lazy").setup({
-- ========================================================================
-- SYNTAX HIGHLIGHTING & CODE PARSING
-- ========================================================================
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
config = function()
require("nvim-treesitter.configs").setup({
ensure_installed = {
"lua",
"typescript",
"javascript",
"tsx", -- TypeScript JSX
"html",
"css",
"rust",
"go",
"c_sharp",
"bash",
"json",
"yaml",
"markdown",
"vim", -- For editing Neovim config
"vimdoc", -- For Neovim help files
},
sync_install = false, -- Install parsers synchronously (only applied to `ensure_installed`)
auto_install = true, -- Automatically install missing parsers when entering buffer
ignore_install = {}, -- List of parsers to ignore installing
modules = {}, -- List of modules and their configuration
highlight = {
enable = true,
additional_vim_regex_highlighting = false,
},
indent = { enable = true },
-- Enable incremental selection
incremental_selection = {
enable = true,
keymaps = {
init_selection = "<C-space>",
node_incremental = "<C-space>",
scope_incremental = "<C-s>",
node_decremental = "<M-space>",
},
},
})
end,
},
-- ========================================================================
-- COLORSCHEME
-- ========================================================================
{
"scottmckendry/cyberdream.nvim",
lazy = false, -- Load immediately during startup
priority = 1000, -- Load before other plugins
config = function()
require("cyberdream").setup({
transparent = false,
italic_comments = true,
hide_fillchars = true,
borderless_telescope = false,
})
vim.cmd.colorscheme("cyberdream")
end,
},
-- ========================================================================
-- EDITOR CONFIGURATION & BEHAVIOR
-- ========================================================================
{
"editorconfig/editorconfig-vim",
lazy = false,
desc = "Automatically apply project-specific editor settings",
},
{
"tpope/vim-sleuth",
lazy = false,
desc = "Automatically detect indentation style from existing code",
},
-- ========================================================================
-- LSP (Language Server Protocol) & DIAGNOSTICS
-- ========================================================================
{
"neovim/nvim-lspconfig",
dependencies = {
"williamboman/mason.nvim", -- LSP installer
"williamboman/mason-lspconfig.nvim", -- Bridge between mason and lspconfig
},
config = function()
-- Setup Mason (LSP installer)
require("mason").setup()
require("mason-lspconfig").setup({
ensure_installed = {
"lua_ls", -- Lua
"ts_ls", -- TypeScript/JavaScript
"html", -- HTML
"cssls", -- CSS
"rust_analyzer", -- Rust
"gopls", -- Go
"omnisharp", -- C#
"bashls", -- Bash/Zsh
"eslint", -- ESLint language server
},
})
-- Auto-install formatters and linters
local mason_registry = require("mason-registry")
local mason_formatters = { "stylua", "prettier", "shfmt", "eslint_d" }
for _, formatter in ipairs(mason_formatters) do
if not mason_registry.is_installed(formatter) then
vim.cmd("MasonInstall " .. formatter)
end
end
-- Configure Language Servers
local lspconfig = require("lspconfig")
-- Lua Language Server for Neovim development
lspconfig.lua_ls.setup({
settings = {
Lua = {
runtime = { version = "LuaJIT" },
diagnostics = {
globals = { "vim" }, -- Recognize 'vim' as valid global
},
workspace = {
library = vim.api.nvim_get_runtime_file("", true),
checkThirdParty = false,
},
telemetry = { enable = false },
},
},
})
-- TypeScript/JavaScript
lspconfig.ts_ls.setup({
on_attach = function(client, bufnr)
-- Check if ESLint is configured in this project
local has_eslint_config = vim.fs.find({
"eslint.config.js", "eslint.config.mjs", "eslint.config.cjs",
".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc",
".eslintrc.yml", ".eslintrc.yaml"
}, { path = vim.fn.expand("%:p:h"), upward = true })[1]
if has_eslint_config then
-- If ESLint is present, disable TypeScript's overlapping diagnostics
client.server_capabilities.diagnosticProvider = false
vim.defer_fn(function()
vim.diagnostic.reset(nil, bufnr)
end, 100)
end
end,
})
-- ESLint Language Server (handles both linting and formatting)
lspconfig.eslint.setup({
-- Only attach ESLint if project has ESLint configuration
on_attach = function(client, bufnr)
-- Check if ESLint config exists in project
local has_eslint_config = vim.fs.find({
"eslint.config.js", "eslint.config.mjs", "eslint.config.cjs",
".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc",
".eslintrc.yml", ".eslintrc.yaml"
}, { path = vim.fn.expand("%:p:h"), upward = true })[1]
if not has_eslint_config then
-- If no ESLint config found, detach the client
vim.lsp.buf_detach_client(bufnr, client.id)
return
end
end,
settings = {
codeAction = {
disableRuleComment = {
enable = true,
location = "separateLine"
},
showDocumentation = {
enable = true
}
},
codeActionOnSave = {
enable = false, -- We'll handle this via conform
mode = "all"
},
format = true,
nodePath = "",
onIgnoredFiles = "off",
packageManager = "npm",
quiet = false,
rulesCustomizations = {},
run = "onType",
useESLintClass = false,
validate = "on",
workingDirectory = { mode = "location" }
}
})
-- HTML
lspconfig.html.setup({})
-- CSS
lspconfig.cssls.setup({})
-- Rust
lspconfig.rust_analyzer.setup({
settings = {
["rust-analyzer"] = {
cargo = { allFeatures = true },
checkOnSave = { command = "clippy" },
},
},
})
-- Go
lspconfig.gopls.setup({})
-- C#
lspconfig.omnisharp.setup({})
-- Bash/Zsh
lspconfig.bashls.setup({})
-- ====================================================================
-- LSP & DIAGNOSTIC KEYMAPS
-- ====================================================================
local keymap = vim.keymap.set
-- Diagnostics navigation
keymap("n", "<leader>e", vim.diagnostic.open_float, { desc = "Show diagnostic message" })
keymap("n", "[d", function() vim.diagnostic.jump({ count = -1, float = true }) end, { desc = "Previous diagnostic" })
keymap("n", "]d", function() vim.diagnostic.jump({ count = 1, float = true }) end, { desc = "Next diagnostic" })
-- LSP actions
keymap("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "Code actions" })
end,
},
-- ========================================================================
-- AUTOCOMPLETION
-- ========================================================================
{
"hrsh7th/nvim-cmp",
dependencies = {
"hrsh7th/cmp-nvim-lsp", -- LSP completion source
"hrsh7th/cmp-buffer", -- Buffer text completion
"hrsh7th/cmp-path", -- File path completion
"hrsh7th/cmp-cmdline", -- Command line completion
"L3MON4D3/LuaSnip", -- Snippet engine
"saadparwaiz1/cmp_luasnip", -- Snippet completion source
},
config = function()
local cmp = require("cmp")
local luasnip = require("luasnip")
cmp.setup({
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4),
["<C-f>"] = cmp.mapping.scroll_docs(4),
["<C-Space>"] = cmp.mapping.complete(),
["<C-e>"] = cmp.mapping.abort(),
["<CR>"] = cmp.mapping.confirm({ select = true }),
-- Tab/Shift-Tab for completion navigation
["<Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { "i", "s" }),
["<S-Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif luasnip.jumpable(-1) then
luasnip.jump(-1)
else
fallback()
end
end, { "i", "s" }),
}),
-- Completion sources (order matters - higher priority first)
sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "luasnip" },
{ name = "buffer" },
{ name = "path" },
}),
})
-- Command line completion for : commands
cmp.setup.cmdline(":", {
mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({
{ name = "path" },
{ name = "cmdline" },
}),
})
end,
},
-- ========================================================================
-- CODE FORMATTING
-- ========================================================================
{
"stevearc/conform.nvim",
opts = {
formatters_by_ft = {
lua = { "stylua" },
typescript = { "eslint_d", "prettier" }, -- Try ESLint first, fallback to prettier
javascript = { "eslint_d", "prettier" }, -- Try ESLint first, fallback to prettier
typescriptreact = { "eslint_d", "prettier" },
javascriptreact = { "eslint_d", "prettier" },
html = { "prettier" },
css = { "prettier" },
rust = { "rustfmt" },
go = { "gofmt" },
sh = { "shfmt" },
bash = { "shfmt" },
zsh = { "shfmt" },
json = { "prettier" },
yaml = { "prettier" },
markdown = { "prettier" },
},
-- Smart formatter selection
formatters = {
eslint_d = {
condition = function(self, ctx)
-- Only use eslint_d if project has ESLint config
local has_eslint_config = vim.fs.find({
"eslint.config.js", "eslint.config.mjs", "eslint.config.cjs",
".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc"
}, { path = ctx.filename, upward = true })[1]
return has_eslint_config ~= nil
end,
},
},
format_on_save = {
timeout_ms = 500,
lsp_fallback = true, -- Fall back to LSP formatting if no formatter
},
},
keys = {
{
"<leader>f",
function()
require("conform").format({ async = true, lsp_fallback = true })
end,
desc = "Format buffer",
},
},
},
-- ========================================================================
-- FILE NAVIGATION
-- ========================================================================
{
"nvim-tree/nvim-tree.lua",
dependencies = {
"nvim-tree/nvim-web-devicons", -- File type icons
},
config = function()
-- Disable Neovim's built-in file explorer
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
require("nvim-tree").setup({
sort = {
sorter = "case_sensitive",
},
view = {
width = 30,
relativenumber = true,
},
renderer = {
group_empty = true,
icons = {
show = {
folder_arrow = true,
},
git_placement = "before",
glyphs = {
git = {
unstaged = "●", -- Modified files (instead of red X)
staged = "✓", -- Staged files
unmerged = "◐", -- Merge conflicts
renamed = "➜", -- Renamed files
untracked = "★", -- New files
deleted = "✖", -- Deleted files
ignored = "◌", -- Ignored files
},
},
},
},
filters = {
dotfiles = false, -- Show hidden files
custom = { "^.git$" }, -- Hide .git folder
},
})
end,
keys = {
{ "<leader>tt", ":NvimTreeToggle<CR>", desc = "Toggle file tree" },
{ "<leader>tf", ":NvimTreeFindFile<CR>", desc = "Find current file in tree" },
},
},
-- ========================================================================
-- GIT INTEGRATION & CHANGE TRACKING
-- ========================================================================
{
"lewis6991/gitsigns.nvim",
event = { "BufReadPre", "BufNewFile" }, -- Load when opening any file
config = function()
require("gitsigns").setup({
signs = {
add = { text = "│" }, -- Lines added
change = { text = "│" }, -- Lines modified
delete = { text = "_" }, -- Lines deleted
topdelete = { text = "‾" }, -- Lines deleted at top
changedelete = { text = "~" }, -- Lines changed and deleted
untracked = { text = "┆" }, -- Untracked files
},
signs_staged = {
add = { text = "┃" }, -- Staged additions (thicker line)
change = { text = "┃" }, -- Staged changes
delete = { text = "▁" }, -- Staged deletions
topdelete = { text = "▔" }, -- Staged deletions at top
changedelete = { text = "≃" }, -- Staged changes and deletions
},
signcolumn = true, -- Show signs in sign column
numhl = false, -- Don't highlight line numbers
linehl = false, -- Don't highlight entire lines
word_diff = false, -- Don't show word-level diffs
watch_gitdir = {
follow_files = true
},
auto_attach = true, -- Automatically attach to Git repos
attach_to_untracked = false, -- Don't attach to untracked files initially
current_line_blame = false, -- Don't show blame on current line (can be toggled)
current_line_blame_opts = {
virt_text = true,
virt_text_pos = "eol", -- Show at end of line
delay = 1000, -- Delay before showing blame
ignore_whitespace = false,
},
sign_priority = 6, -- Priority for sign placement
update_debounce = 100, -- Debounce time for updates
status_formatter = nil, -- Use default status formatter
max_file_length = 40000, -- Don't attach to files longer than this
preview_config = {
border = "single", -- Border style for preview windows
style = "minimal",
relative = "cursor",
row = 0,
col = 1
},
})
end,
keys = {
-- Navigation between Git changes
{ "]c", function()
if vim.wo.diff then
vim.cmd.normal({"]c", bang = true})
else
require("gitsigns").nav_hunk("next")
end
end, desc = "Next Git change" },
{ "[c", function()
if vim.wo.diff then
vim.cmd.normal({"[c", bang = true})
else
require("gitsigns").nav_hunk("prev")
end
end, desc = "Previous Git change" },
-- Git hunk actions (leader + h for "hunk")
{ "<leader>hs", ":Gitsigns stage_hunk<CR>", desc = "Stage hunk", mode = { "n", "v" } },
{ "<leader>hr", ":Gitsigns reset_hunk<CR>", desc = "Reset hunk", mode = { "n", "v" } },
{ "<leader>hS", function() require("gitsigns").stage_buffer() end, desc = "Stage entire buffer" },
{ "<leader>hu", ":Gitsigns undo_stage_hunk<CR>", desc = "Undo stage hunk" },
{ "<leader>hR", function() require("gitsigns").reset_buffer() end, desc = "Reset entire buffer" },
{ "<leader>hp", ":Gitsigns preview_hunk<CR>", desc = "Preview hunk changes" },
{ "<leader>hb", function() require("gitsigns").blame_line({full=true}) end, desc = "Show line blame" },
{ "<leader>tb", ":Gitsigns toggle_current_line_blame<CR>", desc = "Toggle line blame display" },
{ "<leader>hd", ":Gitsigns diffthis<CR>", desc = "Diff against index" },
{ "<leader>hD", function() require("gitsigns").diffthis("~") end, desc = "Diff against last commit" },
{ "<leader>td", ":Gitsigns toggle_deleted<CR>", desc = "Toggle show deleted lines" },
-- Text object for Git hunks (works with operators like d, y, etc.)
{ "ih", ":<C-U>Gitsigns select_hunk<CR>", desc = "Select hunk", mode = { "o", "x" } },
},
},
-- ========================================================================
-- Add future plugins here following the same pattern:
-- {
-- "author/plugin-name",
-- dependencies = { ... },
-- config = function() ... end,
-- keys = { ... },
-- },
-- ========================================================================
})
-- ============================================================================
-- ADDITIONAL KEYMAPS (non-plugin specific)
-- ============================================================================
local keymap = vim.keymap.set
-- Better window navigation
keymap("n", "<C-h>", "<C-w>h", { desc = "Move to left window" })
keymap("n", "<C-j>", "<C-w>j", { desc = "Move to bottom window" })
keymap("n", "<C-k>", "<C-w>k", { desc = "Move to top window" })
keymap("n", "<C-l>", "<C-w>l", { desc = "Move to right window" })
-- Better escape from insert mode
keymap("i", "jk", "<Esc>", { desc = "Exit insert mode" })
-- Clear search highlighting
keymap("n", "<Esc>", ":nohlsearch<CR>", { desc = "Clear search highlights" })
-- Quick blame
keymap("n", "<leader>b", function() require("gitsigns").blame({full=true}) end, { desc = "Git blame" })
-- ============================================================================
-- AUTOCOMMANDS (automatic behaviors)
-- ============================================================================
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd
-- Highlight yanked text briefly
autocmd("TextYankPost", {
group = augroup("highlight_yank", { clear = true }),
pattern = "*",
callback = function()
vim.highlight.on_yank({ higroup = "IncSearch", timeout = 300 })
end,
})
-- Remove trailing whitespace on save
autocmd("BufWritePre", {
group = augroup("trim_whitespace", { clear = true }),
pattern = "*",
command = [[%s/\s\+$//e]],
})
-- ============================================================================
-- End of configuration
-- ============================================================================
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment