Skip to content
Draft
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
1 change: 0 additions & 1 deletion Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# Terminal tools
cask 'iterm2'
brew 'bash'
brew 'macvim'
brew 'neovim'
brew 'the_silver_searcher'
brew 'zsh'
Expand Down
284 changes: 284 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
# NeoVim Migration Plan

Migration from MacVim + Vundle + Vimscript to Neovim with its native
conventions and ecosystem. Phased to allow stopping at any phase boundary and
still having a working editor.

Scope: `home/.vimrc`, `home/.config/nvim/`, `Brewfile`, `init.zsh`, `.zshrc`
alias, and `README.md`.

---

## Phase 1 — Minimum cutover (this PR)

**Goal:** Stop launching MacVim. Start launching Neovim. Keep every plugin,
keybinding, and behavior identical. No Lua, no plugin manager changes, no
linter changes.

**Also addresses:** [#64][issue-64] (undodir workaround cleanup) by dropping
the explicit `undodir`/`mkdir` block — Neovim defaults to
`~/.local/state/nvim/undo//`, which is outside the homesick castle and
auto-created.

### Changes

1. Create `home/.config/nvim/init.vim` containing the full former `.vimrc`
contents, **minus** the `undodir` block.
- First line: `set runtimepath^=~/.vim runtimepath+=~/.vim/after` so Vundle
continues finding `~/.vim/bundle/`.
- Second line: `let &packpath = &runtimepath`.
2. Delete `home/.vimrc` entirely (no MacVim fallback to maintain).
3. Update `home/.zshrc` alias: `alias vim='nvim'` (was `alias vim='mvim -v'`).
4. Update `init.zsh` bootstrap: `nvim +PluginInstall +qall` (was
`vim +BundleInstall +qall`).
5. Remove `brew 'macvim'` from `Brewfile`.
6. Update `README.md`:
- Replace "Vim via Vundle" framing with "Neovim via Vundle (transitional)".
- Update the bootstrap step that says "installs Vundle plugins for MacVim".

### Manual cleanup (per-machine, not in repo)

- [ ] `rm -rf ~/.vim/undo` (the workaround directory from #63 is no longer
written to)
- [ ] `brew uninstall macvim` after confirming the alias swap works
- [ ] Verify `homesick link dotfiles` produces no `.un~` symlinks on a fresh
run (closes #64)

### Acceptance

- `nvim` opens with all 30+ plugins loaded from `~/.vim/bundle/`
- `vim` alias resolves to `nvim`
- No `.un~` files appear next to source files in the castle
- README accurately describes the current state

---

## Phase 2 — Neovim-compat hygiene (small, opportunistic)

**Goal:** Remove Vim-only quirks that are no-ops or warnings under Neovim.
Tiny PR, mechanical changes.

### Changes

- Remove `set pastetoggle=<F2>` — Neovim handles paste automatically via
bracketed-paste and the option is deprecated.
- Remove the explicit `set nocompatible` (Neovim is always non-compatible).
- Audit `set rtp+=~/.vim/bundle/Vundle.vim` and the `vundle#begin()` call —
these still work but flag for Phase 3.
- Remove `Plugin 'matchit.zip'` — Neovim ships matchit and loads it
automatically.

### Acceptance

- `nvim --headless +qall` produces no deprecation warnings related to our
config

---

## Phase 3 — Plugin manager: Vundle → lazy.nvim

**Goal:** Replace Vundle. This is the **foundational phase** for everything
that comes after, because most modern Neovim plugins assume a Lua-capable
manager (lazy.nvim, packer.nvim, etc.).

### Why lazy.nvim

- De-facto standard in 2025; actively maintained
- Lazy-loads by default → faster startup
- Lockfile (`lazy-lock.json`) → reproducible installs
- UI for plugin management (`:Lazy`)

### Changes

- Bootstrap `lazy.nvim` into `~/.local/share/nvim/lazy/lazy.nvim` from
`init.vim` (the bootstrap snippet from lazy.nvim docs).
- Convert every `Plugin '...'` line to a `lazy.nvim` spec in a new
`home/.config/nvim/lua/plugins.lua` (still keep `init.vim` as entry point —
Lua conversion comes in Phase 7).
- Drop `Plugin 'VundleVim/Vundle.vim'` and the `call vundle#begin()` /
`call vundle#end()` block.
- Remove the `~/.vim/bundle/` directory and the `set rtp+=~/.vim/...` line.
- Commit `lazy-lock.json` so machines stay in sync.
- Update `init.zsh`: `nvim --headless "+Lazy! sync" +qa`.
- Update `README.md` to describe lazy.nvim instead of Vundle.

### Plugins to drop during conversion

- `jQuery` — obsolete
- `Markdown` (vim-scripts version) — replace with `preservim/vim-markdown` or
defer to treesitter in Phase 6
- `Rename` — vim-eunuch already provides `:Rename`
- `vim-coffee-script` — assess actual usage; likely dead

### Acceptance

- `~/.vim/bundle/` is gone
- `:Lazy` opens the management UI
- `lazy-lock.json` is committed
- Startup time measurable improvement (`nvim --startuptime /tmp/start.log`)

---

## Phase 4 — Diagnostics: syntastic → built-in LSP + nvim-lint

**Goal:** Replace synchronous, slow `syntastic` with Neovim's async-native
diagnostics stack.

### Why

- `syntastic` blocks the UI on save (it shells out and waits)
- Neovim has `vim.diagnostic` and `vim.lsp` built in since 0.5
- LSP gives go-to-definition, hover docs, rename, etc. — free upside

### Changes

- Add `neovim/nvim-lspconfig` and `williamboman/mason.nvim` for managing LSP
servers
- Add `mfussenegger/nvim-lint` for the non-LSP linters you currently use via
syntastic:
- `eslint` (JS) → mostly replaced by `eslint-lsp` now
- `scss_lint`, `haml_lint`, `pyyaml`, `language_check` → keep via nvim-lint
- Add `lewis6991/gitsigns.nvim` to replace `airblade/vim-gitgutter` (also
async, with `vim.diagnostic`-style sign column)
- Remove `Plugin 'scrooloose/syntastic'` and `Plugin 'myint/syntastic-extras'`
- Remove all the `let g:syntastic_*` config blocks
- Configure LSP servers for the languages in your stack:
- Ruby: `ruby_lsp` or `solargraph`
- JS/TS: `ts_ls`
- Terraform: `terraformls`
- YAML: `yamlls`
- Bash: `bashls`

### Acceptance

- Save no longer pauses
- `<leader>` diagnostic keymaps (jump to next error, hover, etc.) work
- `:LspInfo` shows attached servers for each language

---

## Phase 5 — File navigation and search

**Goal:** Replace tree/search plugins with their modern Neovim equivalents.

### Changes

- Replace `scrooloose/nerdtree` with `nvim-tree/nvim-tree.lua` OR
`stevearc/oil.nvim` (oil.nvim is the modern choice — edits the filesystem
as a buffer)
- Replace `rking/ag.vim` and `gabesoft/vim-ags` with
`nvim-telescope/telescope.nvim` (live grep, file find, buffer list, LSP
pickers)
- Switch `the_silver_searcher` (ag) → `ripgrep` (rg) in `Brewfile` —
telescope and most modern tooling expect rg
- Update all `<Leader>s`, `<Leader>a`, `<Leader><Leader>a` mappings to call
telescope pickers
- Update `let g:NERDTreeHijackNetrw=1`, `let NERDTreeShowHidden=1`, and the
startup-NERDTree autocmd → equivalent nvim-tree/oil setup

### Acceptance

- `<Leader>s` on a word opens telescope grep results
- `<C-p>` or equivalent opens a fuzzy file picker
- Tree explorer of choice opens on startup

---

## Phase 6 — Treesitter + completion

**Goal:** Modern syntax highlighting and structural editing, plus real
completion.

### Changes

- Add `nvim-treesitter/nvim-treesitter` with parsers for: ruby, javascript,
typescript, tsx, html, scss, yaml, markdown, lua, vim, vimdoc, bash,
dockerfile, terraform, python
- Drop these syntax/ftplugin plugins (treesitter handles them):
- `pangloss/vim-javascript`
- `jelera/vim-javascript-syntax`
- `mtscout6/vim-cjsx`
- `mxw/vim-jsx` (and the `let g:jsx_ext_required = 0` line)
- `vim-coffee-script` (if not already dropped)
- Add `hrsh7th/nvim-cmp` + sources (`cmp-nvim-lsp`, `cmp-buffer`, `cmp-path`)
→ replaces `ervandew/supertab`
- Add `nvim-treesitter/nvim-treesitter-textobjects` → richer text objects,
may eventually replace `textobj-rubyblock`/`textobj-user`
- Optional: `windwp/nvim-autopairs` replacing `Raimondi/delimitMate`

### Acceptance

- Ruby/JS/etc. syntax highlighting visibly richer
- Tab triggers a completion menu sourced from LSP
- `:TSUpdate` keeps parsers fresh

---

## Phase 7 — Convert `init.vim` to `init.lua`

**Goal:** Pure Lua config. This is mostly mechanical translation, done last
so the previous phases stabilize first.

### Changes

- Create `home/.config/nvim/init.lua` and a `lua/` directory structure:
```
home/.config/nvim/
init.lua
lua/
options.lua -- everything that was set xxx
keymaps.lua -- everything that was map/nnoremap/etc.
autocmds.lua -- everything that was autocmd
plugins.lua -- lazy.nvim spec (moved from Phase 3)
lsp.lua -- LSP config (moved from Phase 4)
after/
ftplugin/
markdown.lua -- replaces the inline au BufRead *.md blocks
gitcommit.lua -- replaces the inline gitcommit autocmd
```
- Delete `home/.config/nvim/init.vim`
- Use `vim.opt`, `vim.keymap.set`, `vim.api.nvim_create_autocmd` instead of
their Vimscript equivalents
- Move filetype-specific behavior out of giant `au BufRead` blocks into
`after/ftplugin/<lang>.lua` files (Neovim convention)

### Acceptance

- `home/.config/nvim/init.vim` no longer exists
- `:checkhealth` reports green across the board
- All keybindings still work

---

## Out of scope (intentionally)

- Replacing `tpope/vim-fugitive` (still the best git plugin; no compelling
successor)
- Replacing `vim-rails`, `vim-eunuch`, `vim-abolish`, `vim-repeat`,
`vim-endwise`, `vim-surround` (all still maintained by tpope and work
natively in nvim)
- Replacing `thoughtbot/vim-rspec` — could move to `vim-test` or
`nvim-neotest/neotest` in a future phase but not necessary

---

## Issue tracking

| Phase | Issue |
|-------|-------|
| 1 | [#67][issue-67] (also closes [#64][issue-64]) |
| 2 | [#68][issue-68] |
| 3 | [#69][issue-69] |
| 4 | [#70][issue-70] |
| 5 | [#71][issue-71] |
| 6 | [#72][issue-72] |
| 7 | [#73][issue-73] |

[issue-64]: https://github.com/mattmenefee/dotfiles/issues/64
[issue-67]: https://github.com/mattmenefee/dotfiles/issues/67
[issue-68]: https://github.com/mattmenefee/dotfiles/issues/68
[issue-69]: https://github.com/mattmenefee/dotfiles/issues/69
[issue-70]: https://github.com/mattmenefee/dotfiles/issues/70
[issue-71]: https://github.com/mattmenefee/dotfiles/issues/71
[issue-72]: https://github.com/mattmenefee/dotfiles/issues/72
[issue-73]: https://github.com/mattmenefee/dotfiles/issues/73
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# dotfiles

Personal macOS development environment managed with [Homesick][homesick_link].
Configures Zsh (with [Oh My Zsh][oh_my_zsh_link]), Vim via [Vundle][vundle_link],
Ruby development tools via [rbenv][rbenv_link], and a curated set of
[Homebrew][homebrew_link] packages for web development. Uses
[mise][mise_link] for managing non-Ruby tool versions.
Configures Zsh (with [Oh My Zsh][oh_my_zsh_link]), Neovim (still using
[Vundle][vundle_link] for now — migration in progress), Ruby development
tools via [rbenv][rbenv_link], and a curated set of [Homebrew][homebrew_link]
packages for web development. Uses [mise][mise_link] for managing non-Ruby
tool versions.

## Getting Started

Expand Down Expand Up @@ -76,15 +77,16 @@ Ruby development tools via [rbenv][rbenv_link], and a curated set of
- **[zsh-syntax-highlighting][zsh_sh_link]** — highlights commands as you type
- **[zsh-autosuggestions][zsh_as_link]** — suggests commands from history as you type

1. Install [Vundle][vundle_link] and run the Vim plugin installer
1. Install [Vundle][vundle_link] and run the Neovim plugin installer

```shell
$ cd ~/.homesick/repos/dotfiles
$ zsh init.zsh
```

This installs Vundle plugins for MacVim. Neovim (also in the Brewfile)
uses a separate configuration.
This installs Vundle plugins for Neovim from `~/.config/nvim/init.vim`.
Vundle is transitional — see [`PLAN.md`](PLAN.md) for the migration to
[lazy.nvim][lazy_link].

1. Set up Git config

Expand Down Expand Up @@ -122,6 +124,7 @@ Oh My Zsh is configured to auto-update daily via `zstyle` settings in `.zshrc`.
[rbenv_link]: https://github.com/rbenv/rbenv
[rbenv_default_gems_link]: https://github.com/rbenv/rbenv-default-gems
[vundle_link]: https://github.com/VundleVim/Vundle.vim
[lazy_link]: https://github.com/folke/lazy.nvim
[mise_link]: https://mise.jdx.dev/
[omz_bundler]: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/bundler
[omz_docker]: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/docker
Expand Down
19 changes: 8 additions & 11 deletions home/.vimrc → home/.config/nvim/init.vim
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
" Make Neovim load plugins from ~/.vim/bundle (Vundle's default location).
set runtimepath^=~/.vim runtimepath+=~/.vim/after
let &packpath = &runtimepath

" RULE #1: Don't put any lines in your vimrc that you don't understand

" Ensure that legacy compatibility mode is off
Expand Down Expand Up @@ -25,13 +29,11 @@ Plugin 'textobj-user'
Plugin 'DrawIt'
Plugin 'Markdown'
Plugin 'Rename'
Plugin 'ervandew/supertab'
Plugin 'pangloss/vim-javascript'
Plugin 'scrooloose/nerdcommenter'
Plugin 'scrooloose/nerdtree'
Plugin 'scrooloose/syntastic'
Plugin 'myint/syntastic-extras'
Plugin 'flazz/vim-colorschemes'
Plugin 'tpope/vim-rails'
Plugin 'tpope/vim-eunuch'
Plugin 'vim-coffee-script'
Expand Down Expand Up @@ -88,20 +90,14 @@ set autoindent
set autoread
set autowrite
set confirm
set pastetoggle=<F2>
set shortmess=atI
set visualbell " stop Vim from beeping at me
" set cursorline " Highlight the current line

" Maintain the undo history even after the file is closed.
" Without an explicit undodir, Vim writes .un~ files next to the source — which
" inside the homesick castle means symlinks back into ~/Library, etc. The //
" suffix encodes the full path in the filename so undo histories don't collide.
" Neovim's default undodir (~/.local/state/nvim/undo//) lives outside the
" homesick castle and is auto-created, so no explicit undodir is needed.
set undofile
set undodir=~/.vim/undo//
if !isdirectory(expand(&undodir))
call mkdir(expand(&undodir), 'p')
endif

" Softtabs, 2 spaces
set tabstop=2
Expand Down Expand Up @@ -139,7 +135,8 @@ syntax on " are both of these necessary?

filetype plugin indent on

colorscheme railscasts
set termguicolors
colorscheme new-railscasts

map QQ :q<CR>
map WW :wall<CR>
Expand Down
Loading