-- nvim writing config -- Launch: NVIM_APPNAME=write nvim -- Plugins: native pack/ (no plugin manager) vim.g.mapleader = " " -- Disable netrw (snacks explorer replaces it) vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 -- Plugin settings (must be set before they load) vim.g.goyo_width = 80 vim.g["goyo_height"] = "90%" vim.g.limelight_conceal_ctermfg = 240 vim.g.limelight_conceal_guifg = "#585858" vim.g["pencil#wrapModeDefault"] = "soft" vim.g["pencil#textwidth"] = 80 -- Appearance vim.opt.number = false vim.opt.relativenumber = false vim.opt.signcolumn = "no" vim.opt.showmode = false vim.opt.ruler = false vim.opt.laststatus = 0 vim.opt.cmdheight = 1 vim.opt.fillchars = { eob = " " } vim.opt.termguicolors = true -- Kill all syntax highlighting vim.cmd("syntax off") vim.cmd("filetype plugin on") vim.cmd("filetype indent off") -- Colorscheme vim.opt.background = "dark" vim.cmd("colorscheme writing") -- Writing defaults vim.opt.wrap = true vim.opt.linebreak = true vim.opt.breakindent = true vim.opt.textwidth = 0 vim.opt.wrapmargin = 0 vim.opt.spell = true vim.opt.spelllang = "en_us" vim.opt.cursorline = true vim.opt.scrolloff = 8 vim.opt.sidescrolloff = 8 -- Tabs vim.opt.expandtab = true vim.opt.tabstop = 4 vim.opt.shiftwidth = 4 -- Search vim.opt.ignorecase = true vim.opt.smartcase = true vim.opt.hlsearch = false vim.opt.incsearch = true -- Misc vim.opt.swapfile = false vim.opt.backup = false vim.opt.undofile = true vim.opt.undodir = vim.fn.stdpath("data") .. "/undo" vim.opt.clipboard = "unnamedplus" vim.opt.mouse = "a" -- snacks.nvim (explorer + project picker) require("snacks").setup({ explorer = { enabled = true }, picker = { enabled = true, sources = { projects = { dev = { "~/.corpus/content" }, recent = false, }, }, }, }) -- Word count in statusline (shown in cmdline area) vim.api.nvim_create_autocmd({ "BufEnter", "CursorHold", "InsertLeave" }, { pattern = "*", callback = function() local wc = vim.fn.wordcount() local words = wc.visual_words or wc.words or 0 vim.opt.rulerformat = string.format("%%=%d words", words) vim.opt.ruler = true end, }) -- Goyo enter/leave hooks vim.api.nvim_create_autocmd("User", { pattern = "GoyoEnter", callback = function() vim.cmd("colorscheme writing") for _, win in ipairs(vim.api.nvim_list_wins()) do vim.api.nvim_win_set_option(win, "winhighlight", "Normal:Normal,NormalNC:Normal,EndOfBuffer:EndOfBuffer") end vim.cmd("Limelight") vim.opt.showmode = false vim.opt.showcmd = false end, }) vim.api.nvim_create_autocmd("User", { pattern = "GoyoLeave", callback = function() vim.cmd("Limelight!") vim.opt.showmode = false vim.opt.showcmd = false end, }) -- Goyo/explorer coordination local goyo_was_on = false local function goyo_is_active() return vim.fn.exists("#goyo") == 1 end local function goyo_suspend() if goyo_is_active() then goyo_was_on = true vim.cmd("Goyo!") end end local function goyo_restore() if goyo_was_on then goyo_was_on = false vim.defer_fn(function() vim.cmd("Goyo") end, 50) end end -- Keymaps local map = vim.keymap.set -- Explorer (suspend/restore Goyo around it) map("n", "e", function() local explorers = Snacks.picker.get({ source = "explorer" }) if #explorers > 0 then explorers[1]:close() goyo_restore() else goyo_suspend() Snacks.explorer.open() end end, { desc = "Toggle file explorer" }) -- Project switcher (content directories + extras) map("n", "fp", function() local content_root = vim.fn.expand("~/.corpus/content") local dirs = {} -- Grab subdirs from ~/.corpus/content local handle = vim.loop.fs_scandir(content_root) if handle then while true do local name, typ = vim.loop.fs_scandir_next(handle) if not name then break end if typ == "directory" and name:sub(1, 1) ~= "." then table.insert(dirs, { name = name, path = content_root .. "/" .. name, text = name }) end end end -- Extra content dirs outside .corpus/content table.insert(dirs, { name = "slipbox", path = vim.fn.expand("~/dev/slipbox"), text = "slipbox" }) table.sort(dirs, function(a, b) return a.name < b.name end) Snacks.picker({ title = "Content", items = dirs, format = function(item) return { { item.name } } end, confirm = function(picker, item) picker:close() if item then vim.cmd("cd " .. vim.fn.fnameescape(item.path)) Snacks.picker.files({ cwd = item.path }) end end, }) end, { desc = "Switch content project" }) -- File search map("n", "sf", function() Snacks.picker.files() end, { desc = "Search files" }) map("n", "fg", function() Snacks.picker.grep() end, { desc = "Grep" }) map("n", "ff", function() Snacks.picker.smart() end, { desc = "Smart find" }) map("n", "s.", function() Snacks.picker.recent() end, { desc = "Recent files" }) map("n", "", function() Snacks.picker.buffers() end, { desc = "Buffers" }) map("n", "/", function() Snacks.picker.lines() end, { desc = "Search in buffer" }) -- Writing tools map("n", "g", "Goyo", { desc = "Toggle Goyo" }) map("n", "l", "Limelight!!", { desc = "Toggle Limelight" }) map("n", "s", "set spell!", { desc = "Toggle spell check" }) map("n", "w", "w", { desc = "Save" }) map("n", "q", "q", { desc = "Quit" }) -- Navigate wrapped lines naturally map("n", "j", "gj") map("n", "k", "gk") -- Pencil + Goyo auto-init for prose files vim.api.nvim_create_autocmd("FileType", { pattern = { "markdown", "text", "tex" }, callback = function() vim.cmd("PencilSoft") end, }) -- Autosave on focus loss, idle, or leaving insert mode vim.api.nvim_create_autocmd({ "FocusLost", "CursorHold", "InsertLeave" }, { pattern = "*", callback = function() if vim.bo.modified and vim.bo.buftype == "" and vim.fn.expand("%") ~= "" then vim.cmd("silent! write") end end, }) -- Goyo on by default vim.api.nvim_create_autocmd("VimEnter", { callback = function() vim.cmd("Goyo") end, })