Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ My personal dotfiles. macOS primary, Linux compatible.
| **zsh** | `zsh/` | Prompt with git info, history, aliases, functions |
| **git** | `git/` | Modern defaults, useful aliases, pretty log |
| **tmux** | `tmux/` | C-a prefix, vim-style nav, Tokyo Night theme, TPM |
| **Neovim** | `config/nvim/` | lazy.nvim, Tokyo Night, Telescope, Treesitter, LSP, nvim-cmp |
| **Ghostty** | `config/ghostty/` | Tokyo Night, Monaspace font, transparency |

## Bootstrap
Expand All @@ -32,6 +33,7 @@ cd ~/.dotfiles

1. **Restart your shell** (or `source ~/.zshrc`)
2. **tmux:** Press `C-a I` to install tmux plugins via TPM
3. **Neovim:** Open `nvim` — plugins install automatically on first launch

## Local overrides

Expand Down Expand Up @@ -70,15 +72,54 @@ dotfiles/
│ ├── tmux.conf # tmux configuration
│ └── tmux.mac.conf # macOS-specific (pbcopy integration)
└── config/
└── ghostty/
└── config # Ghostty terminal config
├── ghostty/
│ └── config # Ghostty terminal config
└── nvim/
├── init.lua # Entry point
└── lua/
├── options.lua # Editor options
├── keymaps.lua # Key mappings
├── autocmds.lua # Autocommands
└── plugins/
├── init.lua # lazy.nvim bootstrap
├── colorscheme.lua # Tokyo Night
├── telescope.lua # Fuzzy finder
├── treesitter.lua # Syntax highlighting
├── lsp.lua # Language servers
├── completion.lua # nvim-cmp
├── ui.lua # Statusline, file explorer, git signs
└── editor.lua # Comments, pairs, surround, etc.
```

## Neovim plugins

Managed by [lazy.nvim](https://github.com/folke/lazy.nvim) (not LazyVim):

- **Tokyo Night** — colorscheme
- **Telescope** — fuzzy finder (files, grep, buffers, symbols)
- **Treesitter** — syntax highlighting, text objects, incremental selection
- **LSP** — language servers via Mason (Ruby, Lua out of the box)
- **nvim-cmp** — autocompletion with LSP, buffer, path, and snippet sources
- **Lualine** — statusline
- **Oil.nvim** — file explorer as a buffer (`-` to open parent dir)
- **Gitsigns** — git decorations in the gutter
- **Fugitive** — Git commands in Neovim
- **Comment.nvim** — toggle comments (`gc` / `gcc`)
- **nvim-surround** — add/change/delete surrounding chars
- **nvim-autopairs** — auto-close brackets and quotes
- **nvim-treesitter-endwise** — auto-add `end` in Ruby, Lua, etc.
- **Trouble** — better diagnostics list
- **Todo-comments** — highlight TODO/FIXME comments
- **Which-key** — keybinding hints
- **Indent blankline** — indent guides
- **vim-sleuth** — auto-detect indentation

## Design decisions

- **mitamae for package installation** — single static binary (mruby compiled in), no Ruby/RubyGems needed. Chef-like DSL with `package` resource that auto-detects the package manager. One recipe works on macOS (Homebrew) and Linux (apt). Cross-platform from day one.
- **install.sh for symlinking** — plain symlinks, idempotent, zero dependencies beyond git and zsh. mitamae handles packages, install.sh handles links — clean separation of concerns.
- **No dotfiles framework** — chezmoi, yadm, etc. are overkill for this setup. mitamae + install.sh covers packages and links with minimal complexity.
- **mise over rbenv/nvm** — single tool for all language versions.
- **Local override files** — machine-specific config stays out of the repo.
- **No LazyVim** — lazy.nvim as plugin manager, but every plugin is explicitly configured. Full control, no hidden magic.
- **macOS + Linux** — platform conditionals where needed (ls colors, vpn-fix, tmux copy).
10 changes: 10 additions & 0 deletions config/nvim/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Neovim configuration — David Stosik
-- Plugin manager: lazy.nvim (NOT LazyVim)
-- Theme: Tokyo Night

require("options")
require("keymaps")
require("autocmds")

-- Bootstrap and load plugins
require("plugins")
49 changes: 49 additions & 0 deletions config/nvim/lua/autocmds.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- Autocommands

local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd

-- Highlight on yank
autocmd("TextYankPost", {
group = augroup("YankHighlight", { clear = true }),
callback = function()
vim.highlight.on_yank({ higroup = "IncSearch", timeout = 200 })
end,
})

-- Restore cursor position when reopening a file
autocmd("BufReadPost", {
group = augroup("RestoreCursor", { clear = true }),
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local lcount = vim.api.nvim_buf_line_count(0)
if mark[1] > 0 and mark[1] <= lcount then
pcall(vim.api.nvim_win_set_cursor, 0, mark)
end
end,
})

-- Remove trailing whitespace on save for certain filetypes
autocmd("BufWritePre", {
group = augroup("TrimWhitespace", { clear = true }),
pattern = { "*.rb", "*.lua", "*.sh", "*.zsh", "*.yml", "*.yaml", "*.json", "*.md" },
callback = function()
local save = vim.fn.winsaveview()
vim.cmd([[%s/\s\+$//e]])
vim.fn.winrestview(save)
end,
})

-- Auto-reload files changed outside of Neovim
autocmd({ "FocusGained", "TermClose", "TermLeave" }, {
group = augroup("AutoReload", { clear = true }),
command = "checktime",
})

-- Resize splits when terminal is resized
autocmd("VimResized", {
group = augroup("ResizeSplits", { clear = true }),
callback = function()
vim.cmd("tabdo wincmd =")
end,
})
55 changes: 55 additions & 0 deletions config/nvim/lua/keymaps.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
-- Keymaps

local map = vim.keymap.set

-- Better escape
map("i", "jk", "<Esc>", { desc = "Escape insert mode" })

-- Clear search highlight
map("n", "<leader><space>", "<cmd>nohlsearch<CR>", { desc = "Clear search highlight" })

-- Window navigation
map("n", "<C-h>", "<C-w>h", { desc = "Move to left window" })
map("n", "<C-j>", "<C-w>j", { desc = "Move to lower window" })
map("n", "<C-k>", "<C-w>k", { desc = "Move to upper window" })
map("n", "<C-l>", "<C-w>l", { desc = "Move to right window" })

-- Window resizing
map("n", "<C-Up>", "<cmd>resize +2<CR>", { desc = "Increase window height" })
map("n", "<C-Down>", "<cmd>resize -2<CR>", { desc = "Decrease window height" })
map("n", "<C-Left>", "<cmd>vertical resize -2<CR>", { desc = "Decrease window width" })
map("n", "<C-Right>", "<cmd>vertical resize +2<CR>", { desc = "Increase window width" })

-- Move lines up/down in visual mode
map("v", "J", ":m '>+1<CR>gv=gv", { desc = "Move selection down" })
map("v", "K", ":m '<-2<CR>gv=gv", { desc = "Move selection up" })

-- Keep cursor centered when scrolling
map("n", "<C-d>", "<C-d>zz")
map("n", "<C-u>", "<C-u>zz")

-- Keep search results centered
map("n", "n", "nzzzv")
map("n", "N", "Nzzzv")

-- Don't lose selection when indenting
map("v", "<", "<gv")
map("v", ">", ">gv")

-- Buffer navigation
map("n", "<S-h>", "<cmd>bprevious<CR>", { desc = "Previous buffer" })
map("n", "<S-l>", "<cmd>bnext<CR>", { desc = "Next buffer" })
map("n", "<leader>bd", "<cmd>bdelete<CR>", { desc = "Delete buffer" })

-- Vertical diff split
vim.opt.diffopt:append("vertical")

-- Save with Ctrl-S (also in insert mode)
map({ "n", "i" }, "<C-s>", "<cmd>w<CR>", { desc = "Save file" })

-- Quit
map("n", "<leader>q", "<cmd>q<CR>", { desc = "Quit" })
map("n", "<leader>Q", "<cmd>qa!<CR>", { desc = "Force quit all" })

-- Delete trailing whitespace
map("n", "<F5>", [[<cmd>let _s=@/<Bar>:%s/\s\+$//e<Bar>:let @/=_s<Bar>:nohl<CR>]], { desc = "Delete trailing whitespace" })
64 changes: 64 additions & 0 deletions config/nvim/lua/options.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- Options

local opt = vim.opt

-- Line numbers
opt.number = true
opt.relativenumber = true

-- Indentation
opt.expandtab = true
opt.shiftwidth = 2
opt.softtabstop = 2
opt.tabstop = 2
opt.smartindent = true

-- Search
opt.ignorecase = true
opt.smartcase = true
opt.hlsearch = true
opt.incsearch = true
opt.gdefault = true -- Substitute globally by default

-- Display
opt.termguicolors = true
opt.signcolumn = "yes"
opt.cursorline = true
opt.scrolloff = 8
opt.sidescrolloff = 8
opt.colorcolumn = "81"
opt.showmode = false -- Shown by lualine instead
opt.wrap = false
opt.list = true
opt.listchars = { tab = "» ", trail = "·", extends = ">", precedes = "<", nbsp = "%" }

-- Splits
opt.splitbelow = true
opt.splitright = true

-- Files
opt.undofile = true
opt.backup = false
opt.swapfile = false
opt.autoread = true

-- Clipboard (sync with system)
opt.clipboard = "unnamedplus"

-- Completion
opt.completeopt = { "menu", "menuone", "noselect" }
opt.pumheight = 10

-- Misc
opt.updatetime = 250
opt.timeoutlen = 300
opt.mouse = "a"
opt.wildmode = { "list", "longest" }

-- Leader
vim.g.mapleader = ","
vim.g.maplocalleader = ","

-- Disable netrw (we use telescope and oil.nvim)
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
23 changes: 23 additions & 0 deletions config/nvim/lua/plugins/colorscheme.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- Tokyo Night colorscheme
return {
{
"folke/tokyonight.nvim",
lazy = false,
priority = 1000,
opts = {
style = "night",
transparent = false,
terminal_colors = true,
styles = {
comments = { italic = true },
keywords = { italic = true },
sidebars = "dark",
floats = "dark",
},
},
config = function(_, opts)
require("tokyonight").setup(opts)
vim.cmd.colorscheme("tokyonight")
end,
},
}
79 changes: 79 additions & 0 deletions config/nvim/lua/plugins/completion.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
-- Completion — nvim-cmp
return {
{
"hrsh7th/nvim-cmp",
event = "InsertEnter",
dependencies = {
"hrsh7th/cmp-nvim-lsp", -- LSP completions
"hrsh7th/cmp-buffer", -- Buffer words
"hrsh7th/cmp-path", -- File paths
"hrsh7th/cmp-cmdline", -- Command-line completions
"L3MON4D3/LuaSnip", -- Snippet engine
"saadparwaiz1/cmp_luasnip", -- Snippet completions
},
config = function()
local cmp = require("cmp")
local luasnip = require("luasnip")

cmp.setup({
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
window = {
completion = cmp.config.window.bordered(),
documentation = cmp.config.window.bordered(),
},
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 = false }),
-- Tab: select next item or jump in snippet
["<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" }),
}),
sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "luasnip" },
{ name = "path" },
}, {
{ name = "buffer" },
}),
})

-- Command-line completion for / search
cmp.setup.cmdline("/", {
mapping = cmp.mapping.preset.cmdline(),
sources = { { name = "buffer" } },
})

-- Command-line completion for : commands
cmp.setup.cmdline(":", {
mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources(
{ { name = "path" } },
{ { name = "cmdline" } }
),
})
end,
},
}
Loading