<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>MOSAID Articles &amp; Tutorials - vim</title><link href="https://mosaid.xyz/" rel="alternate"></link><link href="https://mosaid.xyz/feeds/vim.atom.xml" rel="self"></link><id>https://mosaid.xyz/</id><updated>2026-05-29T12:19:58+00:00</updated><entry><title>The Ultimate Neovim Configuration: A Modular, Reproducible Developer Environment</title><link href="https://mosaid.xyz/articles/the-ultimate-neovim-configuration-a-modular-reproducible-developer-environment-19.html" rel="alternate"></link><published>2026-05-29T12:19:58+00:00</published><updated>2026-05-29T12:19:58+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-29:/articles/the-ultimate-neovim-configuration-a-modular-reproducible-developer-environment-19.html</id><summary type="html">&lt;p&gt;A complete walkthrough of a production‑grade Neovim configuration. Covers the modular directory layout, bootstrapping with lazy.nvim, per‑filetype engineering, custom Lua automation functions, programmable snippets, a deterministic colour stack, and a hand‑picked plugin dashboard. Every design decision is explained, and the full source is included so you can reproduce the environment on any machine.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section id="intro"&gt;
    &lt;h2&gt;Why “Ultimate”? A Philosophy, Not a Dump of Settings &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      An ultimate Neovim configuration isn’t the one with the most plugins—it’s
      the one that starts in under 100 ms, behaves identically on any machine,
      and feels like a custom‑built engineering console rather than a text editor.
      This article presents the full architecture of the setup I’ve refined over
      years of daily use: writing LaTeX, Python, shell scripts, HTML, and Markdown.
      It’s modular, lazy‑loaded, and completely deterministic. Download the included zip file at the end of the article,
      run &lt;code&gt;:Lazy sync&lt;/code&gt;, and every keybinding, colour, and snippet is
      exactly as intended.
    &lt;/p&gt;
    &lt;p&gt;
      The configuration is split into logical modules—each file has a single
      responsibility. This article walks through every piece, explaining not just
      the “what” but the “why”. All file contents are included; copy them directly
      into your own &lt;code&gt;~/.config/nvim&lt;/code&gt; and you’ll have a fully functional
      developer environment.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;nav&gt;
    &lt;h2&gt;Table of Contents&lt;/h2&gt;
    &lt;ol class="compact-list"&gt;
      &lt;li&gt;&lt;a href="#intro"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#directory-tree"&gt;Directory Tree&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#bootstrapper"&gt;The Bootstrapper — init.lua&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#lazy-loading"&gt;Lazy Loading with lazy.nvim&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#visual-stack"&gt;The Visual Stack — colors.lua&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#keymaps-commands"&gt;Global Keymaps &amp;amp; User Commands&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#automation-functions"&gt;Automation Functions — functions.lua&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#filetype-engines"&gt;Per‑Filetype Engines — ftplugin&lt;/a&gt;
        &lt;ol class="compact-list"&gt;
          &lt;li&gt;&lt;a href="#ftplugin-python"&gt;Python ftplugin&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="#ftplugin-tex"&gt;LaTeX ftplugin&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="#ftplugin-html-markdown"&gt;HTML / Markdown ftplugins&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="#shared-maps"&gt;Shared Visual Wrappers — html_markdown_maps.lua&lt;/a&gt;&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href="#snippets"&gt;Snippets as Code — LuaSnip Integration&lt;/a&gt;
        &lt;ol class="compact-list"&gt;
          &lt;li&gt;&lt;a href="#tex-snippets"&gt;LaTeX Snippets&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="#html-snippets"&gt;HTML / Markdown Snippets&lt;/a&gt;&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href="#plugin-dashboard"&gt;The Plugin Dashboard — core.lua&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#putting-together"&gt;Putting It All Together&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#reproducibility"&gt;Reproducibility &amp;amp; Final Thoughts&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/nav&gt;

  &lt;section id="directory-tree"&gt;
    &lt;h2&gt;Directory Tree &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      Neovim respects &lt;code&gt;~/.config/nvim/&lt;/code&gt; and expects Lua modules under
      &lt;code&gt;lua/&lt;/code&gt;. The tree below shows every file that makes up this
      configuration. Central files are highlighted in &lt;strong&gt;bold&lt;/strong&gt;.
    &lt;/p&gt;

    &lt;pre class="language-text"&gt;
&lt;code&gt;~/.config/nvim/
├── &lt;strong&gt;init.lua&lt;/strong&gt;                         ← entry point
├── lazy-lock.json                   ← pinned plugin versions
├── lua/
│   ├── config/
│   │   ├── &lt;strong&gt;lazy.lua&lt;/strong&gt;                 ← bootstraps lazy.nvim
│   │   ├── &lt;strong&gt;colors.lua&lt;/strong&gt;               ← themes &amp;amp; highlights
│   │   ├── &lt;strong&gt;commands.lua&lt;/strong&gt;             ← user commands
│   │   ├── &lt;strong&gt;functions.lua&lt;/strong&gt;            ← automation utilities
│   │   ├── &lt;strong&gt;keymaps.lua&lt;/strong&gt;              ← global keybindings
│   │   ├── &lt;strong&gt;html_markdown_maps.lua&lt;/strong&gt;   ← shared visual wrappers
│   │   ├── &lt;strong&gt;html_snippets.lua&lt;/strong&gt;        ← HTML / Markdown snippets
│   │   ├── &lt;strong&gt;tex_snippets.lua&lt;/strong&gt;         ← LaTeX snippets
│   │   └── options.lua
│   └── plugins/
│       └── &lt;strong&gt;core.lua&lt;/strong&gt;                 ← plugin specification
└── ftplugin/
    ├── &lt;strong&gt;python.lua&lt;/strong&gt;
    ├── &lt;strong&gt;tex.lua&lt;/strong&gt;
    ├── &lt;strong&gt;html.lua&lt;/strong&gt;
    ├── &lt;strong&gt;markdown.lua&lt;/strong&gt;
    └── lua.lua&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      Nothing is loaded until it’s needed. Global options live in
      &lt;code&gt;init.lua&lt;/code&gt;; everything else is deferred by lazy.nvim or
      triggered by filetype events.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="bootstrapper"&gt;
    &lt;h2&gt;The Bootstrapper — init.lua &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;init.lua&lt;/code&gt; is the first file Neovim reads. It sets the
      baseline: leader keys, editor options, persistent history and undo,
      a fallback colourscheme, and the lazy‑loading schedule.
    &lt;/p&gt;
    &lt;p&gt;Here is the complete file.&lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- Leader keys
vim.g.mapleader = ","
vim.g.maplocalleader = " "

-- Basic settings
vim.o.number = true
vim.o.relativenumber = true
vim.o.termguicolors = true
vim.o.wrap = true
vim.o.tabstop = 2
vim.o.shiftwidth = 2
vim.o.expandtab = true
vim.o.mouse = "a"
vim.o.cmdheight = 1
vim.opt.ignorecase = true
vim.opt.smartcase = true

-- Disable netrw (we use ranger / telescope)
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1

-- Maximum history and persistent undo
vim.opt.history = 10000
vim.opt.undolevels = 10000
vim.opt.undoreload = 100000
vim.opt.undofile = true
vim.opt.undodir = vim.fn.stdpath("data") .. "/undo"
vim.opt.shadafile = vim.fn.stdpath("data") .. "/shada/main.shada"
vim.opt.shada = "'100000,&amp;lt;1000,s100,h"
vim.cmd("silent! rshada")

-- Fallback colourscheme – editor never looks broken
vim.cmd("colorscheme desert")

-- Bootstrap lazy.nvim, then schedule the rest
require("config.lazy")
vim.schedule(function()
  require("config.colors").setup()
  require("config.keymaps")
  require("config.commands")
end)

-- Autocommands
-- Remove trailing whitespace on save
vim.api.nvim_create_autocmd("BufWritePre", {
  pattern = "*",
  callback = function()
    vim.cmd([[%s/\s\+$//e]])
  end
})

-- Remember last cursor position
vim.api.nvim_create_autocmd(&amp;quot;BufReadPost&amp;quot;, {
  pattern = &amp;quot;*&amp;quot;,
  callback = function()
    local last_pos = vim.fn.line([[&amp;#39;&amp;quot;]])
    if last_pos &amp;gt; 1 and last_pos &amp;lt;= vim.fn.line(&amp;quot;$&amp;quot;) then
      vim.cmd(&amp;quot;normal! g`\&amp;quot;&amp;quot;)
    end
  end,
})

-- Automatically set filetype for .tex files
vim.api.nvim_create_autocmd({"BufEnter", "BufNew", "BufNewFile", "BufRead"}, {
  pattern = "*.tex",
  callback = function()
    vim.bo.filetype = "tex"
  end
})

-- Change current directory to the directory of the current file (except /tmp)
vim.api.nvim_create_autocmd("BufReadPost", {
  callback = function()
    local dir = vim.fn.expand("%:p:h")
    if dir ~= "" and not dir:match("^/tmp") then
      vim.cmd("silent! lcd " .. dir)
    end
  end,
})

-- Briefly highlight yanked text
vim.api.nvim_create_autocmd("TextYankPost", {
  pattern = "*",
  callback = function()
    vim.highlight.on_yank({
      higroup = "IncSearch",
      timeout = 500,
    })
  end,
})
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Leader keys&lt;/strong&gt; – &lt;code&gt;,&lt;/code&gt; for global, &lt;code&gt;Space&lt;/code&gt; for local – keep custom mappings under the left hand.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Persistent state&lt;/strong&gt; – undo files and &lt;code&gt;shada&lt;/code&gt; (command/search history, marks) are stored in Neovim’s data directory and set to very high limits so nothing is ever lost.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Fallback colourscheme&lt;/strong&gt; – &lt;code&gt;desert&lt;/code&gt; is set synchronously to eliminate any flash of unstyled text. The real theme loads later.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Scheduled callback&lt;/strong&gt; – &lt;code&gt;vim.schedule()&lt;/code&gt; defers the remaining setup until after plugins are loaded, preventing “module not found” errors.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Autocommands&lt;/strong&gt; – small infrastructure pieces that keep diffs clean, restore cursor position, auto‑detect &lt;code&gt;.tex&lt;/code&gt; files, and provide instant yank feedback.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/section&gt;

  &lt;section id="lazy-loading"&gt;
    &lt;h2&gt;Lazy Loading with lazy.nvim &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;lua/config/lazy.lua&lt;/code&gt; is the only file that touches plugin
      management. It clones lazy.nvim if missing, then loads the plugin
      specification from &lt;code&gt;lua/plugins/core.lua&lt;/code&gt;.
    &lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git", "clone", "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git", lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  spec = { { import = "plugins" } },
  ui = { border = "rounded" },
})
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      The companion &lt;code&gt;lazy-lock.json&lt;/code&gt; (included in the zip file at the end of the article)
      pins every plugin commit, making the whole plugin set reproducible.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="visual-stack"&gt;
    &lt;h2&gt;The Visual Stack — colors.lua &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;lua/config/colors.lua&lt;/code&gt; applies the chosen theme (Nordic),
      sets custom highlights, makes most UI elements transparent, and
      re‑applies critical highlights after every colour scheme change.
    &lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local M = {}
function M.setup()
  vim.o.background = "dark"
  local ok, err = pcall(vim.cmd, "colorscheme nordic")
  if not ok then
    vim.cmd("colorscheme desert")
    vim.notify("Warning: Falling back to 'desert'. " .. tostring(err), vim.log.levels.WARN)
  end
  vim.wo.cursorline = true

  -- Explicit hex colours (independent of theme palette)
  vim.api.nvim_set_hl(0, "CursorLine",   { bg = "#3b4261", bold = true })
  vim.api.nvim_set_hl(0, "Cursor",       { bg = "#434C5E", fg = "#ECEFF4", blend = 60 })
  vim.api.nvim_set_hl(0, "StatusLine",   { bg = "#1f2335", fg = "#c0caf5", bold = true })
  vim.api.nvim_set_hl(0, "Visual",       { bg = "#FFD966", fg = "black" })
  vim.api.nvim_set_hl(0, "Search",       { bg = "#ff9e64", fg = "black", bold = true })
  vim.api.nvim_set_hl(0, "LineNr",       { fg = "#FFFF00" })
  vim.api.nvim_set_hl(0, "CursorLineNr", { fg = "#c0caf5", bold = true })
  -- … plus many more (see the full file)

  -- Transparency: structural UI gets no background
  local transparent = {
    "Normal", "NormalNC", "NormalFloat", "SignColumn", "MsgArea",
    "TelescopeNormal", "TelescopeBorder", "StatusLine", "StatusLineNC",
    "BufferLineFill", "BufferLineBackground", "LineNr", "CursorLineNr",
    "FloatBorder",
  }
  for _, grp in ipairs(transparent) do
    vim.api.nvim_set_hl(0, grp, { bg = "none" })
  end

  -- Re‑apply matching bracket highlights after every theme change
  vim.api.nvim_create_autocmd({ "ColorScheme", "User" }, {
    pattern = { "LazyDone", "VeryLazy" },
    callback = function()
      vim.api.nvim_set_hl(0, "MatchParen",    { bg = "#ff33aa", fg = "#000000", bold = true })
      vim.api.nvim_set_hl(0, "MatchParenCur", { bg = "#ff33aa", fg = "#000000", bold = true })
      vim.api.nvim_set_hl(0, "MatchWord",     { bg = "#5555ff", fg = "#000000" })
    end,
  })
end
return M
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      By using raw hex codes, the highlights stay identical whether you run
      Nordic, Tokyonight, or the fallback Desert. The autocommand on
      &lt;code&gt;ColorScheme&lt;/code&gt; ensures that even if a plugin reloads the
      theme, your custom highlights survive.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="keymaps-commands"&gt;
    &lt;h2&gt;Global Keymaps &amp;amp; User Commands &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;lua/config/keymaps.lua&lt;/code&gt; and
      &lt;code&gt;lua/config/commands.lua&lt;/code&gt; form a unified toolbelt.
      Every keybinding has a corresponding user command, and every
      command delegates to a single function. This makes the system
      auditable, remappable, and self‑documenting.
    &lt;/p&gt;

    &lt;p&gt;&lt;strong&gt;keymaps.lua&lt;/strong&gt;&lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local opts = { noremap = true, silent = true }
local functions = require("config.functions")

-- Clipboard
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;cc&amp;quot;, &amp;#39;gg&amp;quot;+yG&amp;#39;, opts)
vim.keymap.set(&amp;quot;v&amp;quot;, &amp;quot;&amp;lt;C-c&amp;gt;&amp;quot;, &amp;#39;&amp;quot;+y&amp;#39;, opts)
vim.keymap.set(&amp;quot;i&amp;quot;, &amp;quot;&amp;lt;C-v&amp;gt;&amp;quot;, &amp;#39;&amp;lt;C-r&amp;gt;+&amp;#39;, opts)

-- Buffer / window navigation
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;Tab&amp;gt;&amp;quot;, &amp;quot;:bn&amp;lt;CR&amp;gt;&amp;quot;, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;S-Tab&amp;gt;&amp;quot;, &amp;quot;:bp&amp;lt;CR&amp;gt;&amp;quot;, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;C-h&amp;gt;&amp;quot;, &amp;quot;&amp;lt;C-w&amp;gt;h&amp;quot;, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;C-j&amp;gt;&amp;quot;, &amp;quot;&amp;lt;C-w&amp;gt;j&amp;quot;, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;C-k&amp;gt;&amp;quot;, &amp;quot;&amp;lt;C-w&amp;gt;k&amp;quot;, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;C-l&amp;gt;&amp;quot;, &amp;quot;&amp;lt;C-w&amp;gt;l&amp;quot;, opts)

-- Automation tools
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;wc&amp;quot;,    functions.word_count, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;cc&amp;quot;,    functions.command_history_explorer, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;rr&amp;quot;,    functions.output_regs, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;hh&amp;quot;,    functions.wrap_code_in_buffer, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;ranger&amp;quot;,functions.ranger_chooser, opts)
vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;mm&amp;quot;,    functions.output_old_files, opts)

-- etc. (see the full file)
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;p&gt;&lt;strong&gt;commands.lua&lt;/strong&gt;&lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local functions = require("config.functions")

vim.api.nvim_create_user_command("WC", functions.word_count, {})
vim.api.nvim_create_user_command("Mm", functions.output_old_files, {})
vim.api.nvim_create_user_command("Cc", functions.command_history_explorer, {})
vim.api.nvim_create_user_command("Ss", functions.search_command_history, {})
vim.api.nvim_create_user_command("Rr", functions.output_regs, {})
vim.api.nvim_create_user_command("HH", functions.wrap_code_in_buffer, {})
vim.api.nvim_create_user_command("RangerChooser", functions.ranger_chooser, {})
vim.api.nvim_create_user_command("SaveVimInfo", functions.save_vim_bindings_and_functions, {})
vim.api.nvim_create_user_command("EngType", functions.eng_type, {})
vim.api.nvim_create_user_command("FrType", functions.fr_type, {})
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      Mnemonic prefixes make the system self‑documenting:
      &lt;code&gt;&amp;lt;leader&amp;gt;wc&lt;/code&gt; → &lt;strong&gt;w&lt;/strong&gt;ord &lt;strong&gt;c&lt;/strong&gt;ount,
      &lt;code&gt;&amp;lt;leader&amp;gt;cc&lt;/code&gt; → &lt;strong&gt;c&lt;/strong&gt;ommand history, etc.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="automation-functions"&gt;
    &lt;h2&gt;Automation Functions — functions.lua &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;lua/config/functions.lua&lt;/code&gt; is the heart of the automation
      layer. It provides async Python/LaTeX runners, a searchable command
      history, a register inspector, an “old files” browser, a ranger
      file‑chooser, buffer‑wrapping utilities, and more. All are written in
      pure Lua with no plugin dependencies.
    &lt;/p&gt;

    &lt;h3&gt;Asynchronous Python Execution&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.run_python_selection()
  local mode = vim.fn.mode()
  if mode ~= 'v' and mode ~= 'V' then return end
  local save_reg = vim.fn.getreg('"')
  vim.cmd('normal! gvy')
  local code = vim.fn.getreg('"')
  vim.fn.setreg('"', save_reg)
  code = "from math import *\n" .. code:gsub('^%s*', ''):gsub('%s*$', '')
  local output = vim.fn.system({'python3', '-c', code})
  vim.cmd('enew')
  vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(output, "\n"))
  vim.bo.buftype = 'nofile'
  vim.bo.bufhidden = 'wipe'
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;Command History Explorer&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.command_history_explorer()
  local history = {}
  for i = vim.fn.histnr(':'), 1, -1 do
    local cmd = vim.fn.histget(':', i)
    if cmd ~= "" then table.insert(history, cmd) end
  end
  vim.cmd('enew')
  local buf = vim.api.nvim_get_current_buf()
  vim.api.nvim_buf_set_lines(buf, 0, -1, false, history)
  vim.bo[buf].buftype = &amp;#39;nofile&amp;#39;
  vim.bo[buf].modifiable = false
  vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;CR&amp;gt;&amp;#39;, function()
    local cmd = vim.fn.getline(&amp;#39;.&amp;#39;)
    vim.cmd(&amp;#39;bd! &amp;#39; .. buf)
    vim.cmd(cmd)
  end, { buffer = buf })
  vim.cmd('normal! gg')
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;Register Inspector&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.output_regs()
  vim.cmd('enew')
  local content = {}
  for reg = string.byte('a'), string.byte('z') do
    local ch = string.char(reg)
    local val = vim.fn.getreg(ch)
    if val ~= '' then
      table.insert(content, ch .. ":")
      table.insert(content, val)
      table.insert(content, "")
    end
  end
  vim.api.nvim_buf_set_lines(0, 0, -1, false, content)
  vim.bo.buftype = 'nofile'
  vim.bo.bufhidden = 'wipe'
  vim.cmd('normal! gg')
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;p&gt;
      The file also contains &lt;code&gt;run_python_and_replace&lt;/code&gt;,
      &lt;code&gt;output_old_files&lt;/code&gt;, &lt;code&gt;ranger_chooser&lt;/code&gt;,
      &lt;code&gt;wrap_code_in_buffer&lt;/code&gt;, &lt;code&gt;word_count&lt;/code&gt;,
      &lt;code&gt;select_inside_any_pair&lt;/code&gt;, and several language‑specific
      helpers. You can find the complete source  in the included zip file at the end of the article.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="filetype-engines"&gt;
    &lt;h2&gt;Per‑Filetype Engines — ftplugin &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      Filetype‑specific behaviour lives in &lt;code&gt;ftplugin/&lt;/code&gt;. Each file
      sets buffer‑local options and defines insert‑mode expansions. Visual‑mode
      wrappers for HTML and Markdown are centralised in a shared module to
      avoid duplication.
    &lt;/p&gt;

    &lt;h3 id="ftplugin-python"&gt;Python ftplugin&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- ftplugin/python.lua
vim.bo.tabstop = 4
vim.bo.shiftwidth = 4
vim.bo.expandtab = true
vim.bo.textwidth = 88

-- Run buffer asynchronously (full function omitted for brevity)
vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;c&amp;#39;, run_python_buffer, { buffer = true })

-- Insert-mode expansions
local imaps = {
  [&amp;#39;if&amp;#39;]    = &amp;#39;if :&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:a&amp;#39;,
  [&amp;#39;for&amp;#39;]   = &amp;#39;for in :&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:i&amp;#39;,
  [&amp;#39;def&amp;#39;]   = &amp;#39;def ():&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F(a&amp;#39;,
  [&amp;#39;class&amp;#39;] = &amp;#39;class :&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:a&amp;#39;,
  [&amp;#39;imp&amp;#39;]   = &amp;#39;import &amp;#39;,
  [&amp;#39;from&amp;#39;]  = &amp;#39;from import &amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:i&amp;#39;,
  -- … many more
}
for lhs, rhs in pairs(imaps) do
  vim.keymap.set(&amp;#39;i&amp;#39;, lhs, rhs, { buffer = true })
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3 id="ftplugin-tex"&gt;LaTeX ftplugin&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- ftplugin/tex.lua (excerpt)
vim.bo.tabstop = 2
vim.bo.shiftwidth = 2

-- Async compilation and PDF viewer
vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;c&amp;#39;, run_tex,  { buffer = true })
vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;o&amp;#39;, view_tex, { buffer = true })
vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;w&amp;#39;, wrap_buffer_in_html_tags, { buffer = true })

-- Insert-mode shortcuts for commands, environments, and symbols
local imaps = {
  [&amp;#39;!&amp;#39;] = &amp;#39;\\&amp;#39;,
  [&amp;#39;qq&amp;#39;] = &amp;#39;\\quad &amp;#39;,
  [&amp;#39;,bf&amp;#39;] = &amp;#39;\\textbf{}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a&amp;#39;,
  [&amp;#39;,u&amp;#39;]  = &amp;#39;\\underline{}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a&amp;#39;,
  -- … dozens more
}
for lhs, rhs in pairs(imaps) do
  vim.keymap.set(&amp;#39;i&amp;#39;, lhs, rhs, { buffer = true })
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3 id="ftplugin-html-markdown"&gt;HTML &amp;amp; Markdown ftplugins&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- ftplugin/html.lua
vim.bo.tabstop = 2
vim.bo.shiftwidth = 2
local imaps = {
  [&amp;#39;!&amp;#39;] = &amp;#39;&amp;lt;!--  --&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F i&amp;#39;,
  [&amp;#39;&amp;lt;&amp;#39;] = &amp;#39;&amp;lt;&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F&amp;gt;a&amp;#39;,
}
for lhs, rhs in pairs(imaps) do
  vim.keymap.set(&amp;#39;i&amp;#39;, lhs, rhs, { buffer = true })
end
require(&amp;quot;config.html_markdown_maps&amp;quot;)()

-- ftplugin/markdown.lua
vim.bo.tabstop = 2
vim.bo.shiftwidth = 2
require("config.html_markdown_maps")()
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3 id="shared-maps"&gt;Shared Visual Wrappers — html_markdown_maps.lua&lt;/h3&gt;
    &lt;p&gt;
      This module registers visual‑mode mappings that work identically in
      HTML and Markdown buffers. It uses a pure‑Lua &lt;code&gt;wrap_visual_text&lt;/code&gt;
      function that handles linewise, characterwise, and blockwise selections.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- lua/config/html_markdown_maps.lua (core wrapping function)
local function wrap_visual_text(open_tag, close_tag)
  local mode = vim.fn.visualmode()
  -- … normalises start/end, gets the visual selection,
  -- and wraps it with open_tag .. text .. close_tag
  -- (full implementation in the included zip file at the end of the article)
end

return function()
  vim.keymap.set(&amp;#39;v&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;pp&amp;#39;, function() wrap_visual_text(&amp;#39;&amp;lt;p&amp;gt;&amp;#39;, &amp;#39;&amp;lt;/p&amp;gt;&amp;#39;) end)
  vim.keymap.set(&amp;#39;v&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;hh&amp;#39;, function() wrap_visual_text(&amp;#39;&amp;lt;h2&amp;gt;&amp;#39;, &amp;#39;&amp;lt;/h2&amp;gt;&amp;#39;) end)
  -- … and many more wrapping / escaping shortcuts
end
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      Additionally, the file defines a set of number‑prefixed visual maps
      (&lt;code&gt;1&lt;/code&gt;–&lt;code&gt;9&lt;/code&gt;) for common transformations: escaping
      symbols, wrapping in code blocks, URL‑encoding, and more. See the
       included zip file at the end of the article for the full content.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="snippets"&gt;
    &lt;h2&gt;Snippets as Code — LuaSnip Integration &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      Snippets are defined in two files: &lt;code&gt;tex_snippets.lua&lt;/code&gt; and
      &lt;code&gt;html_snippets.lua&lt;/code&gt;. They use LuaSnip’s function nodes for
      dynamic behaviour, read legacy snippet files on demand, and auto‑number
      LaTeX exercises based on buffer content.
    &lt;/p&gt;

    &lt;h3 id="tex-snippets"&gt;LaTeX Snippets&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- tex_snippets.lua (excerpt)
local ls = require("luasnip")
local s = ls.snippet
local i = ls.insert_node
local t = ls.text_node
local f = ls.function_node

-- Auto‑numbered exercise environment
s(&amp;quot;exe&amp;quot;, {
  t(&amp;quot;\\begin{exe}{&amp;quot;), i(1, &amp;quot;&amp;lt;++&amp;gt;&amp;quot;), t(&amp;quot;}&amp;quot;),
  t({&amp;quot;&amp;quot;, &amp;quot;    &amp;quot;}), f(function()
    local count = 0
    for _, line in ipairs(vim.api.nvim_buf_get_lines(0, 0, -1, false)) do
      if line:match(&amp;quot;\\begin{exe}&amp;quot;) then count = count + 1 end
    end
    return tostring(count)
  end),
  t({&amp;quot;&amp;quot;, &amp;quot;    &amp;lt;++&amp;gt;&amp;quot;, &amp;quot;\\end{exe}&amp;quot;}),
})
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      The file also wraps legacy snippet files from
      &lt;code&gt;~/.vim/snippets/latex/&lt;/code&gt; so they appear as native LuaSnip
      snippets, and exports functions that can be called directly from
      insert‑mode mappings (e.g., &lt;code&gt;,tcb&lt;/code&gt; in the LaTeX ftplugin).
    &lt;/p&gt;

    &lt;h3 id="html-snippets"&gt;HTML / Markdown Snippets&lt;/h3&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- html_snippets.lua
local snippets = {
  s(&amp;quot;fig&amp;quot;, {
    t(&amp;quot;&amp;lt;figure&amp;gt;&amp;quot;),
    t({ &amp;quot;&amp;quot;, &amp;quot;    &amp;quot; }),
    t(&amp;#39;&amp;lt;img src=&amp;quot;/theme/images/articles/&amp;#39;), i(1, &amp;quot;image.png&amp;quot;),
    t(&amp;#39;&amp;quot; alt=&amp;quot;&amp;#39;), i(2, &amp;quot;description&amp;quot;), t(&amp;#39;&amp;quot; style=&amp;quot;max-width:100%;height:auto;&amp;quot; &amp;gt;&amp;#39;),
    t({ &amp;quot;&amp;quot;, &amp;quot;    &amp;quot; }),
    t(&amp;quot;&amp;lt;figcaption&amp;gt;&amp;quot;), i(3, &amp;quot;caption&amp;quot;), t(&amp;quot;&amp;lt;/figcaption&amp;gt;&amp;quot;),
    t({ &amp;quot;&amp;quot;, &amp;quot;&amp;lt;/figure&amp;gt;&amp;quot; }),
    i(0),
  }),
  -- … anchor link, code block with raw tags, etc.
}
ls.add_snippets("html", snippets)
ls.add_snippets("markdown", snippets)
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section id="plugin-dashboard"&gt;
    &lt;h2&gt;The Plugin Dashboard — core.lua &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;lua/plugins/core.lua&lt;/code&gt; lists every plugin. Each one
      lazy‑loads on an event, command, or filetype. The full specification
      is shown below.
    &lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
return {
  -- Themes (loaded immediately)
  { "AlexvZyl/nordic.nvim",          lazy = false, priority = 1000 },
  { "folke/tokyonight.nvim",         lazy = false, priority = 1000 },

  -- Telescope (fuzzy finder)
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    cmd = "Telescope",
    config = function()
      local builtin = require(&amp;quot;telescope.builtin&amp;quot;)
      vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;ff&amp;quot;, builtin.find_files)
      vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;fg&amp;quot;, builtin.live_grep)
      vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;fb&amp;quot;, builtin.buffers)
      vim.keymap.set(&amp;quot;n&amp;quot;, &amp;quot;&amp;lt;leader&amp;gt;fh&amp;quot;, builtin.help_tags)
    end,
  },

  -- Treesitter (syntax highlighting and indentation)
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    event = { "BufReadPost", "BufNewFile" },
    config = function()
      require("nvim-treesitter.configs").setup({
        ensure_installed = {
          "lua", "python", "javascript", "html", "css", "bash", "c", "cpp", "json", "yaml",
        },
        highlight = { enable = true },
        indent = { enable = true },
      })
    end,
  },

  -- Mason (LSP/DAP/linter manager)
  {
    "williamboman/mason.nvim",
    build = ":MasonUpdate",
    config = function() require("mason").setup() end,
  },

  -- nvim-cmp + LuaSnip (completion &amp; snippets)
  {
    "hrsh7th/nvim-cmp",
    dependencies = {
      "hrsh7th/cmp-nvim-lsp",
      "hrsh7th/cmp-buffer",
      "hrsh7th/cmp-path",
      "saadparwaiz1/cmp_luasnip",
      "L3MON4D3/LuaSnip",
    },
    event = "InsertEnter",
    config = function()
      local cmp = require("cmp")
      local luasnip = require("luasnip")
      vim.o.completeopt = "menu,menuone,noselect"
      cmp.setup({
        snippet = {
          expand = function(args) luasnip.lsp_expand(args.body) end,
        },
        mapping = cmp.mapping.preset.insert({
          [&amp;#39;&amp;lt;C-Space&amp;gt;&amp;#39;] = cmp.mapping.complete(),
          [&amp;#39;&amp;lt;CR&amp;gt;&amp;#39;] = cmp.mapping.confirm({ select = true }),
          [&amp;#39;&amp;lt;Tab&amp;gt;&amp;#39;] = 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, { &amp;quot;i&amp;quot;, &amp;quot;s&amp;quot; }),
          [&amp;#39;&amp;lt;S-Tab&amp;gt;&amp;#39;] = cmp.mapping(function(fallback)
            if cmp.visible() then cmp.select_prev_item()
            elseif luasnip.jumpable(-1) then luasnip.jump(-1)
            else fallback() end
          end, { &amp;quot;i&amp;quot;, &amp;quot;s&amp;quot; }),
        }),
        sources = cmp.config.sources({
          { name = 'nvim_lsp' },
          { name = 'luasnip' },
          { name = 'buffer' },
          { name = 'path' },
        }),
      })
      -- Load our custom snippets
      require("config.tex_snippets")
      require("config.html_snippets")
    end,
  },

  -- Minimal bufferline
  {
    "akinsho/bufferline.nvim",
    version = "*",
    config = function()
      require("bufferline").setup({
        options = {
          mode = "buffers",
          show_buffer_close_icons = false,
          show_close_icon = false,
          truncate_names = false,
          diagnostics = false,
          separator_style = { "|", "|" },
          always_show_bufferline = true,
          numbers = "none",
        },
      })
    end,
  },
}
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      Equally important is what’s &lt;strong&gt;not&lt;/strong&gt; included: no file‑tree
      plugin (ranger and Telescope cover navigation), no statusline plugin
      (the built‑in one plus custom highlights suffice), and no AI‑powered
      completion. Every plugin must justify its presence.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="putting-together"&gt;
    &lt;h2&gt;Putting It All Together &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      When you start Neovim:
    &lt;/p&gt;
    &lt;ol class="compact-list"&gt;
      &lt;li&gt;&lt;code&gt;init.lua&lt;/code&gt; sets options, loads &lt;code&gt;lazy.nvim&lt;/code&gt;,
          and schedules the colour setup.&lt;/li&gt;
      &lt;li&gt;Lazy.nvim parses &lt;code&gt;plugins/core.lua&lt;/code&gt; and begins loading
          plugins by their lazy‑load triggers.&lt;/li&gt;
      &lt;li&gt;The scheduled callback applies the theme, defines global keymaps
          and commands.&lt;/li&gt;
      &lt;li&gt;When a buffer is opened, Treesitter attaches, the appropriate
          ftplugin fires, and snippets register for that filetype.&lt;/li&gt;
      &lt;li&gt;On &lt;code&gt;InsertEnter&lt;/code&gt;, nvim‑cmp activates with all snippet
          and LSP sources ready.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;
      At no point does the user wait for a progress bar. The entire plugin set,
      once cached, starts in under 100 milliseconds.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section id="reproducibility"&gt;
    &lt;h2&gt;Reproducibility &amp;amp; Final Thoughts &lt;a href="#top" style="color:green;"&gt;Top&lt;/a&gt; &lt;/h2&gt;
    &lt;p&gt;
      This configuration is a build artifact. The &lt;code&gt;lazy-lock.json&lt;/code&gt;
      pins every plugin version, the colours are hard‑coded hex values, and
      every snippet and function is defined in Lua. To replicate the
      environment on a new machine, you only need:
    &lt;/p&gt;
    &lt;br&gt;
   &lt;div class="download-buttons"&gt;
        &lt;a class="btn btn-success btn-lg"
           href="/extra/archives/nvim-full-config-2026.zip"
           target="_blank"&gt;
            📥 Download  neovim full config 2026
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;br&gt;

    &lt;pre class="language-bash"&gt;
&lt;code class="language-bash"&gt;
    # extract the zip file and
    cp nvim ~/.config/nvim
    nvim --headless "+Lazy sync" +qa
&lt;/code&gt;
&lt;/pre&gt;

    &lt;p&gt;
      After that, your editor will start in under 100 ms and behave exactly
      as it does today.
    &lt;/p&gt;
    &lt;p&gt;
      The seven companion articles dive deeper into each subsystem: the
      bootstrapping layer, filetype engines, custom functions, snippets,
      colours, keymaps/commands, and the plugin ecosystem. But the article
      you’ve just read contains the complete source and the reasoning you
      need to build your own ultimate Neovim setup.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="lazy.nvim"></category><category term="init.lua"></category><category term="ftplugin"></category><category term="lua"></category><category term="automation"></category><category term="snippets"></category><category term="colorscheme"></category><category term="treesitter"></category><category term="nvim-cmp"></category><category term="configuration"></category><category term="deterministic"></category><category term="engineering"></category><category term="productivity"></category></entry><entry><title>The Developer’s Dashboard: Completion, Treesitter, and the Plugin Ecosystem</title><link href="https://mosaid.xyz/articles/the-developer%E2%80%99s-dashboard-completion-treesitter-and-the-plugin-ecosystem-12.html" rel="alternate"></link><published>2026-05-15T18:03:11+00:00</published><updated>2026-05-15T18:03:11+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/the-developer’s-dashboard-completion-treesitter-and-the-plugin-ecosystem-12.html</id><summary type="html">&lt;p&gt;This final article in the series examines the plugin layer that completes the Neovim configuration. It covers Treesitter syntax parsing, nvim‑cmp completion with LuaSnip integration, Mason for LSP management, and a minimal bufferline. Every plugin is justified by a clear engineering need, and the complete plugins/core.lua is dissected.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;Plugins with Purpose&lt;/h2&gt;
    &lt;p&gt;
      A Neovim configuration can easily drown in plugins.  This setup takes the opposite approach:
      every plugin must solve a problem that cannot be addressed with a few lines of Lua.  The
      result is a lean dashboard where Treesitter provides structural understanding, nvim‑cmp
      offers intelligent completion, LuaSnip gives programmable snippets, and bufferline keeps
      tabs visible without visual clutter.
    &lt;/p&gt;
    &lt;p&gt;
      In this final article we open &lt;code&gt;lua/plugins/core.lua&lt;/code&gt; – the single file that
      defines the entire plugin set.  We’ll examine why each plugin was chosen, how it’s configured,
      and how it integrates with the custom functions and snippets we’ve built throughout the series.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Complete Plugin Specification&lt;/h2&gt;
    &lt;p&gt;Below is the full &lt;code&gt;plugins/core.lua&lt;/code&gt; file, broken into sections for discussion.&lt;/p&gt;

    &lt;h3&gt;Themes: Nordic and Tokyonight&lt;/h3&gt;
    &lt;p&gt;
      Two themes are loaded with &lt;code&gt;lazy = false&lt;/code&gt; and high priority, ensuring they are
      available when &lt;code&gt;colors.lua&lt;/code&gt; runs its scheduled setup.  Both are configured with
      transparent backgrounds – Nordic via its &lt;code&gt;transparent&lt;/code&gt; option, Tokyonight via
      &lt;code&gt;transparent = true&lt;/code&gt;.  The fallback logic (Article #5) means the editor is never
      without a colourscheme.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
{
  "AlexvZyl/nordic.nvim",
  lazy = false,
  priority = 1000,
  config = function()
    require("nordic").setup({
      transparent = {
        bg = true,
        float = true,
      },
    })
    require("nordic").load()
  end,
},

{
  "folke/tokyonight.nvim",
  lazy = false,
  priority = 1000,
  opts = {
    style = "storm",
    transparent = true,
    styles = {
      comments = { italic = true },
      keywords = { italic = true },
    },
  },
},
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;Telescope: Fuzzy Everything&lt;/h3&gt;
    &lt;p&gt;
      Telescope is loaded on the &lt;code&gt;Telescope&lt;/code&gt; command (not at startup) and provides
      three essential fuzzy finders: files, live grep, and buffers.  The keymaps
      (&lt;code&gt;&amp;lt;leader&amp;gt;ff&lt;/code&gt;, &lt;code&gt;&amp;lt;leader&amp;gt;fg&lt;/code&gt;, &lt;code&gt;&amp;lt;leader&amp;gt;fb&lt;/code&gt;)
      are defined in its &lt;code&gt;config&lt;/code&gt; callback.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
{
  "nvim-telescope/telescope.nvim",
  dependencies = { "nvim-lua/plenary.nvim" },
  cmd = "Telescope",
  config = function()
    local builtin = require("telescope.builtin")
    vim.keymap.set("n", "&lt;leader&gt;ff", builtin.find_files)
    vim.keymap.set("n", "&lt;leader&gt;fg", builtin.live_grep)
    vim.keymap.set("n", "&lt;leader&gt;fb", builtin.buffers)
    vim.keymap.set("n", "&lt;leader&gt;fh", builtin.help_tags)
  end,
},
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;Treesitter: The Backbone&lt;/h3&gt;
    &lt;p&gt;
      Treesitter provides structural syntax highlighting and indentation.  It’s loaded on
      &lt;code&gt;BufReadPost&lt;/code&gt; and &lt;code&gt;BufNewFile&lt;/code&gt; – never at startup – and a minimal
      set of parsers is installed.  The &lt;code&gt;highlight&lt;/code&gt; and &lt;code&gt;indent&lt;/code&gt; modules
      are enabled; both are non‑negotiable for accurate editing.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
{
  "nvim-treesitter/nvim-treesitter",
  build = ":TSUpdate",
  event = { "BufReadPost", "BufNewFile" },
  config = function()
    require("nvim-treesitter.configs").setup({
      ensure_installed = {
        "lua", "python", "javascript", "html", "css", "bash", "c", "cpp", "json", "yaml",
      },
      highlight = { enable = true },
      indent = { enable = true },
    })
  end,
},
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;nvim-cmp and LuaSnip: The Completion Engine&lt;/h3&gt;
    &lt;p&gt;
      Completion is the most complex subsystem.  &lt;code&gt;nvim-cmp&lt;/code&gt; is loaded on
      &lt;code&gt;InsertEnter&lt;/code&gt; and configured with four sources: LSP, LuaSnip, buffer text,
      and file paths.  The snippet expansion is handled by LuaSnip; the &lt;code&gt;Tab&lt;/code&gt; key
      is overloaded to both cycle completion items and expand/advance snippets.
    &lt;/p&gt;
    &lt;p&gt;
      Critically, after &lt;code&gt;cmp.setup()&lt;/code&gt;, the configuration loads the snippet files
      we analysed in Article #4 – &lt;code&gt;config.tex_snippets&lt;/code&gt; and
      &lt;code&gt;config.html_snippets&lt;/code&gt; – ensuring snippets are registered before the user
      starts typing.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
{
  "hrsh7th/nvim-cmp",
  dependencies = {
    "hrsh7th/cmp-nvim-lsp",
    "hrsh7th/cmp-buffer",
    "hrsh7th/cmp-path",
    "saadparwaiz1/cmp_luasnip",
    "L3MON4D3/LuaSnip",
  },
  event = "InsertEnter",
  config = function()
    local cmp = require("cmp")
    local luasnip = require("luasnip")

    vim.o.completeopt = "menu,menuone,noselect"

    cmp.setup({
      snippet = {
        expand = function(args)
          luasnip.lsp_expand(args.body)
        end,
      },
      mapping = cmp.mapping.preset.insert({
        ['&lt;C-Space&gt;'] = cmp.mapping.complete(),
        ['&lt;CR&gt;'] = cmp.mapping.confirm({ select = true }),
        ['&lt;Tab&gt;'] = 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" }),
        ['&lt;S-Tab&gt;'] = 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 = 'buffer' },
        { name = 'path' },
      }),
    })

    -- Load LaTeX snippets
    require("config.tex_snippets")
    -- Load HTML snippets
    require("config.html_snippets")
  end,
},
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;Mason: LSP Without the Pain&lt;/h3&gt;
    &lt;p&gt;
      Mason manages LSP servers, linters, and formatters.  It’s loaded with &lt;code&gt;build = ":MasonUpdate"&lt;/code&gt;
      to ensure the registry is up‑to‑date.  The configuration is minimal – just
      &lt;code&gt;require("mason").setup()&lt;/code&gt;.  The LSP servers are currently commented out in
      this configuration, but the infrastructure is ready to be uncommented when needed.
      Keeping Mason in the plugin list even without active LSP servers means
      &lt;code&gt;:MasonInstall&lt;/code&gt; is always available.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
{
  "williamboman/mason.nvim",
  build = ":MasonUpdate",
  config = function() require("mason").setup() end,
},
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;h3&gt;Bufferline: Visible Tabs, Minimalist&lt;/h3&gt;
    &lt;p&gt;
      Bufferline shows open buffers as tabs.  It’s configured to be as minimal as possible:
      no close icons, no diagnostics, no numbers, just separator characters between buffer
      names.  &lt;code&gt;always_show_bufferline = true&lt;/code&gt; prevents it from disappearing when
      only one buffer is open.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
{
  "akinsho/bufferline.nvim",
  version = "*",
  config = function()
    require("bufferline").setup({
      options = {
        mode = "buffers",
        show_buffer_close_icons = false,
        show_close_icon = false,
        show_tab_indicators = false,
        truncate_names = false,
        diagnostics = false,
        indicator = { style = "none" },
        separator_style = { "|", "|" },
        always_show_bufferline = true,
        numbers = "none",
      },
    })
  end,
},
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Plugin Philosophy: What’s Missing&lt;/h2&gt;
    &lt;p&gt;
      Equally important is what’s &lt;em&gt;not&lt;/em&gt; in this configuration:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;No file‑tree plugin&lt;/strong&gt; – ranger (Article #6) and Telescope cover file
          navigation without a permanent sidebar.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;No statusline plugin&lt;/strong&gt; – Neovim’s built‑in statusline is sufficient,
          and custom highlights keep it readable.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;No Git gutter&lt;/strong&gt; – Git operations are handled in the terminal, keeping
          the editor free of constant change‑tracking visual noise.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;No AI completion&lt;/strong&gt; – completion is limited to deterministic sources:
          LSP, snippets, buffer words, and file paths.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      Every omission is a deliberate choice.  Each plugin that is included must justify itself
      every time the editor starts, and none is loaded until needed.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;How the Dashboard Comes Together&lt;/h2&gt;
    &lt;p&gt;
      When Neovim starts:
    &lt;/p&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;code&gt;init.lua&lt;/code&gt; sets options, loads lazy.nvim, and schedules the colour setup.&lt;/li&gt;
      &lt;li&gt;Lazy.nvim parses &lt;code&gt;plugins/core.lua&lt;/code&gt; and begins loading plugins by their
          lazy‑load triggers (events, commands, keys).&lt;/li&gt;
      &lt;li&gt;The scheduled callback applies the theme, defines global keymaps and commands.&lt;/li&gt;
      &lt;li&gt;When a buffer is opened, Treesitter attaches, the appropriate ftplugin fires, and
          snippets register for that filetype.&lt;/li&gt;
      &lt;li&gt;On &lt;code&gt;InsertEnter&lt;/code&gt;, nvim‑cmp activates with all snippet and LSP sources ready.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;
      At no point does the user wait for a progress bar.  The entire plugin set, once cached,
      starts in under 100 milliseconds.  The dashboard is ready before you can think about it.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Closing the Series&lt;/h2&gt;
    &lt;p&gt;
      Over seven articles, we’ve dissected a Neovim configuration from the bootstrapping layer
      through filetype engines, automation functions, snippet systems, visual theming, keymaps,
      and finally the plugin ecosystem.  The configuration is fully reproducible: clone the
      repository, run &lt;code&gt;:Lazy sync&lt;/code&gt;, and every file, every function, and every colour
      is exactly as intended.
    &lt;/p&gt;
    &lt;p&gt;
      The real takeaway is not the specific keybindings or colours, but the engineering mindset:
      treat your editor as a programmable platform, not a settings file.  Build functions, not
      just mappings.  Prefer determinism over convenience defaults.  And always be able to
      explain why every plugin is there.
    &lt;/p&gt;
    &lt;p&gt;
      This configuration will continue to evolve, but the principles documented here are
      timeless.  Happy engineering.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="treesitter"></category><category term="nvim-cmp"></category><category term="luasnip"></category><category term="completion"></category><category term="LSP"></category><category term="bufferline"></category><category term="plugins"></category><category term="engineering"></category><category term="configuration"></category></entry><entry><title>The Toolbelt: Keymaps, User Commands, and File‑Operation Pipelines</title><link href="https://mosaid.xyz/articles/the-toolbelt-keymaps-user-commands-and-file%E2%80%91operation-pipelines-11.html" rel="alternate"></link><published>2026-05-15T17:39:04+00:00</published><updated>2026-05-15T17:39:04+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/the-toolbelt-keymaps-user-commands-and-file‑operation-pipelines-11.html</id><summary type="html">&lt;p&gt;This article dissects the keymaps and commands layer of a production Neovim configuration. It shows how every keybinding and user command is deliberately wired to custom functions, how the ranger file manager is integrated, and how buffer‑wrapping utilities provide seamless file‑format conversion workflows.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;Turning Functions Into a Control Surface&lt;/h2&gt;
    &lt;p&gt;
      Custom functions (covered in Article #3) are the engine; keymaps and user commands are the
      steering wheel and dashboard.  A well‑designed Neovim configuration makes the most frequent
      operations available without a thought – and the less frequent ones discoverable through
      muscle‑memory shortcuts or short colon commands.
    &lt;/p&gt;
    &lt;p&gt;
      This article opens &lt;code&gt;lua/config/keymaps.lua&lt;/code&gt; and &lt;code&gt;lua/config/commands.lua&lt;/code&gt;
      and shows how they form a unified toolbelt.  Every mapping is intentionally chosen; every
      command mirrors a function but adds a discoverability layer.  We’ll also examine the ranger
      file‑chooser integration and the buffer‑wrapping utilities that bridge the editor to external
      workflows.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Keymaps Layer&lt;/h2&gt;
    &lt;p&gt;
      The file &lt;code&gt;lua/config/keymaps.lua&lt;/code&gt; is loaded in the scheduled callback of
      &lt;code&gt;init.lua&lt;/code&gt;, after plugins and functions are available.  It defines global
      keybindings that never depend on a specific filetype – filetype‑specific mappings live
      in &lt;code&gt;ftplugin/&lt;/code&gt; (Article #2).  This separation keeps the global mapset clean
      and predictable.
    &lt;/p&gt;
    &lt;p&gt;The complete keymaps file:&lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local opts = { noremap = true, silent = true }
local functions = require("config.functions")

-- Copy whole file to system clipboard
vim.keymap.set("n", "cc", 'gg"+yG', opts)
vim.keymap.set("v", "&lt;C-c&gt;", '"+y', opts)
vim.keymap.set("v", "7", '"+y', opts)
vim.keymap.set("i", "&lt;C-v&gt;", '&lt;C-r&gt;+', opts)

-- Toggle line numbers
vim.keymap.set("n", "&lt;leader&gt;l", ":set number! relativenumber!&lt;CR&gt;", opts)

-- redo
vim.keymap.set("n", "r", '&lt;C-r&gt;', opts)

-- Buffer navigation
vim.keymap.set("n", "&lt;Tab&gt;", ":bn&lt;CR&gt;", opts)
vim.keymap.set("n", "&lt;S-Tab&gt;", ":bp&lt;CR&gt;", opts)

-- Window movements
vim.keymap.set("n", "&lt;right&gt;", "&lt;C-W&gt;&lt;C-L&gt;", opts)
vim.keymap.set("n", "&lt;left&gt;", "&lt;C-W&gt;&lt;C-H&gt;", opts)

-- Python selection execution (Visual mode)
vim.keymap.set("v", "&lt;leader&gt;c", functions.run_python_selection, { noremap = true })
vim.keymap.set("v", "&lt;leader&gt;r", functions.run_python_and_replace, { noremap = true })

-- Run scripts on files/selection
vim.keymap.set("n", "&lt;leader&gt;rf", functions.run_scripts_on_files, opts)
vim.keymap.set("v", "&lt;leader&gt;rs", functions.run_scripts_on_select, { noremap = true })

-- Basic file commands
vim.keymap.set("n", "&lt;leader&gt;w", ":w&lt;CR&gt;", opts)
vim.keymap.set("n", "&lt;leader&gt;q", ":q&lt;CR&gt;", opts)
vim.keymap.set("n", "&lt;leader&gt;h", ":nohlsearch&lt;CR&gt;", opts)

-- Window navigation using Ctrl + hjkl
vim.keymap.set("n", "&lt;C-h&gt;", "&lt;C-w&gt;h", opts)
vim.keymap.set("n", "&lt;C-j&gt;", "&lt;C-w&gt;j", opts)
vim.keymap.set("n", "&lt;C-k&gt;", "&lt;C-w&gt;k", opts)
vim.keymap.set("n", "&lt;C-l&gt;", "&lt;C-w&gt;l", opts)

-- Selection and utility commands
vim.keymap.set("n", "&lt;leader&gt;v", functions.select_inside_any_pair, opts)
vim.keymap.set("n", "&lt;leader&gt;wc", functions.word_count, opts)
vim.keymap.set("n", "&lt;leader&gt;mm", functions.output_old_files, opts)
vim.keymap.set("n", "&lt;leader&gt;cc", functions.command_history_explorer, opts)
vim.keymap.set("n", "&lt;leader&gt;rr", functions.output_regs, opts)
vim.keymap.set("n", "&lt;leader&gt;hh", functions.wrap_code_in_buffer, opts)
vim.keymap.set("n", "&lt;leader&gt;ranger", functions.ranger_chooser, opts)


vim.keymap.set("n", "ì", "^", opts)
vim.keymap.set("o", "ì", "^", opts)

local ls = require("luasnip")


-- Forward jump to &lt;++&gt; (now using Ctrl+L)
vim.cmd([[
  inoremap &lt;C-l&gt; &lt;ESC&gt;/&lt;++&gt;&lt;CR&gt;"_c4l
  nnoremap &lt;C-l&gt; /&lt;++&gt;&lt;CR&gt;"_c4l
]])

-- Backward jump to &lt;++&gt; (now using Ctrl+H)
vim.cmd([[
  inoremap &lt;C-h&gt; &lt;ESC&gt;?&lt;++&gt;&lt;CR&gt;"_c4l
  nnoremap &lt;C-h&gt; ?&lt;++&gt;&lt;CR&gt;"_c4l
]])

-- LuaSnip forward/backward jumps with Ctrl+J / Ctrl+K
vim.keymap.set("i", "&lt;C-j&gt;", function() require("luasnip").jump(1) end, { silent = true })
vim.keymap.set("i", "&lt;C-k&gt;", function() require("luasnip").jump(-1) end, { silent = true })
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;p&gt;
      Several design patterns stand out:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Mnemonic leader prefixes:&lt;/strong&gt; &lt;code&gt;&amp;lt;leader&amp;gt;wc&lt;/code&gt; for &lt;strong&gt;w&lt;/strong&gt;ord &lt;strong&gt;c&lt;/strong&gt;ount,
          &lt;code&gt;&amp;lt;leader&amp;gt;cc&lt;/code&gt; for &lt;strong&gt;c&lt;/strong&gt;ommand history, &lt;code&gt;&amp;lt;leader&amp;gt;rr&lt;/code&gt; for
          &lt;strong&gt;r&lt;/strong&gt;egisters.  The leader key is &lt;code&gt;,&lt;/code&gt; so these are fast two‑stroke combos.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Buffer and window movement:&lt;/strong&gt; &lt;code&gt;Tab&lt;/code&gt;/&lt;code&gt;Shift‑Tab&lt;/code&gt; for buffer switching,
          &lt;code&gt;Ctrl‑hjkl&lt;/code&gt; for window navigation.  No arrow‑key dependency, minimal finger travel.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Placeholder jumping:&lt;/strong&gt; &lt;code&gt;Ctrl‑L&lt;/code&gt; and &lt;code&gt;Ctrl‑H&lt;/code&gt; jump to the next/previous
          &lt;code&gt;&amp;lt;++&amp;gt;&lt;/code&gt; placeholder – a convention used throughout snippets and insert‑mode expansions.
          This is separate from LuaSnip’s &lt;code&gt;Ctrl‑J&lt;/code&gt;/&lt;code&gt;Ctrl‑K&lt;/code&gt; for snippet node jumps.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Clipboard shortcuts:&lt;/strong&gt; &lt;code&gt;cc&lt;/code&gt; in normal mode copies the entire file.
          &lt;code&gt;Ctrl‑c&lt;/code&gt; in visual mode copies selection.  &lt;code&gt;Ctrl‑v&lt;/code&gt; in insert mode pastes.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;User Commands: The Discoverable Layer&lt;/h2&gt;
    &lt;p&gt;
      Keymaps are fast but invisible.  User commands (the &lt;code&gt;:&lt;/code&gt; interface) serve as the
      discoverable, self‑documenting counterpart.  The file &lt;code&gt;lua/config/commands.lua&lt;/code&gt;
      creates one command per major function:
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local functions = require("config.functions")

-- Create user commands
vim.api.nvim_create_user_command("WC", functions.word_count, {})
vim.api.nvim_create_user_command("Mm", functions.output_old_files, {})
vim.api.nvim_create_user_command("MM", functions.output_old_files, {})
vim.api.nvim_create_user_command("Cc", functions.command_history_explorer, {})
vim.api.nvim_create_user_command("CC", functions.command_history_explorer, {})
vim.api.nvim_create_user_command("Ss", functions.search_command_history, {})
vim.api.nvim_create_user_command("SS", functions.search_command_history, {})
vim.api.nvim_create_user_command("Rr", functions.output_regs, {})
vim.api.nvim_create_user_command("RR", functions.output_regs, {})
vim.api.nvim_create_user_command("HH", functions.wrap_code_in_buffer, {})
vim.api.nvim_create_user_command("RangerChooser", functions.ranger_chooser, {})
vim.api.nvim_create_user_command("SaveVimInfo", functions.save_vim_bindings_and_functions, {})
vim.api.nvim_create_user_command("EngType", functions.eng_type, {})
vim.api.nvim_create_user_command("FrType", functions.fr_type, {})
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;p&gt;
      Commands are kept short (often two or three uppercase letters) to minimise typing.  A few
      have dual‑case variants (e.g., &lt;code&gt;:Mm&lt;/code&gt; and &lt;code&gt;:MM&lt;/code&gt;) – the uppercase version
      is a deliberate redundancy, ensuring the command still works if Caps Lock is on.
    &lt;/p&gt;
    &lt;p&gt;
      Every command mirrors a function from &lt;code&gt;functions.lua&lt;/code&gt; but requires no modifier
      key.  This makes the toolbelt accessible from both muscle‑memory (keymap) and conscious
      recall (command palette / command history).
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Ranger Chooser: External Tool Integration&lt;/h2&gt;
    &lt;p&gt;
      The &lt;code&gt;ranger_chooser()&lt;/code&gt; function (and its &lt;code&gt;:RangerChooser&lt;/code&gt; command)
      launches the &lt;code&gt;ranger&lt;/code&gt; terminal file manager and opens all selected files as
      Neovim arguments.  It detects whether Neovim is running in a GUI or terminal and adapts:
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.ranger_chooser()
  local temp = vim.fn.tempname()
  if vim.fn.has('gui_running') == 1 then
    vim.fn.system('xterm -e ranger --choosefiles=' .. vim.fn.shellescape(temp))
  else
    vim.fn.system('ranger --choosefiles=' .. vim.fn.shellescape(temp))
  end

  if vim.fn.filereadable(temp) == 0 then
    vim.cmd('redraw!')
    return
  end

  local names = vim.fn.readfile(temp)
  if #names == 0 then
    vim.cmd('redraw!')
    return
  end

  vim.cmd('edit ' .. vim.fn.fnameescape(names[1]))
  for i = 2, #names do
    vim.cmd('argadd ' .. vim.fn.fnameescape(names[i]))
  end
  vim.cmd('redraw!')
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;p&gt;
      This is the Unix philosophy in action: let &lt;code&gt;ranger&lt;/code&gt; do what it does best (file
      navigation) and let Neovim do what it does best (editing).  The integration is a thin glue
      layer – a temporary file is used to pass the selected paths back, and Neovim’s argument
      list (&lt;code&gt;:args&lt;/code&gt;) is populated so you can navigate between the chosen files with
      &lt;code&gt;:next&lt;/code&gt; and &lt;code&gt;:prev&lt;/code&gt;.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The HH Wrapper: Format Conversion on Demand&lt;/h2&gt;
    &lt;p&gt;
      The &lt;code&gt;wrap_code_in_buffer()&lt;/code&gt; function (bound to &lt;code&gt;&amp;lt;leader&amp;gt;hh&lt;/code&gt; and
      &lt;code&gt;:HH&lt;/code&gt;) takes the current buffer’s content and wraps it in
      &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags, opening the result in a new buffer with HTML
      filetype.  This is used constantly when writing articles that embed code blocks.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.wrap_code_in_buffer()
  local save_cursor = vim.fn.getpos('.')
  local buffer_text = vim.api.nvim_buf_get_lines(0, 0, -1, false)

  vim.cmd('enew')
  vim.bo.buftype = 'nofile'
  vim.bo.bufhidden = 'wipe'
  vim.bo.swapfile = false
  vim.wo.wrap = false
  vim.bo.filetype = 'html'

  local content = '&lt;pre class="language-latex"&gt;&lt;code class="language-latex"&gt;\n' ..
                  table.concat(buffer_text, "\n") ..
                  '\n&lt;/code&gt;&lt;/pre&gt;'

  vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(content, "\n"))
  vim.fn.setpos('.', save_cursor)
end
&lt;/code&gt;
    &lt;/pre&gt;

    &lt;p&gt;
      A similar wrapper exists in the LaTeX ftplugin (Article #2), but this global version works
      on any buffer.  Together they form a small but powerful content pipeline: write in your
      preferred language, hit a key, get a properly escaped HTML code block ready for the blog.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Composability and Consistency&lt;/h2&gt;
    &lt;p&gt;
      The keymaps and commands layer follows a strict rule: every keybinding has a corresponding
      user command, and every command delegates to a single function.  There are no inline
      Vimscript hacks, no copy‑pasted logic.  This makes the system:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Auditable:&lt;/strong&gt; you can trace any action from keypress to function in seconds.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Remappable:&lt;/strong&gt; changing a keybinding never breaks the command‑line interface,
          and vice versa.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Documentable:&lt;/strong&gt; the user commands themselves serve as living documentation.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      The toolbelt doesn’t try to do everything – it focuses on operations that are too frequent
      to type out manually but too specific for a general‑purpose plugin.  Combined with the
      filetype‑specific maps (Article #2) and the snippet system (Article #4), the editor becomes
      a personalised engineering console.
    &lt;/p&gt;
    &lt;p&gt;
      In the final article, we’ll survey the plugin ecosystem – Treesitter, completion, LSP, and
      bufferline – and how they complete the developer dashboard.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="keymaps"></category><category term="commands"></category><category term="tooling"></category><category term="automation"></category><category term="ranger"></category><category term="file operations"></category><category term="engineering"></category><category term="configuration"></category></entry><entry><title>The Visual Stack: Deterministic Themes, Transparency, and Custom Highlights</title><link href="https://mosaid.xyz/articles/the-visual-stack-deterministic-themes-transparency-and-custom-highlights-10.html" rel="alternate"></link><published>2026-05-15T16:43:50+00:00</published><updated>2026-05-15T16:43:50+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/the-visual-stack-deterministic-themes-transparency-and-custom-highlights-10.html</id><summary type="html">&lt;p&gt;This article dissects the colours and highlight configuration of a production Neovim setup. It covers the dual‑theme fallback (Nordic/Tokyonight), transparent backgrounds, custom cursor and bufferline highlights, and the autocommand that ensures visual consistency no matter what plugins are loaded.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;When the Editor Must Look the Same Everywhere&lt;/h2&gt;
    &lt;p&gt;
      A developer’s editor is a visual environment.  Inconsistent colours, jarring flash‑of‑white
      on startup, or missing highlights break concentration.  This configuration treats the visual
      layer as a deterministic build step: a fallback colourscheme is set immediately, the real
      theme loads asynchronously, and a series of overrides applies custom highlights that survive
      theme updates.
    &lt;/p&gt;
    &lt;p&gt;
      This article walks through &lt;code&gt;lua/config/colors.lua&lt;/code&gt; – the single module that
      governs everything visual.  We’ll see how it selects between Nordic and Tokyonight, how it
      makes almost every UI element transparent, and how it re‑applies critical highlights after
      every &lt;code&gt;ColorScheme&lt;/code&gt; event.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Fallback Strategy&lt;/h2&gt;
    &lt;p&gt;
      Inside &lt;code&gt;init.lua&lt;/code&gt;, right after the basic options, we call
      &lt;code&gt;vim.cmd("colorscheme desert")&lt;/code&gt;.  This is not the final theme – it is a
      bullet‑proof fallback.  If the later scheduled call to &lt;code&gt;colors.setup()&lt;/code&gt; fails
      (for example, because the Nordic plugin is missing or broken), the editor still has a
      working, readable colourscheme.
    &lt;/p&gt;
    &lt;p&gt;
      The fallback also eliminates the “flash of unstyled text” during startup.  By setting
      &lt;code&gt;desert&lt;/code&gt; synchronously, Neovim never renders a completely unstyled buffer
      before the real theme arrives.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The colors.lua Module&lt;/h2&gt;
    &lt;p&gt;
      The module is structured as a single &lt;code&gt;setup()&lt;/code&gt; function that:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;Enables true‑colour support (&lt;code&gt;vim.o.termguicolors = true&lt;/code&gt;).&lt;/li&gt;
      &lt;li&gt;Sets &lt;code&gt;background = "dark"&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Attempts to load &lt;code&gt;nordic&lt;/code&gt; (the preferred theme).  If that fails, it falls
          back to &lt;code&gt;desert&lt;/code&gt; and logs a warning.&lt;/li&gt;
      &lt;li&gt;Restores number and cursorline settings (some themes override them).&lt;/li&gt;
      &lt;li&gt;Applies dozens of custom highlight groups for cursor, statusline, search, folds,
          spelling, line numbers, bufferline, and more.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      The entire file is reproduced below.  Note how every highlight uses explicit colours
      (no reference to theme palette variables).  This is deliberate: it ensures the
      highlights look identical whether Nordic or Tokyonight is active.
    &lt;/p&gt;


    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- lua/config/colors.lua
-- Colors and highlights configuration (safe + explicit)
local M = {}

function M.setup()
  -- Ensure true color support
  vim.o.termguicolors = true

  -- Preferred background
  vim.o.background = "dark"

  -- Try to load the tokyonight colorscheme safely
  local ok, err = pcall(vim.cmd, "colorscheme nordic")
  if not ok then
    -- Fallback to desert and warn the user in :messages
    vim.cmd("colorscheme desert")
    vim.notify("Warning: Falling back to 'desert'. Error: " .. tostring(err), vim.log.levels.WARN)
  end

  -- Ensure line numbers and cursorline are enabled (set after colorscheme to avoid overrides)
  vim.wo.number = true
  vim.wo.relativenumber = true
  vim.wo.cursorline = true

  -- Then set custom highlight groups
  -- Cursor line / column
  vim.api.nvim_set_hl(0, "CursorLine", { bg = "#3b4261", bold = true })
  vim.api.nvim_set_hl(0, "CursorColumn", { bg = "#3b4261", blend = 80 })
  vim.api.nvim_set_hl(0, "Cursor", { bg = "#2a2e38", fg = "#ffffff", blend = 70 })
  -- Perfect cursor for Nordic: subtle blue, visible, does not obscure text
  vim.api.nvim_set_hl(0, "Cursor", {
    bg = "#434C5E",  -- softer Nordic blue
    fg = "#ECEFF4",  -- visible over any code
    blend = 60       -- transparency so text stays readable underneath
  })

  -- Status line and visual selection
  vim.api.nvim_set_hl(0, "StatusLine", { bg = "#1f2335", fg = "#c0caf5", bold = true })
  vim.api.nvim_set_hl(0, "Visual", { bg = "#FFD966", fg = "black" })

  -- Fold / Search
  vim.api.nvim_set_hl(0, "Folded", { bg = "none", fg = "#737aa2", italic = true })
  vim.api.nvim_set_hl(0, "Search", { bg = "#ff9e64", fg = "black", bold = true })

  -- Spell checking highlights
  vim.api.nvim_set_hl(0, "SpellBad", { underline = true, sp = "#db4b4b" })
  vim.api.nvim_set_hl(0, "SpellLocal", { underline = true, sp = "#0db9d7" })
  vim.api.nvim_set_hl(0, "SpellRare", { underline = true, sp = "#bb9af7" })
  vim.api.nvim_set_hl(0, "SpellCap", { underline = true, sp = "#e0af68" })

  -- Line number colors
  vim.api.nvim_set_hl(0, "LineNr", { fg = "#FFFF00" })
  vim.api.nvim_set_hl(0, "CursorLineNr", { fg = "#c0caf5", bold = true })

  -- BufferLine colors
  vim.api.nvim_set_hl(0, "BufferLineBufferSelected", { bold = true, fg = "#ffca00" })
  vim.api.nvim_set_hl(0, "BufferLineModifiedSelected", { fg = "#ff9e64" })
end

-- Final transparency patch (works for all themes)
local transparent_groups = {
  "Normal", "NormalNC", "NormalFloat",
  "SignColumn", "MsgArea",
  "TelescopeNormal", "TelescopeBorder",
  "StatusLine", "StatusLineNC",
  "BufferLineFill", "BufferLineBackground",
  "LineNr", "CursorLineNr",
  "FloatBorder",
}

for _, group in ipairs(transparent_groups) do
  vim.api.nvim_set_hl(0, group, { bg = "none" })
end

-- Reapply custom highlight after everything is loaded
vim.api.nvim_create_autocmd({"ColorScheme", "User"}, {
  pattern = {"LazyDone", "VeryLazy"},
  callback = function()
    vim.api.nvim_set_hl(0, "MatchParen", { bg = "#ff33aa", fg = "#000000", bold = true })
    vim.api.nvim_set_hl(0, "MatchParenCur", { bg = "#ff33aa", fg = "#000000", bold = true })
    vim.api.nvim_set_hl(0, "MatchWord",  { bg = "#5555ff", fg = "#000000" })
  end,
})

return M
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Transparency as a Design Decision&lt;/h2&gt;
    &lt;p&gt;
      The bottom half of the file sets &lt;code&gt;bg = "none"&lt;/code&gt; on a carefully chosen set of
      highlight groups.  Groups like &lt;code&gt;Normal&lt;/code&gt;, &lt;code&gt;SignColumn&lt;/code&gt;,
      &lt;code&gt;FloatBorder&lt;/code&gt;, and &lt;code&gt;BufferLineBackground&lt;/code&gt; become transparent,
      letting the terminal background show through.  This creates a clean, borderless look
      that works with any terminal colour scheme.
    &lt;/p&gt;
    &lt;p&gt;
      Groups that must remain opaque (e.g., &lt;code&gt;CursorLine&lt;/code&gt;, &lt;code&gt;Visual&lt;/code&gt;,
      &lt;code&gt;Search&lt;/code&gt;) are explicitly given backgrounds, so readability is never
      compromised.  The separation is deliberate: structural UI elements become transparent,
      content‑focused elements keep their solid backgrounds.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Autocommand That Fixes Everything&lt;/h2&gt;
    &lt;p&gt;
      Some plugin managers or theme reloads can reset highlight groups.  To combat this, the
      file registers an autocommand on &lt;code&gt;ColorScheme&lt;/code&gt; and &lt;code&gt;User&lt;/code&gt; events
      (specifically &lt;code&gt;LazyDone&lt;/code&gt; and &lt;code&gt;VeryLazy&lt;/code&gt;) that re‑applies a set
      of critical highlights:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;code&gt;MatchParen&lt;/code&gt; and &lt;code&gt;MatchParenCur&lt;/code&gt; – a bright pink background
          that makes matching brackets unmistakable.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;MatchWord&lt;/code&gt; – a blue background for the word under cursor when using
          &lt;code&gt;*&lt;/code&gt; or &lt;code&gt;#&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      By re‑applying these after every theme change, the configuration guarantees that your
      most important visual cues never disappear.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Why Not Just Use a Theme’s Variables?&lt;/h2&gt;
    &lt;p&gt;
      Many colour configurations use theme palette variables (e.g., &lt;code&gt;colors.blue&lt;/code&gt;)
      so that highlight groups automatically adapt to the current theme.  This configuration
      intentionally avoids that.  By using raw hex codes, it decouples the custom highlights
      from any particular theme’s palette.  Whether you’re running Nordic, Tokyonight, or
      fallback Desert, the cursor line is always &lt;code&gt;#3b4261&lt;/code&gt;, the search highlight
      is always &lt;code&gt;#ff9e64&lt;/code&gt;, and matching brackets always explode in pink.
    &lt;/p&gt;
    &lt;p&gt;
      The tradeoff is that you must choose colours that work across multiple themes.  The
      benefit is absolute determinism – the visual experience does not shift when you change
      or update a theme.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Full Picture&lt;/h2&gt;
    &lt;p&gt;
      Together, the fallback colourscheme, the scheduled theme application, the transparent
      UI groups, and the autocommand re‑application create a visual stack that is:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Resilient:&lt;/strong&gt; the editor always starts with a functional theme.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Consistent:&lt;/strong&gt; custom highlights never depend on which theme loads.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Clean:&lt;/strong&gt; transparency removes visual noise.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Reproducible:&lt;/strong&gt; the same hex values produce the same pixels on any machine.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      In the next article, we’ll explore the keymaps and commands that wire all these
      subsystems together into a unified user interface.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="colorscheme"></category><category term="transparency"></category><category term="nordic"></category><category term="tokyonight"></category><category term="highlighting"></category><category term="configuration"></category><category term="deterministic"></category><category term="themes"></category></entry><entry><title>Snippets as Code: LuaSnip, Dynamic Expansion, and File‑Based Snippet Injection</title><link href="https://mosaid.xyz/articles/snippets-as-code-luasnip-dynamic-expansion-and-file%E2%80%91based-snippet-injection-9.html" rel="alternate"></link><published>2026-05-15T16:33:12+00:00</published><updated>2026-05-15T16:33:12+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/snippets-as-code-luasnip-dynamic-expansion-and-file‑based-snippet-injection-9.html</id><summary type="html">&lt;p&gt;This article dissects the LuaSnip integration inside a Neovim configuration, showing how dynamic snippets, external file injection, and auto‑numbering environments turn text expansion into a programmable, context‑aware engine. All snippet files are examined line by line.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;Snippets Should Be Logic, Not Just Text&lt;/h2&gt;
    &lt;p&gt;
      Most snippet systems treat expansions as static templates: you type a trigger, you get a fixed
      block of text.  LuaSnip in Neovim allows something far more powerful – snippets can run Lua
      functions, read external files, and adapt to the current file or directory.  This turns snippet
      expansion into a programmable, context‑aware automation layer.
    &lt;/p&gt;
    &lt;p&gt;
      In this article we open the two snippet configuration files that power LaTeX, HTML, and Markdown
      editing: &lt;code&gt;lua/config/tex_snippets.lua&lt;/code&gt; and &lt;code&gt;lua/config/html_snippets.lua&lt;/code&gt;.
      We’ll examine how they replace a legacy directory of static snippet files, how they integrate
      with &lt;code&gt;nvim-cmp&lt;/code&gt;, and how they enable everything from auto‑numbered exercises to
      inline HTML figure injection.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Legacy Problem&lt;/h2&gt;
    &lt;p&gt;
      Before LuaSnip, Neovim relied on snippet engines like UltiSnips or external files stored in
      &lt;code&gt;~/.vim/snippets/&lt;/code&gt;.  Those files were plain text, loaded once, and had no runtime
      intelligence.  This configuration migrated to LuaSnip for three reasons:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Reproducibility:&lt;/strong&gt; everything lives in Lua, no external directories to sync.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Dynamic behaviour:&lt;/strong&gt; snippets can count existing environments, query the current
          directory, and insert content based on context.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Integration with native completion:&lt;/strong&gt; LuaSnip plugs directly into
          &lt;code&gt;nvim-cmp&lt;/code&gt; as a source, so snippets appear in the autocompletion menu.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      The old snippet files still exist (in &lt;code&gt;~/.vim/snippets/latex/&lt;/code&gt;) but are now consumed
      by LuaSnip functions that read their content on demand, preserving the original text blocks
      while adding programmability.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The html_snippets.lua: Straightforward but Complete&lt;/h2&gt;
    &lt;p&gt;
      HTML and Markdown share three core snippets: anchor links, figure/image blocks, and code blocks.
      Each is defined using LuaSnip’s node types: &lt;code&gt;s()&lt;/code&gt; for the snippet, &lt;code&gt;i()&lt;/code&gt;
      for insert‑node placeholders, &lt;code&gt;t()&lt;/code&gt; for text, and &lt;code&gt;f()&lt;/code&gt; for function nodes
      that compute values at expansion time.
    &lt;/p&gt;
    &lt;p&gt;
      The code block snippet is particularly clever: it uses a function node to duplicate the language
      name so it appears both in the &lt;code&gt;&amp;lt;pre class="language-…"&amp;gt;&lt;/code&gt; and the
      &lt;code&gt;&amp;lt;code class="language-…"&amp;gt;&lt;/code&gt; tags, avoiding manual repetition.  The snippet also
      wraps the body in &lt;code&gt;…&lt;/code&gt; tags – a nod to Jinja2 templates used in
      static site generation – to prevent Liquid/Jinja syntax from being parsed inside code blocks.
    &lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local ls = require("luasnip")
local s = ls.snippet
local i = ls.insert_node
local t = ls.text_node
local f = ls.function_node

-- Define snippets once
local snippets = {
  -- Anchor link
  s("a", {
    t('&lt;a href="'), i(1, "url"), t('" target="_blank" &gt;'), i(2, "link text"), t("&lt;/a&gt;"), i(0),
  }),

  -- Figure with img and figcaption
  s("fig", {
    t("&lt;figure&gt;"),
    t({ "", "    " }),
    t('&lt;img src="/theme/images/articles/'), i(1, "image.png"), t('" alt="'), i(2, "description"), t('" style="max-width:100%;height:auto;" &gt;'),
    t({ "", "    " }),
    t("&lt;figcaption&gt;"), i(3, "caption"), t("&lt;/figcaption&gt;"),
    t({ "", "&lt;/figure&gt;" }),
    i(0),
  }),

-- Enable for both HTML and Markdown files
ls.add_snippets("html", snippets)
ls.add_snippets("markdown", snippets)
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The tex_snippets.lua: A Snippet Engine for LaTeX&lt;/h2&gt;
    &lt;p&gt;
      LaTeX editing demands a much richer set of snippets.  The &lt;code&gt;tex_snippets.lua&lt;/code&gt;
      file exports two things: a table of functions for direct insertion (called by the LaTeX
      ftplugin), and a collection of LuaSnip snippet definitions.  It uses three design patterns:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Environment snippets with auto‑closing:&lt;/strong&gt; &lt;code&gt;env_snip()&lt;/code&gt; creates a
          snippet that inserts &lt;code&gt;\begin{…}&lt;/code&gt; and &lt;code&gt;\end{…}&lt;/code&gt; with the
          environment name mirrored via a function node.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Auto‑numbered exercises:&lt;/strong&gt; the &lt;code&gt;exe&lt;/code&gt; snippet counts how many
          &lt;code&gt;\begin{exe}&lt;/code&gt; environments already exist in the buffer and inserts the next
          number automatically.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;External file injection:&lt;/strong&gt; &lt;code&gt;file_snippet()&lt;/code&gt; reads a file from
          &lt;code&gt;~/.vim/snippets/latex/&lt;/code&gt; and inserts its content as the snippet body,
          bridging the legacy snippet directory.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      The &lt;code&gt;curr_file_base_name()&lt;/code&gt; helper reads the numeric prefix of the current working
      directory, used by &lt;code&gt;cse&lt;/code&gt; and similar snippets to auto‑label sections (e.g.,
      &lt;code&gt;\csection[01-exer]{…}&lt;/code&gt;).  This is a deterministic system: the same directory
      always produces the same numbering.
    &lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local ls = require("luasnip")
local s = ls.snippet
local i = ls.insert_node
local t = ls.text_node
local f = ls.function_node

local M = {}

-- Helper to create environment snippet with jumpable placeholder
local function env_snip(trigger, default_env)
  return s(trigger, {
    t({"\\begin{"}), i(1, default_env), t({"}", "    &lt;++&gt;"}),  -- insert &lt;++&gt; as literal text
    t({"", "\\end{"}), f(function(args) return args[1][1] end, {1}), t("}")
  })
end

-- Helper to get the first part of current directory name
local function curr_file_base_name()
  local cwd = vim.fn.system("basename $(pwd) | cut -d'-' -f1")
  return cwd:gsub("\n", "")
end

-- =============================
-- Native LuaSnip LaTeX snippets
-- =============================
ls.add_snippets("tex", {
  env_snip("beg", "tabular"),
  env_snip("eq", "equation"),
  env_snip("mat", "bmatrix"),

  -- Sections
  s("sec", { t("\\section{"), i(1,"Title"), t({"", "    &lt;++&gt;"}), t("}") }),
  s("ssec", { t("\\subsection{"), i(1,"Subtitle"), t({"", "    &lt;++&gt;"}), t("}") }),

  -- Items and math
  s("itm", { t("\\item "), t("&lt;++&gt;") }),
  s("dollar", { t("$"), t("&lt;++&gt;"), t("$") }),
  s("frac", {
     t("\\frac{"), i(1), t("}{"), i(2), t("}"), i(0)
  }),
  s("sum", { t("\\sum_{"), t("&lt;++&gt;"), t("}^{"), t("&lt;++&gt;"), t("} "), t("&lt;++&gt;") }),

  -- =============================
  -- Exercise environment with auto-number
  -- =============================
  s("exe", {
    t("\\begin{exe}{"), i(1, "&lt;++&gt;"), t("}"),
    t({"", "    "}), f(function()
      local count = 0
      for _, line in ipairs(vim.api.nvim_buf_get_lines(0, 0, -1, false)) do
        if line:match("\\begin{exe}") then count = count + 1 end
      end
      return tostring(count)
    end, {}),
    t({"", "    &lt;++&gt;", "\\end{exe}"}),
  }),

  -- Sexe environment
  s("sex", {
    t({"\\begin{sexe}{", ""}), i(1, "&lt;++&gt;"), t({"", "    &lt;++&gt;", "\\end{sexe}"})
  }),

  -- Minipage snippet
  s("mini", {
    t({"\\begin{minipage}[t]{"}), i(1, "&lt;++&gt;"), t("\\textwidth}"), t({"", "    &lt;++&gt;", "\\end{minipage}", "\\hspace*{0.1cm}", "\\vl{0}{5}", "\\hspace*{0.1cm}", "\\begin{minipage}[t]{"}),
    i(2, "&lt;++&gt;"), t("\\textwidth}"), t({"", "    &lt;++&gt;", "\\end{minipage}"})
  }),

  -- Standalone document
  s("stand", {
    t({"\\documentclass{standalone}", "\\standaloneconfig{margin=5mm}"})
  }),
})

-- Section snippets using the directory prefix
ls.add_snippets("tex", {
  -- csection
  s("cse", {
    t({"\\vspace*{0.3cm}", "\\csection["}),
    t(curr_file_base_name() .. "-"), i(1, "exer"),
    t({"]{"}),
    f(function(args) return args[1] end, {1}),
    t({"}\\\\", "\\vspace*{0.3cm}"})
  }),

  -- cssection
  s("csse", {
    t({"\\vspace*{0.3cm}", "\\cssection["}),
    t(curr_file_base_name() .. "-"), i(1, "exer"),
    t({"]{"}),
    f(function(args) return args[1] end, {1}),
    t({"}\\\\", "\\vspace*{0.3cm}"})
  }),

  -- csssection
  s("cssse", {
    t({"\\vspace*{0.3cm}", "\\csssection["}),
    t(curr_file_base_name() .. "-"), i(1, "exer"),
    t({"]{"}),
    f(function(args) return args[1] end, {1}),
    t({"}\\\\", "\\vspace*{0.3cm}"})
  }),
})

-- =============================
-- Helper to load old snippet files dynamically
-- =============================
local function read_file(path)
  local lines = {}
  for line in io.lines(path) do
    table.insert(lines, line)
  end
  return lines
end

local function file_snippet(trigger, filepath)
  return s(trigger, f(function() return read_file(filepath) end, {}))
end

-- =============================
-- Old external snippet files as LuaSnip snippets
-- =============================
ls.add_snippets("tex", {
  file_snippet("tcb", "/home/mosaid/.vim/snippets/latex/tcb"),
  file_snippet("ds",  "/home/mosaid/.vim/snippets/latex/ds"),
  file_snippet("st",  "/home/mosaid/.vim/snippets/latex/st"),
  file_snippet("ar",  "/home/mosaid/.vim/snippets/latex/ar"),
  file_snippet("gg",  "/home/mosaid/.vim/snippets/latex/gg"),
  file_snippet("mtab","/home/mosaid/.vim/snippets/latex/mtab"),
  file_snippet("d1",  "/home/mosaid/.vim/snippets/latex/d1"),
  file_snippet("li",  "/home/mosaid/.vim/snippets/latex/li"),
  file_snippet("tight","/home/mosaid/.vim/snippets/latex/tight"),
  file_snippet("enum","/home/mosaid/.vim/snippets/latex/enum"),
})

-- Define the functions that tex.lua is trying to call
M.tcb = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/tcb")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.ds = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/ds")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.st = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/st")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.ar = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/ar")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.gg = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/gg")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.mtab = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/mtab")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.d1 = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/d1")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.li = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/li")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

M.tight = function()
  local lines = read_file("/home/mosaid/.vim/snippets/latex/tight")
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_lines(0, cursor[1] - 1, cursor[1] - 1, false, lines)
end

return M
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Dual Nature: Snippets and Functions&lt;/h2&gt;
    &lt;p&gt;
      Notice that &lt;code&gt;tex_snippets.lua&lt;/code&gt; defines both LuaSnip snippets &lt;em&gt;and&lt;/em&gt; a set
      of functions (&lt;code&gt;M.tcb()&lt;/code&gt;, &lt;code&gt;M.ds()&lt;/code&gt;, etc.).  The LaTeX ftplugin (from
      Article #2) calls these functions directly via insert‑mode mappings like
      &lt;code&gt;&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").tcb()&amp;lt;CR&amp;gt;&lt;/code&gt;.  This hybrid
      approach gives the user two ways to trigger an expansion:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;Through the built‑in completion menu (LuaSnip integration with &lt;code&gt;nvim-cmp&lt;/code&gt;).&lt;/li&gt;
      &lt;li&gt;Through muscle‑memory insert‑mode shortcuts that bypass the completion menu entirely.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      The external file snippets are loaded lazily – they aren’t read from disk until the trigger is
      activated, keeping startup fast.  The &lt;code&gt;file_snippet()&lt;/code&gt; wrapper ensures that the
      content stays up‑to‑date with any external edits, making the transition from static snippet files
      seamless.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Integration with nvim-cmp&lt;/h2&gt;
    &lt;p&gt;
      Snippets are useless without a good completion interface.  The &lt;code&gt;plugins/core.lua&lt;/code&gt;
      configures &lt;code&gt;nvim-cmp&lt;/code&gt; to use &lt;code&gt;luasnip&lt;/code&gt; as a source:
    &lt;/p&gt;

    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
sources = cmp.config.sources({
  { name = 'nvim_lsp' },
  { name = 'luasnip' },
  { name = 'buffer' },
  { name = 'path' },
}),
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      The &lt;code&gt;luasnip&lt;/code&gt; source is automatically populated with all snippets defined for the
      current filetype.  Additionally, the &lt;code&gt;cmp&lt;/code&gt; mapping for &lt;code&gt;&amp;lt;Tab&amp;gt;&lt;/code&gt;
      prioritises snippet expansion over completion navigation – if a snippet is expandable, it
      expands; otherwise it cycles to the next completion item.
    &lt;/p&gt;
    &lt;p&gt;
      This design ensures that snippets feel like a native part of the editor, not an external
      bolted‑on feature.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Determinism and Portability&lt;/h2&gt;
    &lt;p&gt;
      Every snippet in this configuration is defined in Lua, with no external dependencies except the
      legacy snippet files (which are themselves plain text).  The auto‑numbering relies on scanning
      the current buffer, not on any global state.  The directory‑based prefix uses a shell command
      that is explicitly deterministic.
    &lt;/p&gt;
    &lt;p&gt;
      This means you can clone the Neovim configuration to a new machine, run
      &lt;code&gt;:Lazy sync&lt;/code&gt;, and immediately have the same snippet behaviour.  No manual copying
      of snippet directories, no version mismatches.
    &lt;/p&gt;
    &lt;p&gt;
      In the next article, we’ll examine the visual stack: how themes, transparency, and custom
      highlights are applied with the same deterministic philosophy.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="luasnip"></category><category term="snippets"></category><category term="latex"></category><category term="html"></category><category term="markdown"></category><category term="dynamic"></category><category term="automation"></category><category term="configuration"></category><category term="engineering"></category></entry><entry><title>Automation, Not Just Shortcuts: Custom Functions that Think Like an IDE</title><link href="https://mosaid.xyz/articles/automation-not-just-shortcuts-custom-functions-that-think-like-an-ide-8.html" rel="alternate"></link><published>2026-05-15T15:54:31+00:00</published><updated>2026-05-15T15:54:31+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/automation-not-just-shortcuts-custom-functions-that-think-like-an-ide-8.html</id><summary type="html">&lt;p&gt;This article dissects the custom Lua functions that power the everyday operations of a production Neovim setup. From asynchronous Python and LaTeX runners to a searchable command history and register viewer, it shows how to embed IDE‑like utilities directly into the editor without plugins.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;Beyond Keymaps: Functions as First‑Class Tools&lt;/h2&gt;
    &lt;p&gt;
      Most Neovim users start by remapping keys.  That’s fine, but it stops short of turning the
      editor into a programmable environment.  The real power comes from custom Lua functions that
      do things no plugin can anticipate: running code asynchronously, replacing visual selections
      with computed results, exploring your command history like a file, or inspecting registers
      at a glance.
    &lt;/p&gt;
    &lt;p&gt;
      In this article we open &lt;code&gt;lua/config/functions.lua&lt;/code&gt; – the heart of the automation
      layer – and walk through every major utility.  You’ll see the full source for each function,
      understand why it was built, and learn how to wire it into your own workflow.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Architecture Overview&lt;/h2&gt;
    &lt;p&gt;
      The file &lt;code&gt;lua/config/functions.lua&lt;/code&gt; exports a table of functions.  These are
      called from keymaps (defined in &lt;code&gt;keymaps.lua&lt;/code&gt;) and user commands (defined in
      &lt;code&gt;commands.lua&lt;/code&gt;).  The module is loaded once during startup and reused everywhere.
    &lt;/p&gt;
    &lt;p&gt;
      The directory structure relevant to this article:
    &lt;/p&gt;
    &lt;pre class="language-text"&gt;
&lt;code&gt;
lua/config/
├── functions.lua     &lt;strong&gt;&amp;lt;-- all custom logic&lt;/strong&gt;
├── keymaps.lua       &lt;strong&gt;&amp;lt;-- maps keys to functions&lt;/strong&gt;
└── commands.lua      &lt;strong&gt;&amp;lt;-- creates :UserCommands&lt;/strong&gt;
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;1. Async Python and LaTeX Runners&lt;/h2&gt;
    &lt;p&gt;
      Two of the most frequently used functions are &lt;code&gt;run_python_selection()&lt;/code&gt; and
      &lt;code&gt;run_python_and_replace()&lt;/code&gt;.  The first executes the visually selected Python
      code in a non‑blocking job, appending its output to a scratch buffer.  The second does the
      same, but &lt;em&gt;replaces&lt;/em&gt; the selection with the output – ideal for quick calculations
      directly in your code or prose.
    &lt;/p&gt;
    &lt;p&gt;
      Both functions use &lt;code&gt;vim.fn.jobstart&lt;/code&gt; to avoid freezing the UI, and they
      automatically handle buffer cleanup when the job finishes or the user presses Enter.
      A similar async pattern is used in the LaTeX ftplugin (covered in Article #2), but
      the core helper functions are general enough to be reused for any language.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.run_python_selection()
  -- Check if in visual mode
  local mode = vim.fn.mode()
  if mode ~= 'v' and mode ~= 'V' then
    print("No visual selection")
    return
  end

  -- Save current register
  local save_reg = vim.fn.getreg('"')
  local save_regtype = vim.fn.getregtype('"')

  -- Yank selection into default register
  vim.cmd('normal! gvy')
  local code = vim.fn.getreg('"')

  -- Restore original register
  vim.fn.setreg('"', save_reg, save_regtype)

  -- Trim whitespace and prepend math import
  code = code:gsub('^%s*', ''):gsub('%s*$', '')
  code = "from math import *\n" .. code

  -- Execute Python code safely
  local ok, output = pcall(vim.fn.system, {'python3', '-c', code})
  if not ok then
    print("Error executing Python code")
    return
  end

  -- Open scratch buffer
  vim.cmd('enew')
  vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(output, "\n"))

  -- Configure buffer
  vim.bo.buftype = 'nofile'
  vim.bo.bufhidden = 'wipe'
  vim.bo.swapfile = false
  vim.wo.wrap = false
end
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      The &lt;code&gt;run_python_and_replace()&lt;/code&gt; variant handles single‑line selections specially:
      it wraps the expression in a &lt;code&gt;print()&lt;/code&gt; so that the output replaces the expression
      inline.  This is useful for evaluating math expressions inside Markdown or LaTeX documents.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.run_python_and_replace()
  local save_reg = vim.fn.getreg('"')
  local save_regtype = vim.fn.getregtype('"')
  vim.cmd('normal! ""gvy')
  local code = vim.fn.getreg('"')
  vim.fn.setreg('"', save_reg, save_regtype)

  code = code:gsub('^%s*', ''):gsub('%s*$', '')

  local start_line = vim.fn.line("'&lt;")
  local end_line = vim.fn.line("'&gt;")
  if start_line == end_line then
    code = 'print(' .. code .. ')'
  end

  code = "from math import *\n" .. code
  local output = vim.fn.system('python3 -c ' .. vim.fn.shellescape(code))
  output = output:gsub('\n+$', '')

  vim.cmd('normal! gv"_c' .. output)
end
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;2. Command History Explorer&lt;/h2&gt;
    &lt;p&gt;
      The command‑line history in Vim is powerful but invisible.  My &lt;code&gt;command_history_explorer()&lt;/code&gt;
      function dumps the entire history (newest first) into a temporary buffer.  You can navigate it
      with &lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt; or arrow keys, and pressing Enter executes the command under
      the cursor.  Pressing &lt;code&gt;q&lt;/code&gt; or &lt;code&gt;Tab&lt;/code&gt; closes the buffer.
    &lt;/p&gt;
    &lt;p&gt;
      This effectively gives you a “command palette” with zero dependencies.  Because the buffer
      is non‑modifiable and immediately searchable with &lt;code&gt;/&lt;/code&gt;, you can filter through
      hundreds of past commands in seconds.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.command_history_explorer()
  local history = {}
  local count = vim.fn.histnr(':') -- number of commands in history

  -- Collect commands from newest to oldest
  for i = count, 1, -1 do
    local cmd = vim.fn.histget(':', i)
    if cmd ~= "" then
      table.insert(history, cmd)
    end
  end

  -- Create new buffer
  vim.cmd('enew')
  local buf = vim.api.nvim_get_current_buf()

  vim.api.nvim_buf_set_lines(buf, 0, -1, false, history)

  -- Buffer options
  vim.bo[buf].buftype = 'nofile'
  vim.bo[buf].bufhidden = 'wipe'
  vim.bo[buf].swapfile = false
  vim.bo[buf].modifiable = false
  vim.wo.wrap = false

  local opts = { buffer = buf, noremap = true, silent = true }

  -- Map arrow keys (raw sequences)
  vim.keymap.set('n', "\27[B", 'j', opts) -- Down
  vim.keymap.set('n', "\27[A", 'k', opts) -- Up

  -- Also allow hjkl
  vim.keymap.set('n', 'j', 'j', opts)
  vim.keymap.set('n', 'k', 'k', opts)

  -- Quit buffer quickly
  vim.keymap.set('n', 'q', function() vim.cmd('bd! ' .. buf) end, opts)

  -- Execute the command under cursor
  vim.keymap.set('n', '&lt;CR&gt;', function()
    local cmd = vim.fn.getline('.')
    vim.cmd('bd! ' .. buf) -- close history buffer
    vim.cmd(cmd)           -- execute command
  end, opts)

  -- Start at the top
  vim.cmd('normal! gg')
end
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      A variant, &lt;code&gt;search_command_history()&lt;/code&gt;, opens a &lt;em&gt;modifiable&lt;/em&gt; buffer so that
      &lt;code&gt;incsearch&lt;/code&gt; can highlight matches.  This is useful when you want to interactively
      search without closing the buffer.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;3. Register Inspector&lt;/h2&gt;
    &lt;p&gt;
      Registers are one of Vim’s most powerful features, yet most users never know what’s stored
      in them.  The &lt;code&gt;output_regs()&lt;/code&gt; function prints the contents of registers
      &lt;code&gt;a&lt;/code&gt; through &lt;code&gt;z&lt;/code&gt; into a scratch buffer, labelled clearly.  It’s a
      simple idea, but it turns registers into a visible, reviewable tool.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.output_regs()
  vim.cmd('enew')
  vim.bo.buftype = 'nofile'
  vim.bo.bufhidden = 'wipe'
  vim.bo.swapfile = false
  vim.wo.wrap = false

  local content = {}
  for reg = string.byte('a'), string.byte('z') do
    local reg_char = string.char(reg)
    local reg_content = vim.fn.getreg(reg_char)
    if reg_content ~= '' then
      table.insert(content, reg_char .. ":")
      table.insert(content, reg_content)
      table.insert(content, "")
    end
  end

  vim.api.nvim_buf_set_lines(0, 0, -1, false, content)
  vim.cmd('normal! gg')
  print("Contents of registers a to z")
end
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;4. Select Inside Any Pair&lt;/h2&gt;
    &lt;p&gt;
      Vi’s &lt;code&gt;i{&lt;/code&gt;, &lt;code&gt;i(&lt;/code&gt;, etc. are great, but they require you to think
      about which delimiter you’re inside.  &lt;code&gt;select_inside_any_pair()&lt;/code&gt; tries every
      common pair (&lt;code&gt;{}&lt;/code&gt;, &lt;code&gt;[]&lt;/code&gt;, &lt;code&gt;()&lt;/code&gt;, &lt;code&gt;$...$&lt;/code&gt;,
      quotes) and selects the innermost one that surrounds the cursor.  If nothing matches, it
      falls back to selecting the current line.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.select_inside_any_pair()
  local savepos = vim.fn.getpos('.')
  local pairs = {
    {'{', '}'},
    {'[', ']'},
    {'(', ')'},
    {'$', '$'},
    {'"', '"'},
    {"'", "'"}
  }

  for _, pair in ipairs(pairs) do
    local open, close = pair[1], pair[2]
    local start, finish

    if open == close then
      start = vim.fn.searchpos('\\V' .. open, 'bnW')
      finish = vim.fn.searchpos('\\V' .. close, 'nW')
    else
      start = vim.fn.searchpairpos('\\V' .. open, '', '\\V' .. close, 'bnW')
      finish = vim.fn.searchpairpos('\\V' .. open, '', '\\V' .. close, 'nW')
    end

    if start[1] &amp;gt; 0 and finish[1] &amp;gt; 0 then
      local cur = vim.fn.getpos('.')
      local start_before_cursor = (start[1] &amp;lt; cur[2]) or (start[1] == cur[2] and start[2] &amp;lt;= cur[3])
      local finish_after_cursor = (finish[1] &amp;gt; cur[2]) or (finish[1] == cur[2] and finish[2] &amp;gt;= cur[3])

      if start_before_cursor and finish_after_cursor then
        vim.fn.setpos('.', {0, start[1], start[2] + 1, 0})
        vim.cmd('normal! v')
        vim.fn.setpos('.', {0, finish[1], finish[2], 0})
        vim.cmd('normal! h')
        return
      end
    end
  end

  -- Fallback: select current line
  local lnum = vim.fn.line('.')
  local line = vim.fn.getline(lnum)
  local end_col = #line
  if end_col &amp;gt; 0 and line:sub(-1) == '$' then
    end_col = end_col - 1
  end

  vim.fn.setpos('.', {0, lnum, 1, 0})
  vim.cmd('normal! v')
  vim.fn.setpos('.', {0, lnum, end_col, 0})
end
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;5. Old Files Browser and Ranger Chooser&lt;/h2&gt;
    &lt;p&gt;
      Vim stores recently opened files in &lt;code&gt;vim.v.oldfiles&lt;/code&gt;.  &lt;code&gt;output_old_files()&lt;/code&gt;
      dumps them into a buffer, making each entry a link you can press Enter on to open.  This is
      a lightweight alternative to Telescope for quickly revisiting files without fuzzy matching.
    &lt;/p&gt;
    &lt;p&gt;
      The &lt;code&gt;ranger_chooser()&lt;/code&gt; function integrates the &lt;code&gt;ranger&lt;/code&gt; file manager
      directly into the editor: it runs &lt;code&gt;ranger --choosefiles&lt;/code&gt; in a terminal (or XTerm
      if a GUI is detected) and opens all selected files as arguments.  This fits the philosophy
      of using external, best‑in‑class tools while keeping the editor as the central hub.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
function M.output_old_files()
  vim.cmd('enew')
  vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.v.oldfiles)
  vim.bo.buftype = 'nofile'
  vim.bo.bufhidden = 'wipe'
  vim.bo.swapfile = false
  vim.wo.wrap = false

  vim.keymap.set('n', '&lt;CR&gt;', function()
    local line = vim.fn.getline('.')
    vim.cmd('e ' .. line)
  end, { buffer = true })

  local buf = vim.api.nvim_get_current_buf()
  vim.keymap.set('n', 'q', function() vim.cmd('bd! ' .. buf) end, { buffer = buf, noremap = true, silent = true })
  vim.keymap.set('n', '&lt;Tab&gt;', function() vim.cmd('bd! ' .. buf) end, { buffer = buf, noremap = true, silent = true })
  vim.cmd('normal! gg')
end
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;6. Word Count, URL Maker, and Directory Prefix&lt;/h2&gt;
    &lt;p&gt;
      Several smaller utilities fill in the gaps:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;code&gt;word_count()&lt;/code&gt; – runs &lt;code&gt;detex&lt;/code&gt; on a LaTeX file and pipes it
          through &lt;code&gt;wc -w&lt;/code&gt; to count plain‑text words (ignoring markup).&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;make_url(word)&lt;/code&gt; – replaces every occurrence of a word with an HTML
          link pointing to &lt;code&gt;https://word.com&lt;/code&gt;.  Useful for quickly linking to
          domains in blog articles.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;get_dir_number()&lt;/code&gt; – extracts the leading numeric prefix of the current
          working directory, used in LaTeX snippet auto‑numbering.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;7. Save Bindings and Functions&lt;/h2&gt;
    &lt;p&gt;
      &lt;code&gt;save_vim_bindings_and_functions()&lt;/code&gt; captures all current key mappings and
      function definitions into a buffer.  This is invaluable for debugging or documenting
      your setup.  It uses Vim’s &lt;code&gt;:redir&lt;/code&gt; mechanism to capture the output of
      &lt;code&gt;:map&lt;/code&gt; and &lt;code&gt;:function&lt;/code&gt; and writes them into a scratch buffer.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Integration: Keymaps and Commands&lt;/h2&gt;
    &lt;p&gt;
      These functions are exposed through two files:
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- keymaps.lua (excerpt)
vim.keymap.set("v", "&lt;leader&gt;c", functions.run_python_selection, { noremap = true })
vim.keymap.set("v", "&lt;leader&gt;r", functions.run_python_and_replace, { noremap = true })
vim.keymap.set("n", "&lt;leader&gt;wc", functions.word_count, opts)
vim.keymap.set("n", "&lt;leader&gt;mm", functions.output_old_files, opts)
vim.keymap.set("n", "&lt;leader&gt;cc", functions.command_history_explorer, opts)
vim.keymap.set("n", "&lt;leader&gt;rr", functions.output_regs, opts)
vim.keymap.set("n", "&lt;leader&gt;ranger", functions.ranger_chooser, opts)
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- commands.lua (excerpt)
vim.api.nvim_create_user_command("WC", functions.word_count, {})
vim.api.nvim_create_user_command("Mm", functions.output_old_files, {})
vim.api.nvim_create_user_command("Cc", functions.command_history_explorer, {})
vim.api.nvim_create_user_command("Ss", functions.search_command_history, {})
vim.api.nvim_create_user_command("Rr", functions.output_regs, {})
vim.api.nvim_create_user_command("HH", functions.wrap_code_in_buffer, {})
vim.api.nvim_create_user_command("RangerChooser", functions.ranger_chooser, {})
vim.api.nvim_create_user_command("EngType", functions.eng_type, {})
vim.api.nvim_create_user_command("FrType", functions.fr_type, {})
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      User commands (e.g., &lt;code&gt;:WC&lt;/code&gt;, &lt;code&gt;:Cc&lt;/code&gt;) are intentionally uppercase
      and short to minimise typing.  Keymaps are mnemonic: &lt;code&gt;&amp;lt;leader&amp;gt;cc&lt;/code&gt; for
      &lt;strong&gt;c&lt;/strong&gt;ommand history, &lt;code&gt;&amp;lt;leader&amp;gt;rr&lt;/code&gt; for
      &lt;strong&gt;r&lt;/strong&gt;egisters, etc.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Design Philosophy: Functions as Infrastructure&lt;/h2&gt;
    &lt;p&gt;
      The unifying principle behind these utilities is that they treat Neovim as a programmable
      platform, not just a text editor.  Each function solves a concrete, recurring need in the
      developer’s daily workflow:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Async execution&lt;/strong&gt; – never block the UI.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Scratch buffers&lt;/strong&gt; – temporary, non‑file buffers that clean up after themselves.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Explicit buffer management&lt;/strong&gt; – every function that opens a buffer also defines
          how to close it (&lt;code&gt;q&lt;/code&gt; or &lt;code&gt;bd!&lt;/code&gt;).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Pure Lua, no plugin dependencies&lt;/strong&gt; – everything runs on the built‑in API,
          making the config portable.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      The result is an editor that behaves like an IDE, but without the weight.  In the next
      article, we’ll see how snippets push this automation even further, turning tiny triggers
      into large, context‑aware code blocks.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="lua"></category><category term="automation"></category><category term="async"></category><category term="functions"></category><category term="command history"></category><category term="registers"></category><category term="tooling"></category><category term="engineering"></category><category term="IDE"></category></entry><entry><title>Per‑Filetype Engineering: Tailoring Neovim for Python, LaTeX, HTML, and Markdown</title><link href="https://mosaid.xyz/articles/per%E2%80%91filetype-engineering-tailoring-neovim-for-python-latex-html-and-markdown-7.html" rel="alternate"></link><published>2026-05-15T15:31:53+00:00</published><updated>2026-05-15T15:31:53+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/per‑filetype-engineering-tailoring-neovim-for-python-latex-html-and-markdown-7.html</id><summary type="html">&lt;p&gt;This article explores the filetype‑specific layer of a Neovim configuration. It dissects each ftplugin file, the shared html_markdown_maps module, and the design pattern that eliminates duplication while keeping every filetype feel native.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;Context is Everything&lt;/h2&gt;
    &lt;p&gt;
      A general‑purpose editor can only take you so far.  The moment you switch from a Python script
      to a LaTeX document, your brain expects different indentation, different macros, and different
      ways to manipulate the text.  A well‑engineered Neovim configuration reacts to filetypes not as
      an afterthought, but as a first‑class design principle.
    &lt;/p&gt;
    &lt;p&gt;
      In this second article of the series, we open the &lt;code&gt;ftplugin/&lt;/code&gt; directory and the
      shared mapping modules that give each language its own personality – without duplicating logic.
      We'll walk through the full source of every relevant file, explaining the reasoning behind
      each buffer‑local option, each insert‑mode expansion, and each visual‑mode wrapping command.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The ftplugin Mechanism&lt;/h2&gt;
    &lt;p&gt;
      Neovim automatically sources Lua files in &lt;code&gt;~/.config/nvim/ftplugin/&lt;/code&gt; when a buffer's
      filetype matches the filename (e.g., &lt;code&gt;python.lua&lt;/code&gt; for Python files).  This is the
      official way to inject buffer‑local settings without polluting global configuration.  Everything
      we set in these files is scoped to the current buffer, so you can have 2‑space indentation for
      HTML and 4‑space for Python simultaneously.
    &lt;/p&gt;
    &lt;p&gt;
      Our &lt;code&gt;ftplugin&lt;/code&gt; files do three things:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Set buffer‑local options&lt;/strong&gt; (tabstop, shiftwidth, textwidth, etc.).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Define insert‑mode keymaps&lt;/strong&gt; that expand into larger code constructs (e.g., typing &lt;code&gt;if&lt;/code&gt; becomes &lt;code&gt;if :&amp;lt;++&amp;gt;&lt;/code&gt; in Python).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Call shared mapping modules&lt;/strong&gt; to apply visual‑mode wrappers and other common utilities.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      This last point is crucial: HTML and Markdown share a large set of visual mappings (wrapping text in
      &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;, etc.), so we centralise that logic in
      &lt;code&gt;lua/config/html_markdown_maps.lua&lt;/code&gt; and require it from both ftplugins.  No duplication,
      no drift.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Python: 4‑Space Indentation and Quick Constructs&lt;/h2&gt;
    &lt;p&gt;
      The Python ftplugin sets &lt;code&gt;tabstop=4&lt;/code&gt;, &lt;code&gt;shiftwidth=4&lt;/code&gt;, and
      &lt;code&gt;textwidth=88&lt;/code&gt; (PEP 8 line length).  It also defines two normal‑mode mappings:
      &lt;code&gt;&amp;lt;leader&amp;gt;c&lt;/code&gt; to run the buffer asynchronously in a scratch buffer, and
      &lt;code&gt;&amp;lt;leader&amp;gt;i&lt;/code&gt; to open an interactive REPL in a vertical split.
    &lt;/p&gt;
    &lt;p&gt;
      The insert‑mode mappings are a collection of shorthand triggers: typing &lt;code&gt;if&lt;/code&gt; expands to
      &lt;code&gt;if :&amp;lt;++&amp;gt;&lt;/code&gt; with the cursor placed after the colon, &lt;code&gt;def&lt;/code&gt; expands to
      &lt;code&gt;def ():&amp;lt;++&amp;gt;&lt;/code&gt;, and so on.  The &lt;code&gt;&amp;lt;++&amp;gt;&lt;/code&gt; placeholders are jump
      targets (navigated with Ctrl‑L / Ctrl‑H) that appear in many languages throughout the config.
      This gives a consistent, muscle‑memory‑based editing flow.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- Python-specific settings
vim.bo.tabstop = 4
vim.bo.shiftwidth = 4
vim.bo.expandtab = true
vim.bo.textwidth = 88  -- PEP 8 line length

-- Run Python code and display output in a scratch buffer
local function run_python_buffer()
  local current_file = vim.fn.expand("%")

  -- Create scratch buffer
  vim.cmd("enew")
  local buf = vim.api.nvim_get_current_buf()

  -- Buffer settings
  vim.bo[buf].buftype = "nofile"
  vim.bo[buf].bufhidden = "wipe"
  vim.bo[buf].swapfile = false
  vim.bo[buf].filetype = "python"

  vim.wo.wrap = false
  vim.wo.number = true
  vim.wo.relativenumber = true

  vim.api.nvim_buf_set_lines(buf, 0, -1, false, {
    "Running Python script...",
    "File: " .. current_file,
    ""
  })

  local function append_lines(lines)
    if vim.api.nvim_buf_is_valid(buf) and lines then
      vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines)
      -- Scroll to bottom
      local last = vim.api.nvim_buf_line_count(buf)
      vim.api.nvim_win_set_cursor(0, { last, 0 })
    end
  end

  -- Start async job
  local job_id
  job_id = vim.fn.jobstart({ "python3", current_file }, {
    stdout_buffered = false,
    stderr_buffered = false,

    on_stdout = function(_, data)
      append_lines(data)
    end,

    on_stderr = function(_, data)
      append_lines(data)
    end,

    on_exit = function(_, exit_code)
      append_lines({
        "",
        "Execution finished (exit code: " .. exit_code .. ")",
      })

      -- Only set keymap if buffer still exists
      if vim.api.nvim_buf_is_valid(buf) then
        vim.keymap.set("n", "&amp;lt;CR&amp;gt;", function()
          if job_id then vim.fn.jobstop(job_id) end
          if vim.api.nvim_buf_is_valid(buf) then
            vim.cmd("bd!")
          end
        end, { buffer = buf })
      end
    end,
  })

  -- Kill job if buffer is wiped
  vim.api.nvim_create_autocmd("BufWipeout", {
    buffer = buf,
    once = true,
    callback = function()
      if job_id then vim.fn.jobstop(job_id) end
    end,
  })
end

-- Normal mode mappings for Python
vim.keymap.set('n', '&amp;lt;leader&amp;gt;c', run_python_buffer, { buffer = true, desc = "Run Python buffer" })
vim.keymap.set('n', '&amp;lt;leader&amp;gt;i', function()
  vim.cmd('TermExec cmd="python3 -i %" direction=vertical size=60')
end, { buffer = true, desc = "Interactive Python REPL" })

-- Insert mode mappings for Python
local insert_maps = {
  ['if'] = 'if :&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:a',
  ['for'] = 'for in :&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:i',
  ['def'] = 'def ():&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F(a',
  ['class'] = 'class :&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:a',
  ['imp'] = 'import ',
  ['from'] = 'from import &amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F:i',
  ['try'] = 'try:&amp;lt;CR&amp;gt;&amp;lt;++&amp;gt;&amp;lt;CR&amp;gt;except:&amp;lt;CR&amp;gt;&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;kkk0f:a',
  ['print'] = 'print()&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F(a',
  ['main'] = 'if __name__ == "__main__":&amp;lt;CR&amp;gt;&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;k0f:a',
  [';dv'] = '"""&amp;lt;CR&amp;gt;&amp;lt;CR&amp;gt;"""&amp;lt;ESC&amp;gt;kA',
}

for lhs, rhs in pairs(insert_maps) do
  vim.keymap.set('i', lhs, rhs, { buffer = true })
end
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;LaTeX: Compilation Pipeline and Rich Snippets&lt;/h2&gt;
    &lt;p&gt;
      The LaTeX ftplugin is the largest of the set.  It sets 2‑space indentation and provides
      normal‑mode mappings to compile with &lt;code&gt;xelatex&lt;/code&gt; (&lt;code&gt;&amp;lt;leader&amp;gt;c&lt;/code&gt;),
      open the resulting PDF (&lt;code&gt;&amp;lt;leader&amp;gt;o&lt;/code&gt;), and wrap the entire buffer in
      &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags for blog post inclusion (&lt;code&gt;&amp;lt;leader&amp;gt;w&lt;/code&gt;).
      The compilation runs asynchronously in a scratch buffer, outputting both stdout and stderr.
    &lt;/p&gt;
    &lt;p&gt;
      Insert‑mode mappings cover everything from single‑character overrides (e.g., &lt;code&gt;!&lt;/code&gt;
      inserts a backslash) to complex environment expansions (e.g., &lt;code&gt;,tcb&lt;/code&gt; inserts a
      custom &lt;code&gt;tcb&lt;/code&gt; environment by calling a Lua function).  Visual‑mode mappings allow
      wrapping selected text in &lt;code&gt;\textbf&lt;/code&gt;, &lt;code&gt;\underline&lt;/code&gt;, math mode, and
      custom colour boxes – all through muscle‑memory shortcuts.
    &lt;/p&gt;
    &lt;p&gt;
      Note the use of a helper to extract the current directory's numeric prefix: this is used
      to auto‑number exercises in LaTeX snippets defined elsewhere (we'll cover snippet design
      in article #4).  For now, the important insight is that ftplugin files can access shared
      helper modules and even call Lua functions from snippet libraries.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;

-- LaTeX-specific settings
vim.bo.tabstop = 2
vim.bo.shiftwidth = 2
vim.bo.expandtab = true

-- Set global variable with filename
local get_curr_file_base_name = function()
  local handle = io.popen("basename $(pwd) | cut -d'-' -f1")
  local result = handle:read("*a")
  handle:close()
  return result:gsub('\n', '')
end

vim.g.curr_file_base_name = get_curr_file_base_name()

-- Run LaTeX compilation asynchronously in a scratch buffer
local function run_tex()
  local current_file = vim.fn.expand("%")
  local pdf_file = vim.fn.expand("%:p:r") .. ".pdf"

  -- Create scratch buffer
  vim.cmd("enew")
  local buf = vim.api.nvim_get_current_buf()

  -- Buffer settings
  vim.bo[buf].buftype = "nofile"
  vim.bo[buf].bufhidden = "wipe"
  vim.bo[buf].swapfile = false
  vim.bo[buf].filetype = "texlog"

  vim.wo.wrap = false
  vim.wo.number = true
  vim.wo.relativenumber = true

  vim.api.nvim_buf_set_lines(buf, 0, -1, false, {
    "Compiling with xelatex, please wait...",
    ""
  })

  local function append_lines(lines)
    if vim.api.nvim_buf_is_valid(buf) and lines then
      vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines)
      -- Scroll to bottom
      local last = vim.api.nvim_buf_line_count(buf)
      vim.api.nvim_win_set_cursor(0, { last, 0 })
    end
  end

  -- Start async job
  local job_id
  job_id = vim.fn.jobstart({ "xelatex", current_file }, {
    stdout_buffered = false,
    stderr_buffered = false,

    on_stdout = function(_, data)
      append_lines(data)
    end,

    on_stderr = function(_, data)
      append_lines(data)
    end,

    on_exit = function(_, exit_code)
      append_lines({
        "",
        "Compilation Finished (exit code: " .. exit_code .. ")",
        pdf_file,
      })

      -- Only set keymap if buffer still exists
      if vim.api.nvim_buf_is_valid(buf) then
        vim.keymap.set("n", "&amp;lt;CR&amp;gt;", function()
          if job_id then vim.fn.jobstop(job_id) end
          if vim.api.nvim_buf_is_valid(buf) then
            vim.cmd("bd!")
          end
        end, { buffer = buf })
      end
    end,
  })

  -- Kill job if buffer is wiped
  vim.api.nvim_create_autocmd("BufWipeout", {
    buffer = buf,
    once = true,
    callback = function()
      if job_id then vim.fn.jobstop(job_id) end
    end,
  })
end

-- View PDF
local function view_tex()
  local pdf = vim.fn.expand('%:p'):gsub('%.tex$', '.pdf')
  vim.fn.execute('!nohup evince ' .. vim.fn.shellescape(pdf) .. ' &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;')
  vim.cmd('redraw!')
end

-- Wrap buffer in HTML tags
local function wrap_buffer_in_html_tags()
  -- Yank the entire buffer content
  vim.cmd('normal! ggVGy')

  -- Replace special characters in the yanked content
  local content = vim.fn.getreg('0')
  content = content:gsub('&amp;amp;', '&amp;amp;amp;')
  content = content:gsub('&amp;lt;', '&amp;amp;lt;')
  content = content:gsub('&amp;gt;', '&amp;amp;gt;')
  vim.fn.setreg('0', content)

  -- Get current filename and append .html
  local current_filename = vim.fn.expand('%:t:r') .. ".html"

  -- Open new buffer with HTML syntax
  vim.cmd('enew')
  vim.bo.filetype = 'html'
  vim.cmd('file ' .. current_filename)

  -- Insert opening tags
  vim.api.nvim_buf_set_lines(0, 0, -1, false, {
    '&amp;lt;pre class="language-latex"&amp;gt;',
    '&amp;lt;code class="language-latex"&amp;gt;'
  })

  -- Paste the content
  vim.cmd('normal! 2Go')
  vim.cmd('normal! "0p')

  -- Insert closing tags
  vim.api.nvim_buf_set_lines(0, -1, -1, false, {
    '&amp;lt;/code&amp;gt;',
    '&amp;lt;/pre&amp;gt;'
  })

  -- Move cursor to top
  vim.cmd('normal! gg')
end

-- Normal mode mappings
vim.keymap.set('n', '&amp;lt;leader&amp;gt;c', run_tex, { buffer = true })
vim.keymap.set('n', '&amp;lt;leader&amp;gt;o', view_tex, { buffer = true })
vim.keymap.set('n', '&amp;lt;leader&amp;gt;w', wrap_buffer_in_html_tags, { buffer = true })

-- Insert mode mappings
local insert_maps = {
  ['!'] = '\\',
  ['§'] = '!',
  ['qq'] = '\\quad ',
  ['tt'] = '~\\text{}~&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a',
  [',hs'] = '\\hspace*{cm}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a',
  [',vs'] = '\\vspace*{cm}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a',
  [',bf'] = '\\textbf{}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a',
  [',u'] = '\\underline{}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a',
  [',tcb'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").tcb()&amp;lt;CR&amp;gt;',
  [',nn'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").d1()&amp;lt;CR&amp;gt;',
  [',ds'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").ds()&amp;lt;CR&amp;gt;',
  [',st'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").st()&amp;lt;CR&amp;gt;',
  [',ar'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").ar()&amp;lt;CR&amp;gt;',
  [',gg'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").gg()&amp;lt;CR&amp;gt;',
  [',mt'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").mtab()&amp;lt;CR&amp;gt;',
  [',dd'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").d1()&amp;lt;CR&amp;gt;',
  [',li'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").li()&amp;lt;CR&amp;gt;',
  [',ti'] = '&amp;lt;ESC&amp;gt;:lua require("config.tex_snippets").tight()&amp;lt;CR&amp;gt;',
  [',mm'] = '\\usepackage{amsmath, amsfonts, amssymb, amsthm}\n\\usepackage{mathrsfs}',
  [',ig'] = '\\includegraphics[width=&amp;lt;++&amp;gt;cm]{&amp;lt;++&amp;gt;}',
  [',bm'] = '\\def\\bottommsg{{\\normalsize{ \\vskip 3pt \\hrule height 3pt \\vskip 5pt \\RL{\\arabicfont &amp;lt;++&amp;gt; }}}}',
  [',o'] = '$(O; \\vec i, \\vec j)$',
  ['$$'] = '~$$~&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F$i',
  ['['] = '[]&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F]i',
  ['{'] = '{}&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F}i',
  ['('] = '()&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F)i',
  [',no'] = '\\noindent',
  ['...'] = '\\cdots',
  ['&amp;gt;='] = '\\ge',
  ['&amp;lt;='] = '\\le',
  [',xx'] = '\\times',
  ['°'] = '$',
  ['ł'] = '{}&amp;lt;Esc&amp;gt;F{a',
  ['ĸ'] = '&amp;amp;'
}

for lhs, rhs in pairs(insert_maps) do
  vim.keymap.set('i', lhs, rhs, { buffer = true })
end

-- Visual mode mappings
local visual_maps = {
  ['&amp;lt;leader&amp;gt;$'] = 's~$&amp;lt;C-r&amp;gt;"$~&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;',
  ['&amp;lt;leader&amp;gt;u'] = 'c\\underline{&amp;lt;C-r&amp;gt;"}&amp;lt;++&amp;gt;',
  ['&amp;lt;leader&amp;gt;b'] = 'c\\textbf{&amp;lt;C-r&amp;gt;"}&amp;lt;++&amp;gt;',
  ['&amp;lt;leader&amp;gt;i'] = 'c\\textit{&amp;lt;C-r&amp;gt;"}&amp;lt;++&amp;gt;',
  ['&amp;lt;leader&amp;gt;cc'] = 'c\\textcolor{}{&amp;lt;C-r&amp;gt;"}&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;2F{a',
  ['1'] = 'c\\bm{\\textcolor{}{&amp;lt;C-r&amp;gt;"}}&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;2F{a',
  ['2'] = 'c\\bbox{&amp;lt;C-r&amp;gt;"}&amp;lt;Esc&amp;gt;',
  ['3'] = 'c\\rbox{&amp;lt;C-r&amp;gt;"}&amp;lt;Esc&amp;gt;',
  ['4'] = 'c\\obox{&amp;lt;C-r&amp;gt;"}&amp;lt;Esc&amp;gt;',
  ['5'] = 'c~$&amp;lt;C-r&amp;gt;"$~&amp;lt;Esc&amp;gt;',
  ['6'] = '&amp;lt;Esc&amp;gt;&amp;lt;cmd&amp;gt;\'&amp;lt;,\'&amp;gt;s/\\\\\\[/\\~\\(/e&amp;lt;CR&amp;gt;&amp;lt;cmd&amp;gt;\'&amp;lt;,\'&amp;gt;s/\\\\\\]/\\\\)\\~/e&amp;lt;CR&amp;gt;',
  ['9'] = '&amp;lt;Esc&amp;gt;&amp;lt;cmd&amp;gt;\\&amp;lt;,&amp;gt;s/\\\\(/\\~$/eg&amp;lt;CR&amp;gt;&amp;lt;cmd&amp;gt;\\&amp;lt;,&amp;gt;s/\\\\)/\\$\\~/eg&amp;lt;CR&amp;gt;'
}

for lhs, rhs in pairs(visual_maps) do
  vim.keymap.set('v', lhs, rhs, { buffer = true })
end

-- Operator-pending mode mapping
vim.keymap.set('o', 'à', '$', { buffer = true })
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;HTML &amp;amp; Markdown: Shared Wrappers and Insert Maps&lt;/h2&gt;
    &lt;p&gt;
      HTML and Markdown share the same visual wrapping commands, so their ftplugin files are minimal:
      each sets 2‑space indentation and then calls &lt;code&gt;require("config.html_markdown_maps")()&lt;/code&gt;.
      The HTML ftplugin also defines a few insert‑mode mappings for comments and tag pairs.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- ftplugin/html.lua
vim.bo.tabstop = 2
vim.bo.shiftwidth = 2
vim.bo.expandtab = true

-- Insert mode mappings (like your tex ones)
local insert_maps = {
  ['!'] = '&amp;lt;!--  --&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F i',          -- HTML comment
  ['&amp;lt;'] = '&amp;lt;&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F&amp;gt;a',                  -- open/close tag
  -- add other quick expansions
}
for lhs, rhs in pairs(insert_maps) do
  vim.keymap.set('i', lhs, rhs, { buffer = true })
end

-- Load shared mappings
require("config.html_markdown_maps")()
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- ftplugin/markdown.lua
vim.bo.tabstop = 2
vim.bo.shiftwidth = 2
vim.bo.expandtab = true

require("config.html_markdown_maps")()
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Shared Mapping Module: html_markdown_maps.lua&lt;/h2&gt;
    &lt;p&gt;
      This module is a Lua function that, when called, registers visual‑mode mappings for both HTML
      and Markdown buffers.  It uses a pure‑Lua &lt;code&gt;wrap_visual_text&lt;/code&gt; function that handles
      linewise, characterwise, and blockwise selections correctly – no shelling out, no edge‑case
      failures.  It also provides a &lt;code&gt;ulify_selection&lt;/code&gt; function to convert a block of text
      into an HTML unordered list.
    &lt;/p&gt;
    &lt;p&gt;
      Because this logic is centralised, any improvement to visual wrapping (e.g., supporting
      additional selection modes) benefits both HTML and Markdown immediately.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- lua/config/html_markdown_maps.lua
-- Shared visual/insert mappings for HTML and Markdown
-- All wraps use a pure‑Lua function – no shell, no gv, no E488.

-- ----------------------------------------------------------------------
-- Pure‑Lua wrapper that mimics your sed replacements
--   v/V/Ctrl‑v   → wraps each selected line (or each block fragment)
--   v (charwise) → wraps only the selected text inside the line
-- ----------------------------------------------------------------------
local function wrap_visual_text(open_tag, close_tag)
  local mode = vim.fn.visualmode()
  local start_pos = vim.fn.getpos("v")
  local end_pos   = vim.fn.getpos(".")

  -- Normalise order: start before end
  if start_pos[2] &amp;gt; end_pos[2] or (start_pos[2] == end_pos[2] and start_pos[3] &amp;gt; end_pos[3]) then
    start_pos, end_pos = end_pos, start_pos
  end

  local buf = vim.api.nvim_get_current_buf()
  local sr = start_pos[2] - 1   -- start row (0‑based)
  local sc = start_pos[3] - 1   -- start col (0‑based)
  local er = end_pos[2] - 1     -- end row
  local ec = end_pos[3]         -- end col (exclusive for nvim_buf_get_text)

  if mode == 'V' then
    -- Linewise: wrap each full line individually
    local lines = vim.api.nvim_buf_get_lines(buf, sr, er + 1, false)
    local result = {}
    for _, line in ipairs(lines) do
      local trimmed = vim.trim(line)
      if trimmed ~= "" then
        table.insert(result, open_tag .. trimmed .. close_tag)
      else
        table.insert(result, line)   -- keep empty lines as-is
      end
    end
    vim.api.nvim_buf_set_lines(buf, sr, er + 1, false, result)
  else
    -- Character‑ or blockwise: get the exact selection
    local region = vim.api.nvim_buf_get_text(buf, sr, sc, er, ec, {})
    local text = table.concat(region, '\n')
    local wrapped = open_tag .. text .. close_tag
    local new_lines = vim.split(wrapped, '\n')
    vim.api.nvim_buf_set_text(buf, sr, sc, er, ec, new_lines)
  end
end

-- ----------------------------------------------------------------------
-- Your original ulify_selection (unchanged)
-- ----------------------------------------------------------------------
local function ulify_selection()
  local start_line = vim.fn.line("'&amp;lt;")
  local end_line   = vim.fn.line("'&amp;gt;")
  local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)

  local result_lines = {}
  for _, line in ipairs(lines) do
    local trimmed = vim.trim(line)
    if trimmed ~= "" then
      local replaced = trimmed:gsub("%*%*(.-)%*%*", "&amp;lt;strong&amp;gt;%1&amp;lt;/strong&amp;gt;")
      table.insert(result_lines, "  &amp;lt;li&amp;gt;" .. replaced .. "&amp;lt;/li&amp;gt;")
    end
  end

  local final = { "&amp;lt;ul&amp;gt;" }
  for _, li in ipairs(result_lines) do
    table.insert(final, li)
  end
  table.insert(final, "&amp;lt;/ul&amp;gt;")

  vim.api.nvim_buf_set_lines(0, start_line - 1, end_line, false, final)
end

-- ----------------------------------------------------------------------
-- Setup mappings
-- ----------------------------------------------------------------------
return function()
  local opts = { buffer = true, silent = true, noremap = true }

  -- ====================================================
  -- Visual surrounds (all unified, pure Lua)
  -- ====================================================
  vim.keymap.set('v', '&amp;lt;leader&amp;gt;ss', function()
    wrap_visual_text('&amp;lt;strong style="color: #000000;"&amp;gt;', '&amp;lt;/strong&amp;gt;')
  end, opts)

  vim.keymap.set('v', '&amp;lt;leader&amp;gt;pp', function()
    wrap_visual_text('&amp;lt;p&amp;gt;', '&amp;lt;/p&amp;gt;')
  end, opts)

  vim.keymap.set('v', '&amp;lt;leader&amp;gt;hh', function()
    wrap_visual_text('&amp;lt;h2&amp;gt;', '&amp;lt;/h2&amp;gt;')
  end, opts)

  vim.keymap.set('v', '&amp;lt;leader&amp;gt;aa', function()
    wrap_visual_text('&amp;lt;a href="&amp;lt;++&amp;gt;" target="_blank"&amp;gt;', '&amp;lt;/a&amp;gt;')
  end, opts)

  -- ====================================================
  -- Other mappings (unchanged)
  -- ====================================================
  vim.keymap.set('v', '&amp;lt;leader&amp;gt;cc',
    'x&amp;lt;Esc&amp;gt;i&amp;lt;pre class="language-bash" &amp;gt;&amp;lt;Enter&amp;gt;   &amp;lt;code class="language-bash" &amp;gt;&amp;lt;Esc&amp;gt;po    &amp;lt;/code&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;br&amp;gt;&amp;lt;Esc&amp;gt;',
    opts)

  vim.keymap.set('i', '&amp;lt;leader&amp;gt;ff',
    '&amp;lt;Esc&amp;gt;:r ~/.vim/snippets/html/fig&amp;lt;CR&amp;gt;', opts)

  vim.keymap.set('i', '&amp;lt;&amp;lt;', '&amp;amp;lt;', opts)
  vim.keymap.set('i', '&amp;gt;&amp;gt;', '&amp;amp;gt;', opts)

  vim.keymap.set('v', '&amp;lt;leader&amp;gt;ul', ulify_selection, opts)
end
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Design Lessons&lt;/h2&gt;
    &lt;p&gt;
      The ftplugin architecture demonstrates a key principle: &lt;strong&gt;context should be handled
      as close to the buffer as possible&lt;/strong&gt;.  Global configuration sets the baseline;
      filetype‑specific modules add the personality.  Shared logic lives in a separate module
      and is required only where needed – no conditional checks based on filetype inside global
      keymaps.
    &lt;/p&gt;
    &lt;p&gt;
      In the next article, we'll explore the custom functions that power the normal‑mode utilities
      – async execution, register inspection, command history navigation – and how they transform
      Neovim into a programmable engineering dashboard.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="ftplugin"></category><category term="filetype"></category><category term="configuration"></category><category term="python"></category><category term="latex"></category><category term="html"></category><category term="markdown"></category><category term="keymaps"></category><category term="snippets"></category></entry><entry><title>Engineering a Developer‑Grade Neovim Environment: The Foundation</title><link href="https://mosaid.xyz/articles/engineering-a-developer%E2%80%91grade-neovim-environment-the-foundation-6.html" rel="alternate"></link><published>2026-05-15T12:52:37+00:00</published><updated>2026-05-15T12:52:37+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-15:/articles/engineering-a-developer‑grade-neovim-environment-the-foundation-6.html</id><summary type="html">&lt;p&gt;This article dissects the bootstrapping layer of a production Neovim configuration. It covers the init.lua entry point, lazy.nvim self‑loading, early colourscheme fallback, and the modular philosophy that makes the environment robust, fast, and completely reproducible.&lt;/p&gt;</summary><content type="html">&lt;article&gt;
  &lt;section&gt;
    &lt;h2&gt;Why the First 100 Milliseconds Matter&lt;/h2&gt;
    &lt;p&gt;
      A Neovim configuration that starts fast and behaves predictably isn’t an accident – it’s a designed system.
      When you open an editor dozens of times a day across different machines, the difference between a 80 ms cold start
      and a 400 ms one is the difference between an invisible tool and a constant reminder that something is loading.
      This article lays out the foundation of my daily driver: a fully modular, lazy‑loaded, deterministic Neovim setup
      that I’ve used for years to write LaTeX, Python, shell scripts, Markdown, and more.
    &lt;/p&gt;
    &lt;p&gt;
      We’ll start with the directory tree, then walk through every line of &lt;code&gt;init.lua&lt;/code&gt; and the
      lazy‑bootstrapping module, explaining the engineering decisions – not just the “what”, but the “why”.
      The complete file contents are included so you can rebuild this foundation yourself or adapt it
      to your own toolchain.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Directory Tree&lt;/h2&gt;
    &lt;p&gt;
      Below is the entire configuration tree at the time of writing.  Files that are central to this article
      are highlighted in bold:
    &lt;/p&gt;
    &lt;pre class="language-text"&gt;
&lt;code&gt;
~/.config/nvim/
├── init.lua                       &lt;strong&gt;&amp;lt;-- entry point&lt;/strong&gt;
├── lazy-lock.json                 &lt;strong&gt;&amp;lt;-- pinned plugin versions&lt;/strong&gt;
├── lua/
│   ├── config/
│   │   ├── lazy.lua               &lt;strong&gt;&amp;lt;-- bootstraps lazy.nvim&lt;/strong&gt;
│   │   ├── colors.lua
│   │   ├── commands.lua
│   │   ├── functions.lua
│   │   ├── html_markdown_maps.lua
│   │   ├── html_snippets.lua
│   │   ├── keymaps.lua
│   │   ├── options.lua
│   │   └── tex_snippets.lua
│   └── plugins/
│       └── core.lua
└── ftplugin/
    ├── html.lua
    ├── lua.lua
    ├── markdown.lua
    ├── python.lua
    └── tex.lua
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      The tree follows the standard Neovim convention: &lt;code&gt;init.lua&lt;/code&gt; in the root, &lt;code&gt;lua/&lt;/code&gt;
      for modules, &lt;code&gt;ftplugin/&lt;/code&gt; for filetype‑specific settings.  The entire configuration is
      lazy‑loaded, meaning no plugin is sourced until it is actually needed (filetype, command, or event).
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;The Entry Point: init.lua&lt;/h2&gt;
    &lt;p&gt;
      The entry point sets global options, defines the leader keys, configures persistent state,
      and schedules the later loading of plugins and UI.  Every choice is deliberate:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Leader keys:&lt;/strong&gt; &lt;code&gt;,&lt;/code&gt; as mapleader, &lt;code&gt;Space&lt;/code&gt; as local leader – keeps custom mappings comfortably under the left hand.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Basic settings:&lt;/strong&gt; line numbers, relative numbers, mouse support, 2‑space tabs, smart case – a sane baseline that individual filetypes can override.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Persistent history &amp;amp; undo:&lt;/strong&gt; &lt;code&gt;shada&lt;/code&gt; and &lt;code&gt;undofile&lt;/code&gt; are pushed to &lt;code&gt;stdpath('data')&lt;/code&gt;.  The &lt;code&gt;shada&lt;/code&gt; file stores command history and marks; the undo tree survives restarts.  Both are set to very high limits to never lose context.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Fallback colourscheme:&lt;/strong&gt; &lt;code&gt;desert&lt;/code&gt; is set immediately so the editor never looks broken, then &lt;code&gt;require('config.lazy')&lt;/code&gt; is called to pull in plugins.  After plugins load, a scheduled callback applies the real theme and custom highlights.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Autocommands:&lt;/strong&gt; trailing whitespace stripping on save, restoring cursor position, setting filetype for &lt;code&gt;.tex&lt;/code&gt; files, auto‑&lt;code&gt;lcd&lt;/code&gt; to file directory, and yank highlighting.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;Here is the complete &lt;code&gt;init.lua&lt;/code&gt;:&lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
-- Modern Neovim by MOSAID

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

-- Basic settings (keep your existing ones)
vim.o.number = true
vim.o.relativenumber = true
vim.o.termguicolors = true
vim.o.wrap = true
vim.o.tabstop = 2
vim.o.shiftwidth = 2
vim.o.expandtab = true
vim.o.mouse = "a"
vim.o.cmdheight = 1
vim.opt.ignorecase = true
vim.opt.smartcase = true


vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1

-- History, undo, backup
-- vim.opt.undofile = true
-- vim.opt.undodir = vim.fn.stdpath("data") .. "/undo"
-- vim.opt.undolevels = 1000
-- vim.opt.undoreload = 10000
-- vim.opt.backup = true
-- vim.opt.backupdir = vim.fn.stdpath("data") .. "/backup"
-- vim.opt.directory = vim.fn.stdpath("data") .. "/backup"
-- vim.opt.history = 10000

-- Maximum history
vim.opt.history = 10000          -- command and search history
vim.opt.undolevels = 10000        -- undo steps
vim.opt.undoreload = 100000       -- undo for this many lines

-- Persistent undo
vim.opt.undofile = true
vim.opt.undodir = vim.fn.stdpath("data") .. "/undo"

-- Persistent command/search history
vim.opt.shadafile = vim.fn.stdpath("data") .. "/shada/main.shada"
vim.opt.shada = "'100000,&lt;1000,s100,h"
vim.cmd("silent! rshada")  -- load at startup


-- Set a basic colorscheme immediately
vim.cmd("colorscheme desert")  -- Fallback colorscheme

-- Load modules in correct order
require("config.lazy")  -- This loads plugins first

-- After plugins are loaded, set up our colors
vim.schedule(function()
  require("config.colors").setup()  -- This sets tokyonight and custom highlights
  require("config.keymaps")
  require("config.commands")
end)


-- Auto-command to remove trailing whitespace
vim.api.nvim_create_autocmd("BufWritePre", {
  pattern = "*",
  callback = function()
    vim.cmd([[%s/\s\+$//e]])
  end
})


-- Remember last cursor position when reopening a file
vim.api.nvim_create_autocmd("BufReadPost", {
  pattern = "*",
  callback = function()
    local last_pos = vim.fn.line([['"]])
    if last_pos &gt; 1 and last_pos &lt;= vim.fn.line("$") then
      vim.cmd("normal! g`\"")
    end
  end,
})


-- Filetype detection autocmds
vim.api.nvim_create_autocmd({"BufEnter", "BufNew", "BufNewFile", "BufRead"}, {
  pattern = "*.tex",
  callback = function()
    vim.bo.filetype = "tex"
  end
})

-- Set current directory to directory of the current file, except /tmp
vim.api.nvim_create_autocmd("BufReadPost", {
  callback = function()
    local dir = vim.fn.expand("%:p:h")
    if dir ~= "" and not dir:match("^/tmp") then
      vim.cmd("silent! lcd " .. dir)
    end
  end,
})

-- Briefly highlight yanked text
vim.api.nvim_create_autocmd("TextYankPost", {
  pattern = "*",
  callback = function()
    vim.highlight.on_yank({
      higroup = "IncSearch",  -- or "Visual", "Search", etc.
      timeout = 500,          -- highlight duration in milliseconds
    })
  end,
})
&lt;/code&gt;
    &lt;/pre&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Bootstrapping lazy.nvim&lt;/h2&gt;
    &lt;p&gt;
      The &lt;code&gt;require("config.lazy")&lt;/code&gt; call inside &lt;code&gt;init.lua&lt;/code&gt; delegates to
      &lt;code&gt;lua/config/lazy.lua&lt;/code&gt;, which is the only file that touches plugin management.
      It clones lazy.nvim itself if missing, then loads the plugin specification from
      &lt;code&gt;lua/plugins/core.lua&lt;/code&gt;.
    &lt;/p&gt;
    &lt;p&gt;
      This file is intentionally minimal – it does not configure any plugins, only wires
      the package manager.  The &lt;code&gt;border = "rounded"&lt;/code&gt; setting is purely cosmetic.
      Every plugin is listed in &lt;code&gt;core.lua&lt;/code&gt; and lazy‑loaded by event, command,
      or filetype.  The &lt;code&gt;lazy-lock.json&lt;/code&gt; file pins exact commits so the environment
      is fully reproducible across machines.
    &lt;/p&gt;
    &lt;pre class="language-lua"&gt;
&lt;code class="language-lua"&gt;
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"

if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    lazypath,
  })
end

vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  spec = {
    { import = "plugins" },
  },
  ui = {
    border = "rounded"
  }
})
&lt;/code&gt;
    &lt;/pre&gt;
    &lt;p&gt;
      The &lt;code&gt;lazy-lock.json&lt;/code&gt; file (not shown in full here, but available in the
      configuration repository) records the exact commit of every plugin.  This guarantees
      that &lt;code&gt;lazy.sync()&lt;/code&gt; always restores the same plugin set, making the
      environment truly deterministic.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Ordering and the Scheduled Callback&lt;/h2&gt;
    &lt;p&gt;
      The line &lt;code&gt;vim.schedule(function() ... end)&lt;/code&gt; after loading lazy.nvim is critical.
      It defers the remaining setup until after the UI has been entered and plugins have been loaded
      (but not necessarily after they are all configured – lazy.nvim’s &lt;code&gt;config&lt;/code&gt; keys
      run during the &lt;code&gt;setup()&lt;/code&gt; call).  This prevents race conditions where a
      colourscheme or highlight group is set before the plugin that defines it has been sourced.
    &lt;/p&gt;
    &lt;p&gt;
      Inside the scheduled callback we call:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;code&gt;require("config.colors").setup()&lt;/code&gt; – applies the chosen theme (Nordic) and
          sets custom highlight groups.  A fallback to &lt;code&gt;desert&lt;/code&gt; is already in place,
          so the editor is always readable.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;require("config.keymaps")&lt;/code&gt; – defines global keybindings that rely on
          functions from &lt;code&gt;config.functions&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;require("config.commands")&lt;/code&gt; – creates user commands (like &lt;code&gt;:WC&lt;/code&gt;,
          &lt;code&gt;:Cc&lt;/code&gt;, etc.) that also depend on those utility functions.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      Because everything after plugins is scheduled, the editor remains responsive during
      startup and never throws “module not found” errors even if a plugin fails to load.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Early Fallback Colourscheme&lt;/h2&gt;
    &lt;p&gt;
      Immediately after the basic options, we call &lt;code&gt;vim.cmd("colorscheme desert")&lt;/code&gt;.
      This is the ultimate safety net: if the later scheduled &lt;code&gt;colors.setup()&lt;/code&gt; fails
      (for example, because the Tokyonight plugin is missing), the editor still has a functioning
      colourscheme.  It also eliminates the “flash of unstyled text” that occurs when Neovim
      starts with no theme and then asynchronously loads one.  The fallback is always overridden
      by the scheduled callback, so users never see &lt;code&gt;desert&lt;/code&gt; once the real theme kicks in.
    &lt;/p&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Autocommands as Infrastructure&lt;/h2&gt;
    &lt;p&gt;
      The autocommands defined in &lt;code&gt;init.lua&lt;/code&gt; are small pieces of infrastructure that
      would be annoying to lose:
    &lt;/p&gt;
    &lt;ul class="compact-list"&gt;
      &lt;li&gt;&lt;strong&gt;Trailing whitespace removal&lt;/strong&gt; on save – keeps diffs clean without
          manual clean‑up.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Cursor position restoration&lt;/strong&gt; – when you reopen a file you left the
          day before, you’re exactly where you stopped.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Automatic filetype for &lt;code&gt;.tex&lt;/code&gt;&lt;/strong&gt; – some systems don’t
          recognise &lt;code&gt;.tex&lt;/code&gt; as LaTeX without this explicit mapping.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Auto‑&lt;code&gt;lcd&lt;/code&gt; to file directory&lt;/strong&gt; – makes
          &lt;code&gt;:Telescope find_files&lt;/code&gt; and relative imports work out of the box.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Yank highlight&lt;/strong&gt; – provides instant visual feedback that the yank
          succeeded, a subtle but powerful usability improvement.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/section&gt;

  &lt;section&gt;
    &lt;h2&gt;Reproducibility from the Ground Up&lt;/h2&gt;
    &lt;p&gt;
      Every line in these two files serves a purpose: there are no boilerplate copies from
      other people’s dotfiles, no unused settings, and every module is loaded lazily only when
      required.  The &lt;code&gt;lazy-lock.json&lt;/code&gt; ensures that even plugin versions are frozen,
      turning the entire configuration into a &lt;em&gt;build artifact&lt;/em&gt; that can be checked out and
      expected to work identically on any Neovim 0.9+ installation.
    &lt;/p&gt;
    &lt;p&gt;
      In the next article we’ll dive into the filetype‑specific engines – how
      &lt;code&gt;ftplugin/*.lua&lt;/code&gt; and shared mapping modules create a context‑aware editor
      that feels custom‑built for Python, LaTeX, HTML, and Markdown.
    &lt;/p&gt;
  &lt;/section&gt;
&lt;/article&gt;</content><category term="vim"></category><category term="neovim"></category><category term="lazy.nvim"></category><category term="init.lua"></category><category term="configuration"></category><category term="modular"></category><category term="developer tools"></category><category term="vim"></category><category term="engineering"></category><category term="bootstrapping"></category></entry><entry><title>Engineering a Modal Text Object Engine in Vim: Custom Motions and Operator-Pending Grammars</title><link href="https://mosaid.xyz/articles/engineering-a-modal-text-object-engine-in-vim-custom-motions-and-operator-pending-grammars-3.html" rel="alternate"></link><published>2026-05-13T20:50:06+00:00</published><updated>2026-05-13T20:50:06+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-13:/articles/engineering-a-modal-text-object-engine-in-vim-custom-motions-and-operator-pending-grammars-3.html</id><summary type="html">&lt;p&gt;A production‑grade exploration of Vim’s operator‑pending mode: designing custom text objects for structured editing, chaining motions into composable grammars, and building a reusable engine that turns raw Vimscript into a declarative selection language. Covers everything from &lt;code&gt;omap&lt;/code&gt; trickery to integration with plugins and performance profiling.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Vim’s built‑in text objects—&lt;code&gt;iw&lt;/code&gt;, &lt;code&gt;i(&lt;/code&gt;, &lt;code&gt;at&lt;/code&gt;—are powerful, but they stop short of domain‑specific structures. What if you could select an indented block with &lt;code&gt;ii&lt;/code&gt;, a function argument with &lt;code&gt;ia&lt;/code&gt;, or a column of a Markdown table with &lt;code&gt;ic&lt;/code&gt;? For advanced users, the real magic lies in creating custom objects that compose seamlessly with any operator—&lt;code&gt;dii&lt;/code&gt;, &lt;code&gt;cia&lt;/code&gt;, &lt;code&gt;yic&lt;/code&gt;—turning Vim into a structured editing engine tailored to your exact filetypes.&lt;/p&gt;

&lt;p&gt;This article documents a system that generalises custom text objects into a declarative grammar, built entirely from Vim’s native operator‑pending mode and a handful of helper functions. It is not a plugin; it is a pattern you can integrate directly into your &lt;code&gt;.vimrc&lt;/code&gt; or ftplugin files, giving you deterministic, composable selections without external dependencies.&lt;/p&gt;

&lt;h2&gt;Architecture of Operator‑Pending Mode&lt;/h2&gt;

&lt;p&gt;To build new objects, you must first understand the state machine behind every &lt;code&gt;d}&lt;/code&gt; or &lt;code&gt;cib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Operator‑pending mode&lt;/strong&gt; is entered after an operator key (&lt;code&gt;d&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;g~&lt;/code&gt;, etc.). Vim then waits for a motion or a text object to define the range.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Text objects&lt;/strong&gt; are special mappings that define two boundaries: a start and an end. They can be character‑wise, line‑wise, or block‑wise.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;The &lt;code&gt;omap&lt;/code&gt; and &lt;code&gt;xmap&lt;/code&gt; commands&lt;/strong&gt; allow you to define mappings that only apply in operator‑pending or visual mode, respectively. This is the key to making custom objects operator‑agnostic.&lt;/p&gt;

&lt;p&gt;The engine we design will leverage &lt;code&gt;omap&lt;/code&gt; mappings that call functions to calculate a region, and then set the &lt;code&gt;'[&lt;/code&gt; and &lt;code&gt;']&lt;/code&gt; marks appropriately so the pending operator acts on exactly the right span.&lt;/p&gt;

&lt;h2&gt;Core Engine: The Object Factory&lt;/h2&gt;

&lt;h3&gt;Declaring a Text Object&lt;/h3&gt;

&lt;p&gt;Instead of hand‑writing a dozen nearly identical functions, we define a small specification language. Each object is described by a dictionary containing:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;name&lt;/code&gt;&lt;/strong&gt;: a unique label (used for debugging and documentation).&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;selector&lt;/code&gt;&lt;/strong&gt;: a Vimscript expression that returns a pair of positions—start and end (inclusive). It receives a boolean &lt;code&gt;inner&lt;/code&gt; to distinguish &lt;code&gt;i&lt;/code&gt; from &lt;code&gt;a&lt;/code&gt; variants.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;'char'&lt;/code&gt;, &lt;code&gt;'line'&lt;/code&gt;, or &lt;code&gt;'block'&lt;/code&gt;. This determines how the operator applies (e.g., &lt;code&gt;d&lt;/code&gt; uses line‑wise if the object is line‑wise).&lt;/p&gt;

&lt;p&gt;We then register this specification with the engine:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
call textobj#register({
    \ 'name': 'indent',
    \ 'selector': 'textobj#indent#select(inner)',
    \ 'mode': 'line'
    \})
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;textobj#indent#select&lt;/code&gt; function scans for lines with the same indentation, wrapping the inner or outer block. The engine handles the rest: creating the &lt;code&gt;omap&lt;/code&gt; entries for both &lt;code&gt;ii&lt;/code&gt; and &lt;code&gt;ai&lt;/code&gt;, setting marks, and respecting the pending operator.&lt;/p&gt;

&lt;h3&gt;Engine Implementation&lt;/h3&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! textobj#register(object) abort
    let s:objects[a:object.name] = a:object

    " inner variant: i + name (e.g., ii)
    execute 'onoremap &amp;lt;silent&amp;gt; i' . a:object.name
        \ . ' :&amp;lt;C-u&amp;gt;call textobj#apply('
        \ . string(a:object.name) . ', 1)&amp;lt;CR&amp;gt;'

    " a variant: a + name (e.g., ai)
    execute 'onoremap &amp;lt;silent&amp;gt; a' . a:object.name
        \ . ' :&amp;lt;C-u&amp;gt;call textobj#apply('
        \ . string(a:object.name) . ', 0)&amp;lt;CR&amp;gt;'
endfunction

function! textobj#apply(name, inner) abort
    let obj = s:objects[a:name]
    let [l:start, l:end] = call(obj.selector, [a:inner])
    if l:start == 0 || l:end == 0 | return | endif

    " Set the '[' and ']' marks; Vim uses these for the operator range
    call setpos("'[", [0, l:start, 1, 0])
    call setpos("']", [0, l:end, 1, 0])

    " Force the operator to use the correct mode
    if obj.mode ==# 'line'
        normal! V
    elseif obj.mode ==# 'char'
        normal! v
    elseif obj.mode ==# 'block'
        " blockwise needs visual block – handled with special marks
        normal! &amp;lt;C-v&amp;gt;
    endif
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;This is the entire engine—under 30 lines of Vimscript. It is deterministic, transparent, and composable. Every custom object you create from this point forward is just a selector function and a registration call.&lt;/p&gt;

&lt;h2&gt;Building Real Objects&lt;/h2&gt;

&lt;h3&gt;Indentation Block (&lt;code&gt;ii&lt;/code&gt; / &lt;code&gt;ai&lt;/code&gt;)&lt;/h3&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! textobj#indent#select(inner) abort
    let indent = indent(line('.'))
    let start = line('.')
    let end = line('.')

    " search upward for a line with less indent (outer boundary)
    while start &amp;gt; 1 &amp;amp;&amp;amp; indent(start - 1) &amp;gt;= indent
        let start -= 1
    endwhile

    " search downward for a line with less indent
    while end &amp;lt; line('$') &amp;amp;&amp;amp; indent(end + 1) &amp;gt;= indent
        let end += 1
    endwhile

    if a:inner
        " inner: exclude the first and last line if they are blank
        if getline(start) =~# '^\s*$' | let start += 1 | endif
        if getline(end) =~# '^\s*$' | let end -= 1 | endif
    endif

    return [start, end]
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Now &lt;code&gt;dii&lt;/code&gt; deletes the current indented block, &lt;code&gt;yii&lt;/code&gt; yanks it, and &lt;code&gt;&amp;gt;ii&lt;/code&gt; increases its indentation. The object definition stays clean because the engine separates the selection logic from the operator handling.&lt;/p&gt;

&lt;h3&gt;Function Arguments (&lt;code&gt;ia&lt;/code&gt; / &lt;code&gt;aa&lt;/code&gt;)&lt;/h3&gt;

&lt;p&gt;A more ambitious selector that parses the current line for a comma‑separated argument list. The inner version (&lt;code&gt;ia&lt;/code&gt;) selects the argument text excluding optional surrounding whitespace; the outer version includes trailing commas and spaces.&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! textobj#arg#select(inner) abort
    let line = getline('.')
    let col = col('.') - 1  " 0-indexed

    " Find argument boundaries: commas or parentheses
    let pattern = '[,()]'
    let start = 0
    let end = len(line)

    " search backward for opening delimiter
    let idx = col
    while idx &amp;gt; 0
        if line[idx] =~# pattern
            let start = idx + 1
            break
        endif
        let idx -= 1
    endwhile

    " search forward for closing delimiter
    let idx = col
    while idx &amp;lt; len(line) - 1
        if line[idx] =~# pattern
            let end = idx
            break
        endif
        let idx += 1
    endwhile

    if a:inner
        " trim whitespace from both ends
        while start &amp;lt; end &amp;amp;&amp;amp; line[start] =~# '\s'
            let start += 1
        endwhile
        while end &amp;gt; start &amp;amp;&amp;amp; line[end - 1] =~# '\s'
            let end -= 1
        endwhile
    endif

    " Return line positions; the engine will convert to mark positions
    return [line('.'), line('.')]   " both on the same line – set marks manually
    " For same‑line object we need to adjust col in marks:
    " We'll do this inside a wrapper that returns [lnum, col] pairs.
    " (Full implementation omitted for brevity – see repo.)
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Because the engine supports only line‑wise marks by default, same‑line objects require a slight extension: the selector can return a list of &lt;code&gt;[line, col]&lt;/code&gt; pairs, and &lt;code&gt;textobj#apply&lt;/code&gt; sets &lt;code&gt;setpos("'[", [0, l:line, l:col, 0])&lt;/code&gt; accordingly. This is a small modification that makes the engine handle all mode types uniformly.&lt;/p&gt;

&lt;h3&gt;Markdown Table Cell (&lt;code&gt;ic&lt;/code&gt; / &lt;code&gt;ac&lt;/code&gt;)&lt;/h3&gt;

&lt;p&gt;Another selector that navigates the vertical bar &lt;code&gt;|&lt;/code&gt; separators in a GitHub‑flavoured Markdown table. The inner object selects the cell content; the outer includes the pipe characters. Thanks to the engine, &lt;code&gt;cic&lt;/code&gt; clears a cell and leaves you in insert mode—as if Vim knew about Markdown tables natively.&lt;/p&gt;

&lt;h2&gt;Composition with Operators: The Power of Grammar&lt;/h2&gt;

&lt;p&gt;Because every custom object is exposed as an operator‑pending mapping, they automatically compose with any built‑in operator, and even with custom operators from other plugins (e.g., &lt;code&gt;commentary&lt;/code&gt;’s &lt;code&gt;gc&lt;/code&gt;). You can chain them with counts, repeat them with &lt;code&gt;.&lt;/code&gt; (if the operator is repeatable), and use them from visual mode if you add &lt;code&gt;xmap&lt;/code&gt; equivalents. The engine provides a &lt;code&gt;textobj#visual&lt;/code&gt; helper that does exactly that:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! textobj#visual(name, inner) abort
    call textobj#apply(a:name, a:inner)
    " Already in visual mode from the apply()
    " Remap i/a within visual mode to keep extending
    execute 'xnoremap &amp;lt;buffer&amp;gt; i' . a:name . ' :&amp;lt;C-u&amp;gt;call textobj#apply('
        \ . string(a:name) . ', 1)&amp;lt;CR&amp;gt;'
    execute 'xnoremap &amp;lt;buffer&amp;gt; a' . a:name . ' :&amp;lt;C-u&amp;gt;call textobj#apply('
        \ . string(a:name) . ', 0)&amp;lt;CR&amp;gt;'
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Now &lt;code&gt;vii&lt;/code&gt; selects an indented block, and pressing &lt;code&gt;ii&lt;/code&gt; again extends the selection to the next outer block—a true modal grammar.&lt;/p&gt;

&lt;h2&gt;Performance and Edge Cases&lt;/h2&gt;

&lt;p&gt;Text objects must be fast because they are invoked hundreds of times during a typical editing session. Selector functions should avoid external processes and prefer built‑in functions like &lt;code&gt;search()&lt;/code&gt;, &lt;code&gt;indent()&lt;/code&gt;, and &lt;code&gt;getline()&lt;/code&gt;. The registration overhead is negligible; &lt;code&gt;omap&lt;/code&gt; definitions are stored once.&lt;/p&gt;

&lt;p&gt;Some edge cases to handle:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Empty buffer/line:&lt;/strong&gt; Selectors should return &lt;code&gt;[0,0]&lt;/code&gt; to abort the operation gracefully.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Nested structures:&lt;/strong&gt; Indentation selectors need a clear definition of “same indent or deeper”. The provided &lt;code&gt;textobj#indent#select&lt;/code&gt; treats all lines with indent ≥ current as part of the block, which works well for Python but may need tuning for languages with free‑form indentation.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Blockwise operations:&lt;/strong&gt; Vim’s blockwise mode requires special care: after setting marks with &lt;code&gt;setpos&lt;/code&gt;, you must start a blockwise visual selection (&lt;code&gt;gv&lt;/code&gt;) so the operator can interpret the marks as a block. Our engine does this conditionally.&lt;/p&gt;

&lt;h2&gt;Integration with Your Existing Workflow&lt;/h2&gt;

&lt;p&gt;The engine is a file you can drop into &lt;code&gt;~/.vim/autoload/textobj.vim&lt;/code&gt; and source from your &lt;code&gt;.vimrc&lt;/code&gt;. Individual selectors become small autoload files (&lt;code&gt;textobj/indent.vim&lt;/code&gt;, &lt;code&gt;textobj/arg.vim&lt;/code&gt;) that are loaded on demand. This keeps startup time minimal and allows you to version‑control them independently.&lt;/p&gt;

&lt;p&gt;For users who already use plugins like &lt;code&gt;vim-textobj-user&lt;/code&gt; by Kana Natsuno, this engine offers a compatible but simpler alternative. You can even wrap the popular plugin’s API into our registration function to avoid duplication.&lt;/p&gt;

&lt;h2&gt;Tradeoffs&lt;/h2&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;No built‑in visual feedback:&lt;/strong&gt; Unlike some plugins, our engine doesn’t flash a highlight when an object is selected (you can add that with &lt;code&gt;:redraw&lt;/code&gt; and &lt;code&gt;matchadd&lt;/code&gt;). We prioritised minimalism.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Selector functions must be designed carefully:&lt;/strong&gt; They receive only an &lt;code&gt;inner&lt;/code&gt; flag, not the full operator context. If you need to behave differently for &lt;code&gt;c&lt;/code&gt; vs &lt;code&gt;d&lt;/code&gt;, you can inspect &lt;code&gt;v:operator&lt;/code&gt; within the selector, but this couples the two layers.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Same‑line objects demand extra mark handling:&lt;/strong&gt; The engine’s base version returns line numbers; the extended version that handles column positions adds complexity. The design documented here can be refined to accept either format seamlessly.&lt;/p&gt;

&lt;h2&gt;Future Directions&lt;/h2&gt;

&lt;p&gt;This engine can grow into a full domain‑specific language for structured editing:&lt;/p&gt;

&lt;ul class="compact-list"&gt;
  &lt;li&gt;Add &lt;strong&gt;tree‑sitter integration&lt;/strong&gt;: a selector that uses the syntax tree to define objects like functions, classes, or loops, making the engine filetype‑aware without per‑language rules.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GUI selection manager&lt;/strong&gt;: a picker (via &lt;code&gt;fzf&lt;/code&gt; or Vim’s &lt;code&gt;inputlist()&lt;/code&gt;) that lets you compose objects interactively.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Visual mode combinators&lt;/strong&gt;: extend the grammar so &lt;code&gt;vii}&lt;/code&gt; means “select inner indent block, then extend to the next paragraph”, merging two objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The foundational pattern—separating registration, mapping, and selection logic—is what makes this extensible. Every new object is just a function that sees the buffer and returns two points.&lt;/p&gt;

&lt;h2&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Building a custom text object engine is not about reinventing plugins; it’s about understanding Vim’s modal architecture so deeply that you can bend it to your will. With a tiny amount of Vimscript, you can unlock editing paradigms that feel like they were always part of the editor. This engine is now a permanent part of my &lt;code&gt;~/.vim&lt;/code&gt; tree, and I suspect it will become one in yours as well.&lt;/p&gt;</content><category term="vim"></category><category term="vim"></category><category term="text-objects"></category><category term="operator-pending"></category><category term="mappings"></category><category term="vimscript"></category><category term="structured-editing"></category><category term="custom-motions"></category><category term="composability"></category></entry><entry><title>Engineering a Vim-based LaTeX IDE: Custom Mappings, Snippets, and Compilation Workflow</title><link href="https://mosaid.xyz/articles/engineering-a-vim-based-latex-ide-custom-mappings-snippets-and-compilation-workflow-2.html" rel="alternate"></link><published>2026-05-13T20:50:06+00:00</published><updated>2026-05-13T20:50:06+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-13:/articles/engineering-a-vim-based-latex-ide-custom-mappings-snippets-and-compilation-workflow-2.html</id><summary type="html">&lt;p&gt;A deep dive into a production Vim configuration for LaTeX editing built around ftplugin hooks, custom compilation viewers, snippet repositories, and automated HTML export for static sites. Explores how to transform Vim into a deterministic, keyboard-driven LaTeX environment that eliminates boilerplate and integrates with modern developer toolchains.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Problem Statement&lt;/h2&gt;

&lt;p&gt;LaTeX editing is powerful but repetitive. Every new document demands the same boilerplate: preamble setup, package inclusion, math shortcuts, and environment scaffolding. While dedicated LaTeX IDEs exist, they often feel slow, lack the composability of the terminal, and force a mouse-driven workflow. For engineers and power users who live in Vim, the ideal solution is to build a custom, deterministic editing environment that automates the boring parts without sacrificing control.&lt;/p&gt;

&lt;p&gt;This article dissects a real-world Vim configuration for LaTeX editing, evolved over years of daily use. It lives at &lt;code&gt;~/.vim/after/ftplugin/tex.vim&lt;/code&gt; and leverages filetype-specific autocommands, a collection of snippet files, and custom functions that tie together compilation, PDF viewing, and even HTML export for static site generators like Pelican.&lt;/p&gt;

&lt;h2&gt;Architecture Overview&lt;/h2&gt;

&lt;p&gt;The system is a single Vim ftplugin file plus a directory of snippet templates. When a &lt;code&gt;.tex&lt;/code&gt; file is opened, Vim automatically sources the ftplugin, which:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Sets local buffer options&lt;/strong&gt; (indentation, expansion).&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Defines compilation and viewer functions&lt;/strong&gt; that capture &lt;code&gt;xelatex&lt;/code&gt; output in a new buffer and open the resulting PDF in Evince.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Creates insert-mode mappings&lt;/strong&gt; that expand short keystrokes into full LaTeX commands or import snippet files.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Provides visual-mode wrappers&lt;/strong&gt; to quickly surround selected text with formatting commands like &lt;code&gt;\textbf&lt;/code&gt; or inline math.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Exports the current buffer to an HTML code block&lt;/strong&gt; suitable for pasting into Pelican articles, bridging the gap between local authoring and static site publishing.&lt;/p&gt;

&lt;p&gt;The snippets themselves are independent &lt;code&gt;.tex&lt;/code&gt; files stored under &lt;code&gt;~/.vim/snippets/latex/&lt;/code&gt; and inserted via &lt;code&gt;:read&lt;/code&gt; commands bound to easy-to-type sequences like &lt;code&gt;;ds&lt;/code&gt; (exam template) or &lt;code&gt;;tcb&lt;/code&gt; (tcolorbox exercise).&lt;/p&gt;

&lt;h2&gt;Core Configuration&lt;/h2&gt;

&lt;p&gt;The ftplugin starts with the fundamentals:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
autocmd FileType tex setlocal tabstop=2 shiftwidth=2 expandtab
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;LaTeX benefits from consistent two-space indentation. The &lt;code&gt;expandtab&lt;/code&gt; setting ensures that all tabs are converted to spaces, which prevents formatting chaos when collaborating or sharing code.&lt;/p&gt;

&lt;h2&gt;Compilation Workflow&lt;/h2&gt;

&lt;h3&gt;Running xelatex and Capturing Output&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;RunTex()&lt;/code&gt; function embraces the Unix philosophy: it compiles the current &lt;code&gt;.tex&lt;/code&gt; file with &lt;code&gt;xelatex&lt;/code&gt;, discarding the need for a separate terminal window, and presents the output in a temporary buffer.&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! RunTex()
    let s:current_file = expand("%")
    enew | silent execute ".!xelatex " . shellescape(s:current_file, 1)
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
    if &amp;amp;number == 0
        set number
    endif
    set relativenumber
    normal! G
    nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :bd!&amp;lt;CR&amp;gt;
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Key design decisions:&lt;/p&gt;
&lt;ul class="compact-list"&gt;
  &lt;li&gt;&lt;strong&gt;Full log capture:&lt;/strong&gt; Using &lt;code&gt;enew&lt;/code&gt; followed by &lt;code&gt;.!xelatex ...&lt;/code&gt; reads the command output into a new buffer, preserving all warnings and errors.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Non-file buffer:&lt;/strong&gt; The buffer is set to &lt;code&gt;nofile&lt;/code&gt; and &lt;code&gt;nowrap&lt;/code&gt;, preventing accidental saves while keeping the log readable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Quick dismissal:&lt;/strong&gt; Pressing &lt;code&gt;&amp;lt;CR&amp;gt;&lt;/code&gt; in the output buffer deletes it with &lt;code&gt;:bd!&lt;/code&gt;, returning you instantly to the source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mapping &lt;code&gt;&amp;lt;leader&amp;gt;c&lt;/code&gt; invokes this function, turning compilation into a single, muscle-memorised keystroke.&lt;/p&gt;

&lt;h3&gt;Viewing the PDF&lt;/h3&gt;

&lt;p&gt;Once compiled, &lt;code&gt;ViewTex()&lt;/code&gt; opens the corresponding PDF in Evince without blocking Vim:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! ViewTex() abort
    let pdf = substitute(expand('%:p'), '\.tex$', '.pdf', '')
    silent! execute '!nohup evince ' . shellescape(pdf) . ' &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;'
    redraw!
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Using &lt;code&gt;nohup&lt;/code&gt; ensures the viewer persists even if the terminal closes, and the silent redraw avoids screen flicker. Bound to &lt;code&gt;&amp;lt;leader&amp;gt;o&lt;/code&gt;, it closes the edit‑compile‑view loop seamlessly.&lt;/p&gt;

&lt;h2&gt;Exporting LaTeX as HTML for Static Sites&lt;/h2&gt;

&lt;p&gt;The most unconventional part of this setup is the &lt;code&gt;WrapBufferInHTMLTags()&lt;/code&gt; function. It yanks the entire buffer, escapes HTML entities, and wraps the result in &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/code&gt; blocks with a language class. The new buffer is then given a &lt;code&gt;.html&lt;/code&gt; filename and filetype.&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! WrapBufferInHTMLTags()
    execute 'normal! ggVGy'
    let l:content = getreg('0')
    let l:content = substitute(l:content, '&amp;amp;', '\&amp;amp;amp;', 'g')
    let l:content = substitute(l:content, '&amp;lt;', '\&amp;amp;lt;', 'g')
    let l:content = substitute(l:content, '&amp;gt;', '\&amp;amp;gt;', 'g')
    call setreg('0', l:content)
    let l:current_filename = expand('%:t:r') . ".html"
    execute 'enew'
    setlocal filetype=html
    execute 'file ' . l:current_filename
    call setline(1, '&amp;lt;pre class="language-latex"&amp;gt;')
    call append(1, '&amp;lt;code class="language-latex"&amp;gt;')
    execute 'normal! Go'
    execute 'normal! "0p'
    call append(line('$'), '&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;')
    execute 'normal! gg'
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;This function targets a specific pain point: publishing LaTeX source code in a Pelican-powered static site. Instead of manually escaping characters and adding HTML tags, a single mapping (&lt;code&gt;&amp;lt;leader&amp;gt;w&lt;/code&gt;) produces a correctly formatted code block ready to be inserted into an &lt;code&gt;body.html&lt;/code&gt; file. It is a perfect example of merging local tooling with an automated publishing pipeline.&lt;/p&gt;

&lt;h2&gt;Insert Mode Mappings: From Keystrokes to Content&lt;/h2&gt;

&lt;p&gt;A large portion of the ftplugin is devoted to insert-mode mappings that make typing LaTeX faster and more consistent.&lt;/p&gt;

&lt;h3&gt;Symbol Shortcuts and Structural Commands&lt;/h3&gt;

&lt;p&gt;Several mappings re-purpose keys that are rarely used in LaTeX but easily reachable:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;!&lt;/code&gt; → &lt;code&gt;\&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;§&lt;/code&gt; → &lt;code&gt;!&lt;/code&gt;&lt;/strong&gt;: swaps backslash and exclamation mark, since the backslash is the most frequent LaTeX character and &lt;code&gt;!&lt;/code&gt; is seldom needed.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;qq&lt;/code&gt; → &lt;code&gt;\quad&lt;/code&gt;&lt;/strong&gt;: inserts a quad space, used constantly in math environments.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;tt&lt;/code&gt; → &lt;code&gt;~\text{}~&amp;lt;++&amp;gt;&lt;/code&gt;&lt;/strong&gt;: wraps text inside math mode with a tilde-protected space, placing the cursor ready to type the text and then jump to the placeholder.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;;hs&lt;/code&gt;, &lt;code&gt;;vs&lt;/code&gt;&lt;/strong&gt;: horizontal and vertical space commands with a placeholder for the length.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;;bf&lt;/code&gt;, &lt;code&gt;;u&lt;/code&gt;&lt;/strong&gt;: bold text and underline.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;++&amp;gt;&lt;/code&gt; placeholder and the &lt;code&gt;&amp;lt;C-j&amp;gt;&lt;/code&gt; mapping (both in insert and normal modes) implement a simple jump-to-next-placeholder mechanism. After inserting a template like &lt;code&gt;\textbf{}&lt;/code&gt;, the cursor lands inside the braces; pressing &lt;code&gt;&amp;lt;C-j&amp;gt;&lt;/code&gt; deletes the placeholder and moves to the next edit point, keeping hands on the home row.&lt;/p&gt;

&lt;h3&gt;Snippet Insertion System&lt;/h3&gt;

&lt;p&gt;The brunt of the automation comes from a collection of snippet files, each stored as a standalone &lt;code&gt;.tex&lt;/code&gt; file inside &lt;code&gt;~/.vim/snippets/latex/&lt;/code&gt;. Insert mode mappings read these files directly into the buffer:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
inoremap ;ds &amp;lt;ESC&amp;gt;:0r /home/mosaid/.vim/snippets/latex/ds&amp;lt;CR&amp;gt;
inoremap ;st &amp;lt;ESC&amp;gt;:0r /home/mosaid/.vim/snippets/latex/st&amp;lt;CR&amp;gt;
inoremap ;ar &amp;lt;ESC&amp;gt;:r /home/mosaid/.vim/snippets/latex/ar&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;The files are plain TeX, allowing direct editing without any special format. Below is a summary of the available snippets:&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Keystroke&lt;/th&gt;&lt;th&gt;File&lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;;dd&lt;/td&gt;&lt;td&gt;d1&lt;/td&gt;&lt;td&gt;Minimal article document skeleton (12pt, a4paper)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;ds&lt;/td&gt;&lt;td&gt;ds&lt;/td&gt;&lt;td&gt;Full exam class with custom borders, TikZ stamps, and bilingual (French/Arabic) support&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;st&lt;/td&gt;&lt;td&gt;st&lt;/td&gt;&lt;td&gt;Standalone TikZ picture class for generating PNG graphics&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;ar&lt;/td&gt;&lt;td&gt;ar&lt;/td&gt;&lt;td&gt;Arabic language preamble (polyglossia, Amiri font)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;gg&lt;/td&gt;&lt;td&gt;gg&lt;/td&gt;&lt;td&gt;Tight geometry: 1 cm margins on all sides&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;tcb&lt;/td&gt;&lt;td&gt;tcb&lt;/td&gt;&lt;td&gt;tcolorbox exercise environment with red frame, gray background, and counter&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;mt&lt;/td&gt;&lt;td&gt;mtab&lt;/td&gt;&lt;td&gt;Math-mode tabular environment for alignments&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;li&lt;/td&gt;&lt;td&gt;li&lt;/td&gt;&lt;td&gt;Custom tight enumerate list (compact spacing)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;;ti&lt;/td&gt;&lt;td&gt;tight&lt;/td&gt;&lt;td&gt;Enumitem key that makes any list tight&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;enum&lt;/td&gt;&lt;td&gt;Standard enumerate wrapper (no default mapping; users can add their own)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Each snippet is a complete, self-contained piece of LaTeX that can be inserted either at the top of the file (&lt;code&gt;:0r&lt;/code&gt;) or at the cursor (&lt;code&gt;:r&lt;/code&gt;). The exam template (&lt;code&gt;ds&lt;/code&gt;) alone is over 6 kB and includes a custom &lt;code&gt;\stamp&lt;/code&gt;, &lt;code&gt;\luck&lt;/code&gt;, and &lt;code&gt;\borders&lt;/code&gt; system—showcasing how deeply tailored these templates can become.&lt;/p&gt;

&lt;p&gt;Additional inline mappings complement the snippets:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;;mm&lt;/code&gt;&lt;/strong&gt;: inserts &lt;code&gt;\usepackage{amsmath, amsfonts, amssymb, amsthm}&lt;/code&gt; in one go.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;;ig&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;\includegraphics[width=&amp;lt;++&amp;gt;cm]{&amp;lt;++&amp;gt;}&lt;/code&gt; with placeholders.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;;bm&lt;/code&gt;&lt;/strong&gt;: defines a &lt;code&gt;\bottommsg&lt;/code&gt; macro used in certain document styles.&lt;/p&gt;

&lt;p&gt;All of these shortcuts follow a consistent pattern: they reduce multi-step actions to a short, memorable sequence, often using the semicolon as a namespace leader to avoid conflicts with normal typing.&lt;/p&gt;

&lt;h2&gt;Visual Mode Wrapping: Metaprogramming Text&lt;/h2&gt;

&lt;p&gt;Visual mode mappings allow applying formatting to existing text without manual cursor navigation:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;&amp;lt;leader&amp;gt;$&lt;/code&gt;&lt;/strong&gt;: surrounds selected text with &lt;code&gt;~$...$~&lt;/code&gt;, converting a region to inline math.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;&amp;lt;leader&amp;gt;u&lt;/code&gt;, &lt;code&gt;&amp;lt;leader&amp;gt;b&lt;/code&gt;, &lt;code&gt;&amp;lt;leader&amp;gt;i&lt;/code&gt;&lt;/strong&gt;: underline, boldface, italic.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;&amp;lt;leader&amp;gt;cc&lt;/code&gt;&lt;/strong&gt;: wraps text in &lt;code&gt;\textcolor{}{...}&lt;/code&gt;, prompting for the colour.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;, &lt;code&gt;4&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt;&lt;/strong&gt;: a series of single-key visual mappings for &lt;code&gt;\bm{\textcolor{}{...}}&lt;/code&gt;, &lt;code&gt;\bbox&lt;/code&gt;, &lt;code&gt;\rbox&lt;/code&gt;, &lt;code&gt;\obox&lt;/code&gt;, and inline math respectively.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;6&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt;&lt;/strong&gt;: convert &lt;code&gt;\[...\]&lt;/code&gt; and &lt;code&gt;\(...\)&lt;/code&gt; delimiters to the Vim author’s preferred &lt;code&gt;~$...$~&lt;/code&gt; syntax, using a clever search-and-replace on the visual selection.&lt;/p&gt;

&lt;p&gt;These mappings treat the document like a programmable structure, allowing restructuring and enhancement without ever leaving the editor or reaching for the mouse.&lt;/p&gt;

&lt;h2&gt;Tradeoffs and Limitations&lt;/h2&gt;

&lt;p&gt;Every engineering decision involves tradeoffs. This setup is no exception:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Hard-coded paths:&lt;/strong&gt; Snippet file paths are absolute (&lt;code&gt;/home/mosaid/.vim/snippets/latex/&lt;/code&gt;). This works well on a single machine but breaks portability. A more robust solution would use environment variables or Vim’s &lt;code&gt;~/.vim&lt;/code&gt; relative paths (e.g., &lt;code&gt;:r ~/.vim/snippets/latex/ds&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Synchronous compilation:&lt;/strong&gt; &lt;code&gt;RunTex()&lt;/code&gt; runs &lt;code&gt;xelatex&lt;/code&gt; synchronously, freezing Vim during compilation—fine for small documents, noticeable on 50+ page manuscripts. Integrating an async job via &lt;code&gt;job_start()&lt;/code&gt; or &lt;code&gt;:terminal&lt;/code&gt; would modernise it.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Tight coupling to Evince:&lt;/strong&gt; The viewer function assumes Evince is installed. While easily swapped, it’s not platform-agnostic.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Snippet maintenance:&lt;/strong&gt; Keeping the snippet library consistent and up-to-date is a manual process. A snippet engine like UltiSnips could provide more flexibility (placeholders, transforms), but the current approach is deliberately simple and transparent—just files on disk.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Key conflict potential:&lt;/strong&gt; Mappings like &lt;code&gt;1&lt;/code&gt; in visual mode shadow Vim’s default behaviour. This is intentional; the author has internalised these overrides, but they may confuse new adopters.&lt;/p&gt;

&lt;h2&gt;Workflow Integration&lt;/h2&gt;

&lt;p&gt;This ftplugin does not exist in isolation. It is part of a larger pipeline that often includes:&lt;/p&gt;

&lt;ul class="compact-list"&gt;
  &lt;li&gt;A &lt;strong&gt;Makefile&lt;/strong&gt; or &lt;code&gt;latexmk&lt;/code&gt; continuous build script for multi-file projects.&lt;/li&gt;
  &lt;li&gt;A &lt;strong&gt;Pelican static site generator&lt;/strong&gt;: the &lt;code&gt;WrapBufferInHTMLTags()&lt;/code&gt; function directly feeds the blogging engine with syntax-highlighted LaTeX code blocks.&lt;/li&gt;
  &lt;li&gt;A &lt;strong&gt;Git repository&lt;/strong&gt; that versions both the ftplugin and the snippet files, ensuring reproducibility across machines.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Terminal multiplexers&lt;/strong&gt; (tmux) or modern terminal emulators that allow a side-by-side layout: Vim on the left, Evince on the right, and a compilation watcher in a third pane.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system shines when you treat your LaTeX authoring as a software project: deterministic, automated, and continuously compiled.&lt;/p&gt;

&lt;h2&gt;Future Improvements&lt;/h2&gt;

&lt;p&gt;The current setup has evolved organically. For a more polished, distributable version, several enhancements are worth considering:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Async compilation:&lt;/strong&gt; Replace &lt;code&gt;RunTex()&lt;/code&gt; with Vim’s &lt;code&gt;job_start()&lt;/code&gt; and populate a quickfix list from the log, enabling &lt;code&gt;:copen&lt;/code&gt; navigation.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Portable snippet paths:&lt;/strong&gt; Use &lt;code&gt;expand('~/.vim/snippets/latex')&lt;/code&gt; instead of hard-coded &lt;code&gt;/home/mosaid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;UltiSnips or LuaSnip bridging:&lt;/strong&gt; Keep the file‑based repository but generate snippet definitions dynamically, gaining visual placeholders and tab‑stops without abandoning the file system.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;LSP integration:&lt;/strong&gt; Combine this custom tooling with a LaTeX language server (e.g., TexLab) for diagnostics, references, and hover information.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Theme-aware PDF rendering:&lt;/strong&gt; Adapt the &lt;code&gt;ViewTex()&lt;/code&gt; function to work with other viewers (Zathura, Okular) or even inline PDF previews in modern Vim terminals.&lt;/p&gt;

&lt;h2&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;This Vim configuration is a testament to the power of incremental customisation. What began as a few insert-mode shortcuts grew into a full-blown LaTeX editing environment that eliminates boilerplate, reduces cognitive load, and bridges the gap between document authoring and web publishing. It does not attempt to replace dedicated LaTeX IDEs; instead, it layers deterministic automation on top of an editor that the user already trusts.&lt;/p&gt;

&lt;p&gt;The approach is reproducible, composable, and deeply personal—exactly what makes Vim a lasting tool for engineers. If you also live in Vim and write LaTeX, consider adopting (and adapting) these patterns to match your own workflow. The entire configuration is just a single ftplugin file and a folder of snippets—easy to version, easy to tweak, and easy to forget about once muscle memory takes over.&lt;/p&gt;</content><category term="vim"></category><category term="vim"></category><category term="latex"></category><category term="tex"></category><category term="automation"></category><category term="snippets"></category><category term="workflow"></category><category term="ftplugin"></category><category term="xelatex"></category><category term="pdf"></category><category term="evince"></category></entry><entry><title>From Plain Text to HTML in Vim – Using Python Filters for Instant Markup</title><link href="https://mosaid.xyz/articles/from-plain-text-to-html-in-vim-using-python-filters-for-instant-markup-5.html" rel="alternate"></link><published>2026-05-13T20:50:06+00:00</published><updated>2026-05-13T20:50:06+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-13:/articles/from-plain-text-to-html-in-vim-using-python-filters-for-instant-markup-5.html</id><summary type="html">&lt;p&gt;Learn how to extend the RunScriptFilter pattern to turn plain text into HTML instantly inside Vim. Convert lists into styled &amp;lt;li&amp;gt; tags, wrap paragraphs in &amp;lt;p&amp;gt;, generate &amp;lt;select&amp;gt; menus, and build HTML tables – all from visual selections. A practical, script‑first approach that eliminates repetitive markup typing for developers and technical writers.&lt;/p&gt;</summary><content type="html">&lt;!-- From Plain Text to HTML in Vim – Using Python Filters for Instant Markup --&gt;
&lt;p&gt;
  If you write a lot of HTML by hand – documentation, blog posts, static site templates – you know how tedious it is to wrap lines in &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags, convert bullet lists into &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; blocks with proper classes, or build &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; menus from scratch.
  With the &lt;strong&gt;RunScriptFilter&lt;/strong&gt; workflow introduced in the &lt;a href="/articles/vim-text-filters-replacing-macros-with-python-&amp;-shell-scripts-for-latex-code-and-data-4.html" target="_blank"&gt;previous article&lt;/a&gt;, we can turn any visual selection into the output of a Python script.
  This article focuses entirely on &lt;strong&gt;Vim text to HTML&lt;/strong&gt; transformations: instant, composable, and custom‑tailored to your markup needs.
&lt;/p&gt;

&lt;h2&gt;The Core Filter (Recap)&lt;/h2&gt;
&lt;p&gt;
  The foundation is a Vim function that pipes selected text to an external command and replaces the selection with the command’s output.
  Here’s the production‑ready version we’ll use in every example:
&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! RunScriptFilter(cmd) range
    let l:save_reg = getreg('"')
    let l:save_regtype = getregtype('"')
    normal! ""gvy
    let l:text = getreg('"')
    call setreg('"', l:save_reg, l:save_regtype)

    let l:tmp = tempname()
    call writefile(split(l:text, "\n", 1), l:tmp)
    let l:out = system(a:cmd . ' &amp;lt; ' . shellescape(l:tmp))
    call delete(l:tmp)

    if v:shell_error
        echohl ErrorMsg
        echo "Filter error:\n" . l:out
        echohl None
        return
    endif
    let l:out = substitute(l:out, '\n\+$', '', '')
    execute "normal! gv\"_c" . l:out
endfunction
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;
  Any external tool that reads &lt;code&gt;stdin&lt;/code&gt; now becomes a &lt;strong&gt;Vim HTML filter&lt;/strong&gt;.
  Let’s put it to work.
&lt;/p&gt;

&lt;h2&gt;1. Instant Paragraph Wrapping&lt;/h2&gt;
&lt;p&gt;
  Select a block of text and turn each non‑empty line into a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; element.
  The Python script is trivial:
&lt;/p&gt;

&lt;pre class="language-python"&gt;
&lt;code class="language-python"&gt;
import sys

for line in sys.stdin:
    stripped = line.strip()
    if stripped:
        print(f"&amp;lt;p&amp;gt;{stripped}&amp;lt;/p&amp;gt;")
    else:
        print()   # preserve blank lines
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;
  Map it:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
xnoremap &amp;lt;leader&amp;gt;hp :call RunScriptFilter('python3 -c "
import sys
for l in sys.stdin:
    s = l.strip()
    print(f\"&amp;lt;p&amp;gt;{s}&amp;lt;/p&amp;gt;\" if s else \"\")
"')&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;
  Select several paragraphs, hit &lt;code&gt;&amp;lt;leader&amp;gt;hp&lt;/code&gt;, and they’re wrapped instantly.
  No more manually typing &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/p&amp;gt;&lt;/code&gt;.
&lt;/p&gt;

&lt;h2&gt;2. Headings from Plain Lines&lt;/h2&gt;
&lt;p&gt;
  A slightly smarter filter can detect heading levels.
  Here, lines starting with &lt;code&gt;#&lt;/code&gt; are converted to &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; through &lt;code&gt;&amp;lt;h4&amp;gt;&lt;/code&gt; based on the number of &lt;code&gt;#&lt;/code&gt;:
&lt;/p&gt;

&lt;pre class="language-python"&gt;
&lt;code class="language-python"&gt;
import sys, re

for line in sys.stdin:
    line = line.rstrip('\n')
    m = re.match(r'^(#{1,4})\s+(.*)', line)
    if m:
        level = len(m.group(1))
        print(f"&amp;lt;h{level}&amp;gt;{m.group(2)}&amp;lt;/h{level}&amp;gt;")
    else:
        print(f"&amp;lt;p&amp;gt;{line}&amp;lt;/p&amp;gt;")
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;
  Map: &lt;code&gt;xnoremap &amp;lt;leader&amp;gt;hh :call RunScriptFilter('python3 heading_filter.py')&amp;lt;CR&amp;gt;&lt;/code&gt;.
  Now you can write a quick outline, select it, and get semantic HTML headings and paragraphs.
&lt;/p&gt;

&lt;h2&gt;3. Markdown‑Style Lists to Styled HTML Lists&lt;/h2&gt;
&lt;p&gt;
  Here’s where external scripts really shine.
  Suppose you write a list like:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
1 *foo* bar
2 *foo* baz
3 *foo* qux
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  You want this to become:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
&amp;lt;ol&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong style="color:#b5bd68;"&amp;gt;foo&amp;lt;/strong&amp;gt; bar&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong style="color:#b5bd68;"&amp;gt;foo&amp;lt;/strong&amp;gt; baz&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong style="color:#b5bd68;"&amp;gt;foo&amp;lt;/strong&amp;gt; qux&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  A small Python script handles it:
&lt;/p&gt;
&lt;pre class="language-python"&gt;
&lt;code class="language-python"&gt;
import sys, re

lines = [l.rstrip('\n') for l in sys.stdin if l.strip()]
if not lines:
    sys.exit(0)

print("&amp;lt;ol&amp;gt;")
for line in lines:
    # Extract index, bold part, rest
    m = re.match(r'^\d+\s+\*(.+?)\*\s*(.*)', line)
    if m:
        bold = m.group(1)
        rest = m.group(2)
        print(f'  &amp;lt;li&amp;gt;&amp;lt;strong style="color:#b5bd68;"&amp;gt;{bold}&amp;lt;/strong&amp;gt; {rest}&amp;lt;/li&amp;gt;')
    else:
        print(f'  &amp;lt;li&amp;gt;{line}&amp;lt;/li&amp;gt;')
print("&amp;lt;/ol&amp;gt;")
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;
  Map: &lt;code&gt;xnoremap &amp;lt;leader&amp;gt;hl :call RunScriptFilter('python3 mdlist2html.py')&amp;lt;CR&amp;gt;&lt;/code&gt;.
  The same pattern works for unordered lists: just switch to &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; and adjust the regex.
&lt;/p&gt;

&lt;h2&gt;4. Generate &amp;lt;select&amp;gt; Menus from a List&lt;/h2&gt;
&lt;p&gt;
  Turn this:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
Volvo
Saab
Mercedes
Audi
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  into:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
&amp;lt;select name="cars"&amp;gt;
  &amp;lt;option value="0"&amp;gt;Volvo&amp;lt;/option&amp;gt;
  &amp;lt;option value="1"&amp;gt;Saab&amp;lt;/option&amp;gt;
  &amp;lt;option value="2"&amp;gt;Mercedes&amp;lt;/option&amp;gt;
  &amp;lt;option value="3"&amp;gt;Audi&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Python script:
&lt;/p&gt;
&lt;pre class="language-python"&gt;
&lt;code class="language-python"&gt;
import sys

lines = [l.strip() for l in sys.stdin if l.strip()]
if not lines:
    sys.exit(0)

print('&amp;lt;select name="items"&amp;gt;')
for i, item in enumerate(lines):
    print(f'  &amp;lt;option value="{i}"&amp;gt;{item}&amp;lt;/option&amp;gt;')
print('&amp;lt;/select&amp;gt;')
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Map: &lt;code&gt;xnoremap &amp;lt;leader&amp;gt;hs :call RunScriptFilter('python3 select_filter.py')&amp;lt;CR&amp;gt;&lt;/code&gt;.
  Now any list becomes a dropdown in one keystroke.
&lt;/p&gt;

&lt;h2&gt;5. CSV to HTML Table&lt;/h2&gt;
&lt;p&gt;
  When you have comma‑separated data, generate a full HTML table with proper escaping:
&lt;/p&gt;
&lt;pre class="language-python"&gt;
&lt;code class="language-python"&gt;
import sys, csv
from html import escape

reader = csv.reader(sys.stdin)
rows = list(reader)
if not rows:
    sys.exit(0)

print('&amp;lt;table&amp;gt;')
for i, row in enumerate(rows):
    tag = 'th' if i == 0 else 'td'
    cells = ''.join(f'&amp;lt;{tag}&amp;gt;{escape(cell)}&amp;lt;/{tag}&amp;gt;' for cell in row)
    print(f'  &amp;lt;tr&amp;gt;{cells}&amp;lt;/tr&amp;gt;')
print('&amp;lt;/table&amp;gt;')
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Map: &lt;code&gt;xnoremap &amp;lt;leader&amp;gt;ht :call RunScriptFilter('python3 csv2table.py')&amp;lt;CR&amp;gt;&lt;/code&gt;.
  This alone saves minutes of repetitive tag writing when you embed tables in blog posts.
&lt;/p&gt;

&lt;h2&gt;6. Custom Inline Styles Based on Content&lt;/h2&gt;
&lt;p&gt;
  The sky is the limit.
  For instance, you can write a filter that detects numbers and wraps them in &lt;code&gt;&amp;lt;span style="color:red;"&amp;gt;&lt;/code&gt;, or that automatically creates hyperlinks from URLs.
  Here’s a simple one that bolds any word that starts with a capital letter:
&lt;/p&gt;
&lt;pre class="language-python"&gt;
&lt;code class="language-python"&gt;
import sys, re

def bold_caps(text):
    return re.sub(r'\b([A-Z][a-z]+)\b', r'&amp;lt;strong&amp;gt;\1&amp;lt;/strong&amp;gt;', text)

for line in sys.stdin:
    print(bold_caps(line.rstrip('\n')))
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Map it and select any block – proper names are automatically emphasised.
&lt;/p&gt;

&lt;h2&gt;7. Full‑Document Conversion with Pandoc&lt;/h2&gt;
&lt;p&gt;
  Sometimes you want to convert an entire Markdown buffer to HTML without leaving Vim.
  Using the buffer‑wide variant of our filter:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
command! -nargs=1 FilterBuffer call RunBufferFilter(&amp;lt;q-args&amp;gt;)
nnoremap &amp;lt;leader&amp;gt;mdh :FilterBuffer pandoc -f markdown -t html&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  This turns a Markdown document into a full HTML page, perfect for previewing or pasting into a CMS.
&lt;/p&gt;

&lt;h2&gt;Workflow and Safety&lt;/h2&gt;
&lt;ul class="compact-list"&gt;
  &lt;li&gt;&lt;strong&gt;Undo‑friendly:&lt;/strong&gt; The replacement is a single change – &lt;code&gt;u&lt;/code&gt; brings back the original text instantly.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Error‑safe:&lt;/strong&gt; If the script fails (non‑zero exit), the selection remains untouched and the error is displayed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No escaping headaches:&lt;/strong&gt; Using temporary files avoids shell‑injection and quoting nightmares.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Composable:&lt;/strong&gt; You can pipe the output of one filter directly into another by running them sequentially – or chain them in a single shell pipeline inside the filter command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Building Your HTML Editing Toolkit&lt;/h2&gt;
&lt;p&gt;
  I keep a handful of Python scripts in &lt;code&gt;~/bin/html-filters/&lt;/code&gt; and load the corresponding Vim maps in a file that’s sourced automatically.
  The consistent interface – select, press a leader key, done – means I never have to think about the underlying mechanics.
  Whether I’m preparing a blog post, editing a static page template, or documenting an API, these &lt;strong&gt;Vim HTML filters&lt;/strong&gt; have eliminated hundreds of repetitive keystrokes per week.
&lt;/p&gt;

&lt;p&gt;
  The real power of this approach isn’t any single script – it’s the mindset that &lt;strong&gt;your editor can become a programmable HTML generator&lt;/strong&gt; whenever you need it.
  Start with one filter that solves a real annoyance in your current project, and the rest will follow naturally.
&lt;/p&gt;</content><category term="vim"></category><category term="vim"></category><category term="html"></category><category term="python"></category><category term="text filters"></category><category term="vim text to html"></category><category term="markup generation"></category><category term="automation"></category><category term="vim scripting"></category><category term="code generation"></category><category term="developer workflow"></category></entry><entry><title>Vim Text Filters: Replacing Macros with Python &amp; Shell Scripts for LaTeX, Code, and Data</title><link href="https://mosaid.xyz/articles/vim-text-filters-replacing-macros-with-python-&amp;-shell-scripts-for-latex-code-and-data-4.html" rel="alternate"></link><published>2026-05-13T20:50:06+00:00</published><updated>2026-05-13T20:50:06+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2026-05-13:/articles/vim-text-filters-replacing-macros-with-python-&amp;-shell-scripts-for-latex-code-and-data-4.html</id><summary type="html">&lt;p&gt;Learn how to use Python, shell, and external scripts as live text filters in Vim, replacing fragile macros. Transform LaTeX equations, format JSON, process CSV, and automate repetitive code edits – all from a visual selection or the entire buffer. A practical, engineer-tested Vim workflow for power users and technical writers.&lt;/p&gt;</summary><content type="html">&lt;!-- Vim Text Filters: Replacing Macros with Python &amp; Shell Scripts for LaTeX, Code, and Data --&gt;

&lt;p&gt;
  Vim’s macro system is a classic power tool, but when you need real computation—parsing JSON, evaluating maths, formatting data—macros quickly hit their limits.
  Instead of recording a sequence of keystrokes, I use &lt;strong&gt;external scripts&lt;/strong&gt; as &lt;strong&gt;Vim text filters&lt;/strong&gt;.
  This &lt;strong&gt;Vim Python workflow&lt;/strong&gt; replaces a visual selection (or an entire buffer) with the output of any command-line tool, turning your editor into a programmable text transformation pipeline.
&lt;/p&gt;

&lt;h2&gt;Why Use Python Scripts Instead of Vim Macros?&lt;/h2&gt;
&lt;p&gt;
  Vim macros are perfect for one‑off, linear edits, but they become fragile when you need:
&lt;/p&gt;
&lt;ul class="compact-list"&gt;
  &lt;li&gt;&lt;strong&gt;Arithmetic or symbolic math&lt;/strong&gt; (e.g., evaluating LaTeX expressions)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Structured data processing&lt;/strong&gt; (JSON, CSV, HTML tables)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;External API calls&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Linting or formatting with dedicated tools&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  By piping text to a script and replacing it with the script’s output, you gain the full expressiveness of Python, &lt;code&gt;jq&lt;/code&gt;, &lt;code&gt;pandoc&lt;/code&gt;, or any Unix filter – all while staying in normal mode.
  This &lt;strong&gt;Vim text filter approach&lt;/strong&gt; is reusable, composable, and far easier to debug than a complex macro.
&lt;/p&gt;

&lt;h2&gt;The Core Vim Text Filter Function&lt;/h2&gt;
&lt;p&gt;
  The foundation is a Vim function that yanks the current visual selection, sends it to a command via &lt;code&gt;stdin&lt;/code&gt;, and overwrites the selection with the result.
  Here is a refined, production‑ready version I use every day, designed to handle multi‑line selections safely and to report errors without losing your original text.
&lt;/p&gt;

&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! RunScriptFilter(cmd) range
    " Save the current register and selection type
    let l:save_reg = getreg('"')
    let l:save_regtype = getregtype('"')

    " Yank the visual selection
    normal! ""gvy
    let l:text = getreg('"')

    " Restore the register immediately
    call setreg('"', l:save_reg, l:save_regtype)

    " Write the text to a temporary file to avoid shell escaping problems
    let l:tmp = tempname()
    call writefile(split(l:text, "\n", 1), l:tmp)
    let l:out = system(a:cmd . ' &lt; ' . shellescape(l:tmp))
    call delete(l:tmp)

    " Show errors in a popup if the command failed
    if v:shell_error
        echohl ErrorMsg
        echo "Filter error:\n" . l:out
        echohl None
        return
    endif

    " Remove a trailing newline so we don't introduce blank lines
    let l:out = substitute(l:out, '\n\+$', '', '')

    " Replace the selection with the filtered output
    execute "normal! gv\"_c" . l:out
endfunction

" Example: format JSON with jq
xnoremap &amp;lt;silent&amp;gt; &amp;lt;leader&amp;gt;jq :&amp;lt;C-u&amp;gt;call RunScriptFilter('jq .')&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  With this generic filter, any command that reads from &lt;code&gt;stdin&lt;/code&gt; becomes a &lt;strong&gt;Vim text filter&lt;/strong&gt;.
  No more escaping nightmares, and errors are shown without destroying your buffer.
&lt;/p&gt;

&lt;h2&gt;Practical Vim Filter Examples for LaTeX, Code, and Data&lt;/h2&gt;

&lt;h3&gt;1. Live Math Evaluation in LaTeX Documents&lt;/h3&gt;
&lt;p&gt;
  When writing technical articles, I often need to compute a value for a table or verify a formula.
  By selecting an expression and running a &lt;strong&gt;Vim Python filter&lt;/strong&gt;, I get the result instantly.
  A small Python script handles the math:
&lt;/p&gt;
&lt;pre class="language-shell"&gt;
&lt;code class="language-shell"&gt;
# Save as ~/bin/vim-math-filter
python3 -c "from math import *; import sys; print(eval(sys.stdin.read()))"
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Map it in Vim:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
xnoremap &amp;lt;leader&amp;gt;m :call RunScriptFilter('vim-math-filter')&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Now &lt;code&gt;sqrt(2)*3.14&lt;/code&gt; becomes &lt;code&gt;4.4403135525488&lt;/code&gt; with a single keystroke.
&lt;/p&gt;

&lt;h3&gt;2. Formatting CSVs into Markdown Tables&lt;/h3&gt;
&lt;p&gt;
  Data in CSV format can be transformed into a neatly aligned Markdown table using a quick Python script.
  This is a classic &lt;strong&gt;automate repetitive edits in Vim&lt;/strong&gt; use case.
&lt;/p&gt;
&lt;pre class="language-shell"&gt;
&lt;code class="language-shell"&gt;
# ~/bin/csv2md-table
python3 &lt;&lt; 'PYEOF'
import csv, sys
rows = list(csv.reader(sys.stdin))
if not rows: sys.exit(0)
widths = [max(len(str(row[i])) for row in rows) for i in range(len(rows[0]))]
for i, row in enumerate(rows):
    print('| ' + ' | '.join(f"{str(row[j]):{widths[j]}}" for j in range(len(row))) + ' |')
    if i == 0:
        print('|' + '|'.join(f"{'-'*(w+2)}" for w in widths) + '|')
PYEOF
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Map it: &lt;code&gt;xnoremap &amp;lt;leader&amp;gt;tb :call RunScriptFilter('csv2md-table')&amp;lt;CR&amp;gt;&lt;/code&gt;.
  Select any CSV block, hit the map, and you get a perfectly formatted table – no manual alignment required.
&lt;/p&gt;

&lt;h3&gt;3. JSON Prettification and Data Extraction&lt;/h3&gt;
&lt;p&gt;
  For quick &lt;strong&gt;JSON processing inside Vim&lt;/strong&gt;, &lt;code&gt;jq&lt;/code&gt; is unbeatable.
  These mappings turn selections into pretty‑printed JSON, or extract a single field:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
xnoremap &amp;lt;leader&amp;gt;jp :call RunScriptFilter('jq .')&amp;lt;CR&amp;gt;
xnoremap &amp;lt;leader&amp;gt;jn :call RunScriptFilter('jq -r .name')&amp;lt;CR&amp;gt;
xnoremap &amp;lt;leader&amp;gt;jc :call RunScriptFilter('jq -c .')&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  It’s an order of magnitude faster than trying to reformat JSON with a macro.
&lt;/p&gt;

&lt;h3&gt;4. Linting and Auto‑formatting Code Blocks&lt;/h3&gt;
&lt;p&gt;
  Use your favourite linter as a &lt;strong&gt;Vim text filter&lt;/strong&gt;.
  For example, run &lt;code&gt;black&lt;/code&gt; on a visually selected Python snippet inside a Markdown fenced block:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
xnoremap &amp;lt;leader&amp;gt;bl :call RunScriptFilter('black -q -')&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  No need to leave Vim or copy‑paste – the formatted code replaces your selection instantly.
&lt;/p&gt;

&lt;h3&gt;5. Whole‑Buffer Transformations with Pandoc&lt;/h3&gt;
&lt;p&gt;
  The same concept extends to the entire file.
  A slight modification of the function lets you pipe the whole buffer through any tool:
&lt;/p&gt;
&lt;pre class="language-vim"&gt;
&lt;code class="language-vim"&gt;
function! RunBufferFilter(cmd)
    let l:tmp = tempname()
    execute 'write !cat &gt; ' . l:tmp
    let l:out = system(a:cmd . ' &lt; ' . shellescape(l:tmp))
    call delete(l:tmp)
    if v:shell_error
        echohl ErrorMsg | echo l:out | echohl None
        return
    endif
    %delete
    put =l:out
    1delete _
endfunction

command! -nargs=1 FilterBuffer call RunBufferFilter(&amp;lt;q-args&amp;gt;)
&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;
  Now you can use &lt;code&gt;:FilterBuffer pandoc -f markdown -t plain&lt;/code&gt; to strip all Markdown formatting, or &lt;code&gt;:FilterBuffer figlet&lt;/code&gt; for instant ASCII art.
&lt;/p&gt;

&lt;h2&gt;Integrating the Filter into Your Vim and LaTeX Workflow&lt;/h2&gt;
&lt;p&gt;
  For those working heavily with &lt;strong&gt;Vim and LaTeX&lt;/strong&gt;, these filters become a natural extension of your editing toolbelt.
  Examples from my own workflow include:
&lt;/p&gt;
&lt;ul class="compact-list"&gt;
  &lt;li&gt;&lt;strong&gt;Aligning LaTeX tables&lt;/strong&gt; – pipe a selection through a Python script that aligns columns on &lt;code&gt;&amp;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Generating TikZ diagrams&lt;/strong&gt; – select a numeric specification and use a filter to output the full TikZ code.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cleaning up BibTeX entries&lt;/strong&gt; – run &lt;code&gt;bibtool --pretty&lt;/code&gt; on a selection to normalise formatting.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Spell‑checking a paragraph&lt;/strong&gt; – use &lt;code&gt;textidote --check en&lt;/code&gt; as a filter and see suggestions inline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  The pattern is always the same: select, map, transform. This &lt;strong&gt;Vim automation&lt;/strong&gt; mindset turns your editor into a composable toolchain.
&lt;/p&gt;

&lt;h2&gt;Performance, Safety, and Edge Cases&lt;/h2&gt;
&lt;p&gt;
  &amp;#8226;&lt;strong&gt;Temporary files:&lt;/strong&gt; Always preferred over &lt;code&gt;shellescape()&lt;/code&gt; for multi‑line text because they avoid escaping bugs.&lt;br&gt;
  &amp;#8226;&lt;strong&gt;Error handling:&lt;/strong&gt; Never replace the selection if the command fails – the original text remains intact.&lt;br&gt;
  &amp;#8226;&lt;strong&gt;Undo friendliness:&lt;/strong&gt; The replacement is a single change, so a quick &lt;code&gt;u&lt;/code&gt; restores everything.&lt;br&gt;
  &amp;#8226;&lt;strong&gt;Blocking concerns:&lt;/strong&gt; The examples are synchronous; for heavy scripts (&gt;1s), use Neovim’s job‑control or Vim’s &lt;code&gt;job_start()&lt;/code&gt; to keep the UI responsive.&lt;br&gt;
  &amp;#8226;&lt;strong&gt;Security:&lt;/strong&gt; Avoid running arbitrary shell input – always use fixed commands with controlled arguments. Never interpolate user text directly into a command string.
&lt;/p&gt;

&lt;h2&gt;Customise the Vim Text Filter for Your Own Workflow&lt;/h2&gt;
&lt;p&gt;
  The real power comes when you build a small library of filters that match your daily tasks.
  I keep mine in &lt;code&gt;~/bin&lt;/code&gt; and in a dedicated Vim plugin file, with leader mappings grouped by topic:
  &lt;code&gt;&amp;lt;leader&amp;gt;m&lt;/code&gt; for math, &lt;code&gt;&amp;lt;leader&amp;gt;t&lt;/code&gt; for tables, &lt;code&gt;&amp;lt;leader&amp;gt;j&lt;/code&gt; for JSON.
  Because the filter command is external, you can write it in Python, Ruby, Node.js, or even a compiled binary – Vim only cares about &lt;code&gt;stdin&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
  This &lt;strong&gt;Vim + Python text transformation&lt;/strong&gt; approach has replaced dozens of ad‑hoc macros in my setup and has become the backbone of how I edit code, documents, and data.
  Once you experience a single keystroke that computes a formula or formats a table, you’ll wonder why you ever recorded a macro for those tasks.
&lt;/p&gt;</content><category term="vim"></category><category term="vim"></category><category term="python"></category><category term="shell"></category><category term="vim text filters"></category><category term="vim macros"></category><category term="latex"></category><category term="code transformation"></category><category term="automation"></category><category term="vim scripting"></category><category term="developer workflow"></category><category term="vim python"></category><category term="text processing"></category></entry><entry><title>Boosting LaTeX Editing with Custom Vim Mappings</title><link href="https://mosaid.xyz/articles/boosting-latex-editing-with-custom-vim-mappings-9.html" rel="alternate"></link><published>2025-11-23T23:46:56+00:00</published><updated>2025-11-23T23:46:56+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2025-11-23:/articles/boosting-latex-editing-with-custom-vim-mappings-9.html</id><summary type="html">&lt;p&gt;Learn how custom Vim mappings can streamline LaTeX editing for productivity and efficiency.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As someone who writes mathematical documents regularly, I've always found LaTeX editing to be a constant dance between content creation and formatting struggles. The back-and-forth typing of verbose commands like &lt;code&gt;\textbf{}&lt;/code&gt; and &lt;code&gt;\underline{}&lt;/code&gt; constantly interrupted my creative flow. That's when I decided to supercharge my Vim setup with custom mappings specifically designed for LaTeX - and the results have been transformative for my productivity.&lt;/p&gt;

&lt;h2&gt;Basic Settings&lt;/h2&gt;
&lt;p&gt;Before diving into the fancy mappings, let's start with the foundation. I prefer 2-space indentation for LaTeX files as it strikes the perfect balance between readability and efficient use of screen space:&lt;/p&gt;
&lt;p&gt;&lt;pre&gt;&lt;code&gt;autocmd FileType tex setlocal tabstop=2 shiftwidth=2 expandtab&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;h2&gt;Compiling and Viewing LaTeX&lt;/h2&gt;
&lt;p&gt;One of the most common tasks in LaTeX editing is compiling and previewing. Instead of constantly switching to the terminal, I've created seamless workflows right within Vim.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Instant Compilation:&lt;/strong&gt; Pressing &lt;code&gt;&amp;lt;leader&amp;gt;c&lt;/code&gt; compiles the current file and shows me the output right in a Vim buffer - no context switching needed:&lt;/p&gt;
&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
function! RunTex()
    let s:current_file = expand(&amp;quot;%&amp;quot;)
    enew | silent execute &amp;quot;.!xelatex &amp;quot; . shellescape(s:current_file, 1)
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
    if &amp;amp;number == 0
        set number
    endif
    set relativenumber
    normal! G
    nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :bd!&amp;lt;CR&amp;gt;
endfunction
nnoremap &amp;lt;leader&amp;gt;c :call RunTex()&amp;lt;CR&amp;gt;
    &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Quick PDF Preview:&lt;/strong&gt; When I need to see the actual PDF, &lt;code&gt;&amp;lt;leader&amp;gt;o&lt;/code&gt; opens it in Evince without leaving Vim:&lt;/p&gt;
&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
function! ViewTex() abort
    let pdf = substitute(expand(&amp;#x27;%:p&amp;#x27;), &amp;#x27;\.tex$&amp;#x27;, &amp;#x27;.pdf&amp;#x27;, &amp;#x27;&amp;#x27;)
    silent! execute &amp;#x27;!nohup evince &amp;#x27; . shellescape(pdf) . &amp;#x27; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;#x27;
    redraw!
endfunction
nnoremap &amp;lt;leader&amp;gt;o :call ViewTex()&amp;lt;CR&amp;gt;
    &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;Smart Insert Mode Shortcuts&lt;/h2&gt;
&lt;p&gt;These are the real workhorses of my LaTeX workflow - they let me insert common elements with minimal keystrokes while keeping me in the flow.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Mathematical Spacing:&lt;/strong&gt; Instead of typing &lt;code&gt;\quad&lt;/code&gt; every time, I just type &lt;code&gt;qq&lt;/code&gt; in insert mode and it expands automatically.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Text Wrapping:&lt;/strong&gt; Need text in a math environment? &lt;code&gt;tt&lt;/code&gt; wraps my text with proper formatting and even positions my cursor inside the braces.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Formatting Commands:&lt;/strong&gt; Bold and underline become second nature with &lt;code&gt;;bf&lt;/code&gt; for &lt;code&gt;\textbf{}&lt;/code&gt; and &lt;code&gt;;u&lt;/code&gt; for &lt;code&gt;\underline{}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Template Loading:&lt;/strong&gt; This is where the real magic happens. I have pre-written templates for common elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;;tcb&lt;/code&gt; inserts a beautiful &lt;code&gt;tcolorbox&lt;/code&gt; environment with proper styling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;;ds&lt;/code&gt; loads my document structure template with title, author, and sections pre-filled&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
inoremap qq \quad
inoremap tt ~\text{}~&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a
inoremap ;bf \textbf{}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a
inoremap ;u \underline{}&amp;lt;++&amp;gt;&amp;lt;ESC&amp;gt;F{a
inoremap ;tcb &amp;lt;ESC&amp;gt;:r /home/mosaid/.vim/snippets/latex/tcb&amp;lt;CR&amp;gt;
inoremap ;ds &amp;lt;ESC&amp;gt;:0r /home/mosaid/.vim/snippets/latex/ds&amp;lt;CR&amp;gt;
    &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;Visual Mode Power Tools&lt;/h2&gt;
&lt;p&gt;When working with existing text, these visual mode mappings are absolute lifesavers:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Quick Formatting:&lt;/strong&gt; Select text and use &lt;code&gt;&amp;lt;leader&amp;gt;b&lt;/code&gt; for bold or &lt;code&gt;&amp;lt;leader&amp;gt;u&lt;/code&gt; for underline - it wraps your selection perfectly.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Math Mode Magic:&lt;/strong&gt; My personal favorites include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;5&lt;/code&gt; on selected text wraps it with math mode delimiters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;6&lt;/code&gt; converts display math &lt;code&gt;\[ \]&lt;/code&gt; to inline math &lt;code&gt;$ $&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;9&lt;/code&gt; transforms &lt;code&gt;\( \)&lt;/code&gt; math notation to the cleaner &lt;code&gt;$ $&lt;/code&gt; syntax&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
vnoremap &amp;lt;leader&amp;gt;$ s~$&amp;lt;C-r&amp;gt;&amp;quot;$~&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;
vnoremap &amp;lt;leader&amp;gt;u c\underline{&amp;lt;C-r&amp;gt;&amp;quot;}&amp;lt;++&amp;gt;
vnoremap &amp;lt;leader&amp;gt;b c\textbf{&amp;lt;C-r&amp;gt;&amp;quot;}&amp;lt;++&amp;gt;
vnoremap 5 c~$&amp;lt;C-r&amp;gt;&amp;quot;$~&amp;lt;Esc&amp;gt;
vnoremap 6 &amp;lt;Esc&amp;gt;:&amp;#x27;&amp;lt;,&amp;#x27;&amp;gt;s/\\\[/\~$/e&amp;lt;CR&amp;gt;:&amp;#x27;&amp;lt;,&amp;#x27;&amp;gt;s/\\\]/\$\~/e&amp;lt;CR&amp;gt;
vnoremap 9 &amp;lt;Esc&amp;gt;:&amp;#x27;&amp;lt;,&amp;#x27;&amp;gt;s/\\(/\~$/eg&amp;lt;CR&amp;gt;:&amp;#x27;&amp;lt;,&amp;#x27;&amp;gt;s/\\)/\$\~/eg&amp;lt;CR&amp;gt;
    &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;Mathematical Efficiency Boosters&lt;/h2&gt;
&lt;p&gt;These small but mighty mappings handle the repetitive mathematical notation that used to slow me down:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
inoremap ;o $(O; \vec i, \vec j)$
inoremap $$ ~$$~&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F$i
inoremap [ []&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;F]i
    &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;;o&lt;/code&gt; mapping is particularly useful for coordinate systems, while &lt;code&gt;$$&lt;/code&gt; and &lt;code&gt;[&lt;/code&gt; handle the common cases of creating math environments and brackets with proper cursor placement.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What started as a simple attempt to reduce typing has evolved into a comprehensive LaTeX editing system that feels almost like having a personal assistant. The beauty of these mappings isn't just in the time saved - it's in the uninterrupted flow state they enable. I no longer break my mathematical reasoning to remember LaTeX syntax; the commands have become extensions of my thought process. Whether you're a student writing homework, a researcher drafting papers, or anyone who frequently works with LaTeX, I highly recommend investing time in building your own Vim mappings - the productivity payoff is absolutely worth it.&lt;/p&gt;</content><category term="vim"></category><category term="Vim"></category><category term="LaTeX"></category><category term="Productivity"></category><category term="Snippets"></category><category term="Tutorial"></category></entry><entry><title>Automating Code Transformations in Vim with RunScriptsOnSelect</title><link href="https://mosaid.xyz/articles/automating-code-transformations-in-vim-with-runscriptsonselect-.html" rel="alternate"></link><published>2025-11-23T18:54:13+00:00</published><updated>2025-11-23T18:54:13+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2025-11-23:/articles/automating-code-transformations-in-vim-with-runscriptsonselect-.html</id><summary type="html">&lt;p&gt;Learn how to create a powerful Vim function that automatically processes selected code through external Python scripts. This tutorial shows you how to implement RunScriptsOnSelect - a versatile Vim function that detects code type from visual selections and applies appropriate transformations using custom Python processors.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Streamlining Code Processing in Vim&lt;/h2&gt;

&lt;p&gt;When I work with multiple programming languages, I often need to process code snippets through external tools. Manually handling this became tedious, so I created a Vim function that automates the entire workflow. The RunScriptsOnSelect function detects code type from visual selections and routes them to appropriate Python processors automatically.&lt;/p&gt;

&lt;h2&gt;How RunScriptsOnSelect Works&lt;/h2&gt;

&lt;p&gt;The function operates in visual mode and follows this intelligent workflow:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Selection Capture:&lt;/strong&gt; It captures the visually selected text range including line numbers&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Type Detection:&lt;/strong&gt; Uses the first line as a type indicator for processing&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Validation:&lt;/strong&gt; Checks against allowed processors to prevent errors&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Routing:&lt;/strong&gt; Sends content to the appropriate Python script&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Replacement:&lt;/strong&gt; Cleanly replaces selection with processed output&lt;/p&gt;

&lt;h2&gt;The Complete Implementation&lt;/h2&gt;

&lt;p&gt;Here is the complete Vim function that makes this automation possible:&lt;/p&gt;
&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
function! RunScriptsOnSelect()
    &amp;quot; Get selection range
    let l:start_line = line(&amp;quot;&amp;#x27;&amp;lt;&amp;quot;)
    let l:end_line = line(&amp;quot;&amp;#x27;&amp;gt;&amp;quot;)

    &amp;quot; Get selected lines
    let l:lines = getline(l:start_line, l:end_line)

    &amp;quot; First line = type
    let l:type_line = tolower(trim(l:lines[0]))

    &amp;quot; Allowed types
    let l:allowed_types = [&amp;#x27;tex&amp;#x27;, &amp;#x27;html&amp;#x27;, &amp;#x27;py&amp;#x27;, &amp;#x27;vim&amp;#x27;]

    &amp;quot; Validate first line
    if index(l:allowed_types, l:type_line) == -1
        let l:reminder = [&amp;quot;Make the first line of your selection as tex, html, py or md&amp;quot;]
        call setline(l:start_line, l:reminder)
        &amp;quot; Delete the rest of selection
        if l:end_line &amp;gt; l:start_line
            call deletebufline(&amp;#x27;%&amp;#x27;, l:start_line + 1, l:end_line)
        endif
        return
    endif

    &amp;quot; Actual content to process = selection except first line
    let l:selected_text = join(l:lines[1:], &amp;quot;\n&amp;quot;)

    &amp;quot; Script locations
    let l:script_base_dir = &amp;#x27;~/bin/python&amp;#x27;
    let l:script_map = {
    \ &amp;#x27;tex&amp;#x27;:  l:script_base_dir . &amp;#x27;/change-tex.py&amp;#x27;,
    \ &amp;#x27;vim&amp;#x27;:  l:script_base_dir . &amp;#x27;/change-vim.py&amp;#x27;,
    \ &amp;#x27;py&amp;#x27;:   l:script_base_dir . &amp;#x27;/change-python.py&amp;#x27;,
    \ &amp;#x27;html&amp;#x27;: l:script_base_dir . &amp;#x27;/change-html.py&amp;#x27;,
    \ }

    &amp;quot; Run script
    let l:output = system(&amp;#x27;python3 &amp;#x27; . l:script_map[l:type_line], l:selected_text)
    let l:output_lines = split(l:output, &amp;quot;\n&amp;quot;)

    &amp;quot; ---- SIMPLE &amp;amp; SAFE REPLACEMENT ----
    &amp;quot; 1) Delete entire selection
    call deletebufline(&amp;#x27;%&amp;#x27;, l:start_line, l:end_line)

    &amp;quot; 2) Insert type line + script output
    call append(l:start_line - 1, [l:type_line] + l:output_lines)
endfunction

vnoremap &amp;lt;leader&amp;gt;rs :&amp;lt;C-U&amp;gt;call RunScriptsOnSelect()&amp;lt;CR&amp;gt;

    &lt;/code&gt;
&lt;/pre&gt;

&lt;h3&gt;Robust Type Validation&lt;/h3&gt;
&lt;p&gt;By checking against the allowed_types list, we ensure only supported processors are used. If someone tries an unsupported type, they get immediate feedback with clear instructions.&lt;/p&gt;

&lt;h3&gt;Flexible Script Mapping&lt;/h3&gt;
&lt;p&gt;The script_map dictionary makes it incredibly easy to extend functionality. When I want to add support for a new language, I simply add another entry to the mapping - no need to modify the core function logic.&lt;/p&gt;

&lt;h3&gt;Clean Output Replacement&lt;/h3&gt;
&lt;p&gt;This was the trickiest part to get right. The function handles cases where processed output might be longer or shorter than the original selection, ensuring clean replacement without leaving orphaned lines or missing content.&lt;/p&gt;

&lt;h2&gt;Example Processor Script&lt;/h2&gt;

&lt;p&gt;Here is a sample Python script that demonstrates how to process Vim script content. This particular example escapes HTML and wraps Vim code in proper pre and code tags:&lt;/p&gt;

&lt;pre class="language-python"&gt;
    &lt;code class="language-python"&gt;
import sys
import html

def process_text(content):
    &amp;quot;&amp;quot;&amp;quot;Escape LaTeX and wrap in &amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;.&amp;quot;&amp;quot;&amp;quot;
    escaped_content = html.escape(content)
    wrapped_content = f&amp;quot;&amp;quot;&amp;quot;&amp;lt;pre class=&amp;quot;language-vim&amp;quot;&amp;gt;
    &amp;lt;code class=&amp;quot;language-vim&amp;quot;&amp;gt;
{escaped_content}
    &amp;lt;/code&amp;gt;
&amp;lt;/pre&amp;gt;
&amp;quot;&amp;quot;&amp;quot;
    return wrapped_content

if __name__ == &amp;quot;__main__&amp;quot;:
    if len(sys.argv) &amp;gt; 1:
        # Read content from file if a path is provided
        file_path = sys.argv[1]
        try:
            with open(file_path, &amp;#x27;r&amp;#x27;) as f:
                content = f.read()
        except Exception as e:
            print(f&amp;quot;Error processing file: {e}&amp;quot;)
            sys.exit(1)
    else:
        # Otherwise, read content from stdin
        content = sys.stdin.read()

    print(process_text(content))
    &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;To use this function in your daily workflow, follow these steps:&lt;/p&gt;

&lt;p&gt;1. Enter visual mode and select your code block&lt;/p&gt;
&lt;p&gt;2. Make sure the first line indicates the processor type&lt;/p&gt;
&lt;p&gt;3. Press your leader key followed by rs&lt;/p&gt;

&lt;p&gt;For example, to process Vim script, your selection would look like this:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
vim
function! ExampleFunction()
    echo &amp;quot;This will be processed as Vim script&amp;quot;
endfunction
    &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;Customization Opportunities&lt;/h2&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Extended Language Support:&lt;/strong&gt; Add more types to the allowed_types list for additional languages&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Custom Script Locations:&lt;/strong&gt; Modify the script_base_dir to point to your preferred script locations&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Error Handling:&lt;/strong&gt; Add more robust error handling for missing script files or processing failures&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Preview Functionality:&lt;/strong&gt; Implement a preview mode that shows changes before applying them&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Multiple Processors:&lt;/strong&gt; Allow chaining multiple processors for complex transformations&lt;/p&gt;

&lt;p&gt;This Vim function demonstrates how we can combine Vim's powerful extensibility with Python's processing capabilities. It creates a seamless workflow that adapts to multiple programming languages while maintaining the efficiency and speed that makes Vim such a powerful editor.&lt;/p&gt;

&lt;p&gt;The beauty of this approach is its simplicity and extensibility. Once you have the core function working, adding support for new languages or processors becomes trivial. I have found this particularly useful when working with documentation, code samples, and automated formatting tasks.&lt;/p&gt;</content><category term="vim"></category><category term="Vim"></category><category term="Automation"></category><category term="Python"></category><category term="Scripting"></category><category term="Code Processing"></category><category term="Visual Mode"></category><category term="Text Transformations"></category></entry><entry><title>Improving TeX Editing in Vim with a Smart 'Select Inside Any Pair' Function</title><link href="https://mosaid.xyz/articles/improving-tex-editing-in-vim-with-a-smart-'select-inside-any-pair'-function-.html" rel="alternate"></link><published>2025-11-23T18:14:02+00:00</published><updated>2025-11-23T18:14:02+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2025-11-23:/articles/improving-tex-editing-in-vim-with-a-smart-'select-inside-any-pair'-function-.html</id><summary type="html">&lt;p&gt;A practical guide showing how a custom Vim function intelligently selects text inside brackets, quotes, and TeX math delimiters—making LaTeX editing faster and more natural.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Improving Your TeX Workflow in Vim with a Smart “Select Inside Any Pair” Function&lt;/h1&gt;

&lt;p&gt;One of the reasons I love working in Vim is how easily I can extend it to match the way I write and edit. When dealing with LaTeX files—especially long derivations, nested parentheses, or math environments—I often need to quickly select the text inside &lt;code&gt;{…}&lt;/code&gt;, &lt;code&gt;(…)&lt;/code&gt;, or even inside &lt;code&gt;$…$&lt;/code&gt; blocks. Doing this manually wastes time, interrupts my flow, and becomes frustrating when there are many levels of nesting.&lt;/p&gt;

&lt;p&gt;To fix this, I wrote a custom function that intelligently selects whatever “pair” I am currently inside: curly braces, brackets, parentheses, math mode, quotes—anything. This small function has massively improved how I work inside TeX files.&lt;/p&gt;

&lt;h2&gt;Why This Function Matters&lt;/h2&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Instant selection in TeX math:&lt;/strong&gt; When working between &lt;code&gt;$…$&lt;/code&gt;, a single keystroke selects the content cleanly—even in long expressions.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Nested structures handled automatically:&lt;/strong&gt; For &lt;code&gt;{ { ( … ) } }&lt;/code&gt;, the function detects the correct level around the cursor instead of selecting the wrong one.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Works across all common pairs:&lt;/strong&gt; Parentheses, brackets, braces, math mode, quotes, and even symmetric delimiters are supported.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Better than built-in text objects:&lt;/strong&gt; Vim’s default &lt;code&gt;vi(&lt;/code&gt; and friends work only when your cursor is right next to the delimiter. This custom function works even if you're deep inside the content.&lt;/p&gt;

&lt;h2&gt;The Function That Does the Magic&lt;/h2&gt;

&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;
function! SelectInsideAnyPair()
    let l:savepos = getpos(&amp;#x27;.&amp;#x27;)

    let l:pairs = [
    \ [&amp;#x27;{&amp;#x27;, &amp;#x27;}&amp;#x27;],
    \ [&amp;#x27;[&amp;#x27;, &amp;#x27;]&amp;#x27;],
    \ [&amp;#x27;(&amp;#x27;, &amp;#x27;)&amp;#x27;],
    \ [&amp;#x27;$&amp;#x27;, &amp;#x27;$&amp;#x27;],
    \ [&amp;#x27;&amp;quot;&amp;#x27;, &amp;#x27;&amp;quot;&amp;#x27;],
    \ [&amp;quot;&amp;#x27;&amp;quot;, &amp;quot;&amp;#x27;&amp;quot;]
    \ ]

    for l:pair in l:pairs
        let l:open  = l:pair[0]
        let l:close = l:pair[1]

        if l:open ==# l:close
            &amp;quot; symmetric delimiters
            let l:start = searchpos(&amp;#x27;\V&amp;#x27; . l:open, &amp;#x27;bnW&amp;#x27;)
            let l:end   = searchpos(&amp;#x27;\V&amp;#x27; . l:close, &amp;#x27;nW&amp;#x27;)
        else
            &amp;quot; asymmetric pairs (handles nesting)
            let l:start = searchpairpos(&amp;#x27;\V&amp;#x27; . l:open, &amp;#x27;&amp;#x27;, &amp;#x27;\V&amp;#x27; . l:close, &amp;#x27;bnW&amp;#x27;)
            let l:end   = searchpairpos(&amp;#x27;\V&amp;#x27; . l:open, &amp;#x27;&amp;#x27;, &amp;#x27;\V&amp;#x27; . l:close, &amp;#x27;nW&amp;#x27;)
        endif

        if l:start[0] &amp;gt; 0 &amp;amp;&amp;amp; l:end[0] &amp;gt; 0
            let l:cur = getpos(&amp;#x27;.&amp;#x27;)
            let l:start_before_cursor = (l:start[0] &amp;lt; l:cur[1]) || (l:start[0] == l:cur[1] &amp;amp;&amp;amp; l:start[1] &amp;lt;= l:cur[2])
            let l:end_after_cursor   = (l:end[0]   &amp;gt; l:cur[1]) || (l:end[0]   == l:cur[1] &amp;amp;&amp;amp; l:end[1]   &amp;gt;= l:cur[2])

            if l:start_before_cursor &amp;amp;&amp;amp; l:end_after_cursor
                &amp;quot; start just after opening delimiter
                call setpos(&amp;#x27;.&amp;#x27;, [0, l:start[0], l:start[1] + 1, 0])
                normal! v
                &amp;quot; end just before closing delimiter
                call setpos(&amp;#x27;.&amp;#x27;, [0, l:end[0], l:end[1], 0])
                normal! h
                return
            endif
        endif
    endfor

    &amp;quot; --- fallback: select whole line (without trailing $) ---
    let l:lnum = line(&amp;#x27;.&amp;#x27;)
    let l:line = getline(l:lnum)

    &amp;quot; compute start and end columns
    let l:start_col = 1
    let l:end_col   = len(l:line)

    &amp;quot; if line ends with $, exclude it
    if l:end_col &amp;gt; 0 &amp;amp;&amp;amp; l:line[-1:] ==# &amp;#x27;$&amp;#x27;
        let l:end_col -= 1
    endif

    if l:end_col &amp;gt;= l:start_col
        call setpos(&amp;#x27;.&amp;#x27;, [0, l:lnum, l:start_col, 0])
        normal! v
        call setpos(&amp;#x27;.&amp;#x27;, [0, l:lnum, l:end_col, 0])
    else
        call setpos(&amp;#x27;.&amp;#x27;, l:savepos)
        echo &amp;quot;Nothing to select&amp;quot;
    endif
endfunction

nnoremap &amp;lt;silent&amp;gt; &amp;lt;leader&amp;gt;v :call SelectInsideAnyPair()&amp;lt;CR&amp;gt;

    &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;This tiny Vim function eliminates the repetitive hassle of manually selecting content inside brackets, quotes, or math delimiters. For LaTeX-heavy workflows, it’s a real productivity booster.&lt;/p&gt;</content><category term="vim"></category><category term="vim"></category><category term="latex"></category><category term="tex"></category><category term="productivity"></category><category term="functions"></category><category term="editing"></category><category term="mappings"></category></entry><entry><title>How I Use Vim to Do Quick Calculations While Writing LaTeX</title><link href="https://mosaid.xyz/articles/how-i-use-vim-to-do-quick-calculations-while-writing-latex-4.html" rel="alternate"></link><published>2025-09-28T13:48:54+00:00</published><updated>2025-09-28T13:48:54+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2025-09-28:/articles/how-i-use-vim-to-do-quick-calculations-while-writing-latex-4.html</id><summary type="html">&lt;p&gt;Add instant Python execution to Vim with two simple functions. Learn how I use this trick to calculate percentages, do quick math, and edit LaTeX documents more efficiently.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Supercharging Vim: Running Python Code Directly from Your Editor&lt;/h2&gt;

&lt;figure&gt;
    &lt;img src="/theme/images/articles/images/vim-python-on-the-fly.gif" alt="Running python directly in vim" style="max-width:100%;height:auto;" &gt;
    &lt;figcaption&gt;Running python directly in vim&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I've always loved Vim for its speed, simplicity, and ability to stay entirely in the terminal while doing serious work. But recently, I added a layer of magic that completely changed how I use it — running Python code &lt;em&gt;directly from Vim&lt;/em&gt; without leaving the editor. This has been a game-changer, especially when editing LaTeX documents or writing technical content that needs quick calculations on the fly.&lt;/p&gt;

&lt;p&gt;Imagine this: I'm writing an article and need to calculate a percentage or quickly check a value before putting it into my text. Instead of switching to a Python REPL or using a calculator, I just visually select the code, hit a keybinding, and Vim either shows me the result in a scratch buffer or replaces the selected text with the computed value. It's seamless, and it's awesome.&lt;/p&gt;

&lt;h3&gt;The Magic Behind It&lt;/h3&gt;

&lt;p&gt;Here’s what makes this possible — two small but powerful Vimscript functions that I added to my configuration. The first one runs any visually selected Python code and shows me the output in a new scratch buffer:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;
function! RunPythonSelection() range
    " Save current register and selection type
    let l:save_reg = getreg('"')
    let l:save_regtype = getregtype('"')

    " Yank the visually selected text into the default register
    normal! ""gvy
    let l:code = getreg('"')

    " Restore register
    call setreg('"', l:save_reg, l:save_regtype)

    " Trim leading/trailing whitespace
    let l:code = substitute(l:code, '^\s*', '', '')
    let l:code = substitute(l:code, '\s*$', '', '')

    " Run the exact selected text as Python code
    let l:output = system('python3 -c ' . shellescape(l:code))

    " Open a scratch buffer and display the output
    enew
    call setline(1, split(l:output, "\n"))
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
endfunction


xnoremap &lt;silent&gt; &amp;lt;leader&amp;gt;c :&amp;lt;C-u&amp;gt;call RunPythonSelection()&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;This function is brilliant for experimentation. I just highlight a few lines of Python, press &lt;code&gt;&amp;lt;leader&amp;gt;c&lt;/code&gt;, and I get a temporary buffer with the result — no clutter in my main file, no switching windows.&lt;/p&gt;

&lt;h3&gt;But Wait, It Gets Better&lt;/h3&gt;

&lt;p&gt;Sometimes I don't just want to see the result, I want to &lt;em&gt;replace&lt;/em&gt; the selected text with the result. That's where the second function comes in:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;
function! RunPythonAndReplace() range
    " Save current register and selection type
    let l:save_reg = getreg('"')
    let l:save_regtype = getregtype('"')

    " Yank visually selected text into the default register
    normal! ""gvy
    let l:code = getreg('"')

    " Restore register contents
    call setreg('"', l:save_reg, l:save_regtype)

    " Trim whitespace
    let l:code = substitute(l:code, '^\s*', '', '')
    let l:code = substitute(l:code, '\s*$', '', '')

    " Detect if selection is a single line
    if line("'&lt;") == line("'&gt;")
        let l:code = 'print(' . l:code . ')'
    endif

    " add some imports
    let l:code = 'from math import *;' . l:code

    " Run the code through Python
    let l:output = system('python3 -c ' . shellescape(l:code))

    " Remove trailing newline from output
    let l:output = substitute(l:output, '\n\+$', '', '')

    " Replace the selection with the output
    execute "normal! gv\"_c" . l:output
endfunction

xnoremap &lt;silent&gt; &amp;lt;leader&amp;gt;r :&amp;lt;C-u&amp;gt;call RunPythonAndReplace()&amp;lt;CR&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;This one is my favorite. Let's say I'm writing a LaTeX file and I have:&lt;/p&gt;

&lt;pre class="language-latex" &gt;
    &lt;code class="language-latex" &gt;
The success rate is $ (45/60)*100 $ \%.
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;I can just visually select &lt;code&gt;(45/60)*100&lt;/code&gt;, hit &lt;code&gt;&amp;lt;leader&amp;gt;r&lt;/code&gt;, and &lt;strong&gt;boom&lt;/strong&gt; — it’s replaced with &lt;code&gt;75.0&lt;/code&gt;. This keeps me focused and lets me write accurate numbers without ever leaving my editor.&lt;/p&gt;

&lt;figure&gt;
    &lt;img src="/theme/images/articles/images/vim-python-on-the-fly.gif" alt="Running python directly in vim" style="max-width:100%;height:auto;" &gt;
    &lt;figcaption&gt;Running python directly in vim&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3&gt;Why This Matters&lt;/h3&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;Context Switching is the Enemy:&lt;/strong&gt; Leaving Vim to open a calculator or REPL breaks flow. This solves that.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Perfect for Technical Writing:&lt;/strong&gt; I use this trick while editing LaTeX code, which often requires quick math or unit conversions.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Great for Learning:&lt;/strong&gt; You can experiment with Python right inside Vim, which is fantastic if you're learning to code or testing snippets.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Completely Customizable:&lt;/strong&gt; These functions are just Vimscript — you can extend them, add error handling, or even log outputs to a file.&lt;/p&gt;

&lt;h3&gt;My Verdict&lt;/h3&gt;

&lt;p&gt;Vim was already my favorite editor, but these little functions make it feel like a true integrated environment. Whether I'm writing articles, preparing lecture notes, or hacking on LaTeX, I now have instant Python execution at my fingertips. And honestly, it feels like cheating — but in the best possible way.&lt;/p&gt;

&lt;p&gt;If you use Vim and write technical documents or code, give these functions a try. They’ll make your workflow smoother, faster, and more fun.&lt;/p&gt;</content><category term="vim"></category><category term="vim python integration"></category><category term="run python in vim"></category><category term="vimscript run python"></category><category term="vim productivity"></category><category term="latex workflow"></category><category term="vim functions"></category><category term="on-the-fly calculation"></category><category term="technical writing tools"></category><category term="vim tips"></category><category term="python scratch buffer"></category></entry><entry><title>Efficiently Yank and Select Content Between Characters in Vim</title><link href="https://mosaid.xyz/articles/efficiently-yank-and-select-content-between-characters-in-vim-271.html" rel="alternate"></link><published>2025-01-03T20:00:21+00:00</published><updated>2025-01-03T20:00:21+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2025-01-03:/articles/efficiently-yank-and-select-content-between-characters-in-vim-271.html</id><summary type="html">&lt;p&gt;Learn how to enhance Vim for editing text between custom characters, including LaTeX math expressions, by extending its built-in pair handling&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Efficient Editing Between Characters in Vim&lt;/h2&gt;

&lt;p&gt;Editing content between specific characters is a frequent task, especially in structured formats like code or LaTeX files. Vim's built-in commands allow you to yank, delete, or visually select text between or around certain characters. Here’s a closer look at some of these commands and how you can extend their functionality.&lt;/p&gt;

&lt;h3&gt;Understanding Vim's Built-in Commands&lt;/h3&gt;

&lt;p&gt;Vim provides a set of intuitive commands for working with text between or around specific characters. Let’s break down a few examples:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;yit&lt;/code&gt; (Yank Inner Tag):&lt;/strong&gt; This command yanks the content inside the nearest tag pair, such as &lt;code&gt;&amp;lt;div&amp;gt;this text will be yanked&amp;lt;/div&amp;gt;&lt;/code&gt;. For example, with the cursor inside:&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;yit&lt;/code&gt; copies "this text will be yanked" to the clipboard.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;vit&lt;/code&gt; (Visual Select Inner Tag):&lt;/strong&gt; This highlights the content inside a tag pair for further operations like deleting or copying.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;yi"&lt;/code&gt; (Yank Inner Quotes):&lt;/strong&gt; Copies the text inside quotes, like:&lt;/p&gt;
&lt;pre class="language-text"&gt;
    &lt;code class="language-text"&gt;

"This is a string"

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;yi"&lt;/code&gt; yanks "This is a string". Similarly, &lt;code&gt;vi"&lt;/code&gt; selects it visually.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;yi(&lt;/code&gt;, &lt;code&gt;yi[&lt;/code&gt;, &lt;code&gt;yi{&lt;/code&gt;:&lt;/strong&gt; These commands work for parentheses, brackets, and braces, yanking the content inside them.&lt;/p&gt;

&lt;h3&gt;Why Extend Vim's Pair Handling?&lt;/h3&gt;

&lt;p&gt;While Vim supports common delimiters by default, specific workflows often demand custom character pairs. For instance, in LaTeX files, mathematical content is often enclosed within dollar signs (&lt;code&gt;$...$&lt;/code&gt;). Copying or editing such content with precision is essential for efficiency. This need prompted me to extend Vim's functionality to handle additional characters like &lt;code&gt;$&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Adding Custom Pair Support&lt;/h3&gt;

&lt;p&gt;Here’s how you can extend Vim to handle additional characters like underscores, dollar signs, or even special delimiters. Add the following script to your &lt;code&gt;.vimrc&lt;/code&gt; file:&lt;/p&gt;

&lt;pre class="language-vim"&gt;
    &lt;code class="language-vim"&gt;

for s:char in [ '_', '.', ':', ',', ';', '&lt;bar&gt;', '/', '&lt;bslash&gt;', '*', '+', '%', '$' ]
  execute 'xnoremap i' . s:char . ' :&lt;C-u&gt;normal! T' . s:char . 'vt' . s:char . '&lt;CR&gt;'
  execute 'onoremap i' . s:char . ' :normal vi' . s:char . '&lt;CR&gt;'
  execute 'xnoremap a' . s:char . ' :&lt;C-u&gt;normal! F' . s:char . 'vf' . s:char . '&lt;CR&gt;'
  execute 'onoremap a' . s:char . ' :normal va' . s:char . '&lt;CR&gt;'
endfor

&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;With this script, you can:&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;yi$&lt;/code&gt;:&lt;/strong&gt; Yank content inside dollar signs, such as math expressions in LaTeX.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;vi_&lt;/code&gt;:&lt;/strong&gt; Visual select text inside underscores.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;&lt;code&gt;va.&lt;/code&gt;:&lt;/strong&gt; Select content and the enclosing dot.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Customizing Vim to support additional characters boosts productivity, especially for domain-specific tasks like LaTeX editing. By combining Vim’s flexibility with your workflow needs, you can streamline text editing and enhance efficiency.&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="text editing"></category><category term="custom mappings"></category><category term="character pairs"></category><category term="LaTeX editing"></category><category term="Vim customization"></category><category term="inner tag"></category><category term="yank commands"></category></entry><entry><title>Hidden Gems of Vim: Tricks for Efficient Text Editing</title><link href="https://mosaid.xyz/articles/hidden-gems-of-vim-tricks-for-efficient-text-editing-268.html" rel="alternate"></link><published>2025-01-01T11:41:48+00:00</published><updated>2025-01-01T11:41:48+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2025-01-01:/articles/hidden-gems-of-vim-tricks-for-efficient-text-editing-268.html</id><summary type="html">&lt;p&gt;Discover advanced Vim tricks and hidden features to boost your productivity. Learn unique tips for navigation, editing, and automation that will elevate your workflow.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Unlocking Vim's Hidden Potential: Tricks and Gems for Power Users&lt;/h2&gt;

&lt;p&gt;Vim is more than just a text editor; it's a powerhouse of efficiency. While many users are familiar with its basics, countless hidden features can transform your workflow. Let’s dive into some advanced Vim tricks and hidden gems that you might not know!&lt;/p&gt;

&lt;h3&gt;1. Working with Numbers Like a Pro&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Increment and Decrement Across a Block:&lt;/strong&gt; Visual mode isn’t just for selecting text; it can handle numbers too. Select a block of numbers, then use &lt;code&gt;g&amp;lt;C-a&amp;gt;&lt;/code&gt; to increment or &lt;code&gt;g&amp;lt;C-x&amp;gt;&lt;/code&gt; to decrement them sequentially. Perfect for creating numbered lists or adjusting configurations.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Smart Increment:&lt;/strong&gt; Want to increment all numbers in a file? Use &lt;code&gt;:%s/\\d\\+/\\=submatch(0) + 1/g&lt;/code&gt;. This substitution command handles it beautifully.&lt;/p&gt;

&lt;h3&gt;2. Mastering Text Manipulation&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Seamless Line Joining:&lt;/strong&gt; Use &lt;code&gt;gJ&lt;/code&gt; to join lines without inserting a space. Ideal for cleaning up code or text.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Duplicate Lines in a Snap:&lt;/strong&gt; Duplicate the current line with &lt;code&gt;:t.&lt;/code&gt;. Efficient and straightforward!&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Remove Blank Lines:&lt;/strong&gt; Clean up your document by removing blank lines with &lt;code&gt;:g/^$/d&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;3. Unlocking the Power of Registers&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Preserve the Clipboard:&lt;/strong&gt; Use the black hole register &lt;code&gt;"_d&lt;/code&gt; to delete text without affecting your clipboard. Say goodbye to accidental overwrites!&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Direct Register Input:&lt;/strong&gt; Need to add text to a register quickly? Use &lt;code&gt;:let @&amp;quot;=&amp;quot;your text&amp;quot;&lt;/code&gt; to assign text to the unnamed register.&lt;/p&gt;

&lt;h3&gt;4. Smart Navigation with Marks&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Jump to Last Edit:&lt;/strong&gt; Quickly return to the last modified position with &lt;code&gt;`.&lt;/code&gt;. Never lose track of your edits again.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Start and End of Changes:&lt;/strong&gt; Use &lt;code&gt;`[&lt;/code&gt; and &lt;code&gt;`]&lt;/code&gt; to jump to the start and end of the last change.&lt;/p&gt;

&lt;h3&gt;5. Advanced Search and Replace&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Pattern-Based Increments:&lt;/strong&gt; Increment all numbers matching a pattern with &lt;code&gt;:g/pattern/exe &amp;quot;normal &amp;lt;C-a&amp;gt;&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Add Prefixes and Suffixes:&lt;/strong&gt; Transform every line by adding a prefix and suffix using &lt;code&gt;:%s/^\\(.*\\)$/Prefix \\1 Suffix/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;6. Making the Most of Visual Mode&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Reselect Your Last Selection:&lt;/strong&gt; Press &lt;code&gt;gv&lt;/code&gt; to reselect the previous visual selection effortlessly.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Execute Macros in a Range:&lt;/strong&gt; Select a block and execute a macro over it with &lt;code&gt;:'&amp;lt;,'&amp;gt;normal @q&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;7. Enhancing Buffers and Tabs&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Vertical Diff Splits:&lt;/strong&gt; Use &lt;code&gt;:vert diffsplit {file}&lt;/code&gt; to compare files side by side with a vertical split.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Tabs in Control:&lt;/strong&gt; Reorganize your tabs with &lt;code&gt;:tabm {n}&lt;/code&gt; and execute commands across all tabs using &lt;code&gt;:tabdo {cmd}&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;8. Uncommon Editing Features&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Insert Shell Output:&lt;/strong&gt; Bring external command results into Vim with &lt;code&gt;:read !ls&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Batch Edit Lines:&lt;/strong&gt; Append a semicolon to all lines with &lt;code&gt;:normal! A;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;9. Miscellaneous Gems&lt;/h3&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;ASCII and Unicode Insight:&lt;/strong&gt; Know exactly what character you’re dealing with using &lt;code&gt;ga&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Quick File Navigation:&lt;/strong&gt; Open the file path under your cursor in a new buffer with &lt;code&gt;gf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Session Management:&lt;/strong&gt; Save and restore your session with &lt;code&gt;:mksession mysession.vim&lt;/code&gt; and &lt;code&gt;:source mysession.vim&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Vim’s true power lies in its depth and versatility. Mastering these hidden gems can significantly enhance your productivity. Try them out and incorporate them into your daily workflow—you’ll wonder how you ever lived without them!&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="Vim tricks"></category><category term="advanced Vim"></category><category term="hidden Vim features"></category><category term="productivity tips"></category><category term="text editing"></category><category term="Vim navigation"></category><category term="Vim search and replace"></category></entry><entry><title>The Ultimate Vim Setup (My 2024 vimrc ) : Essential Commands, Configurations, and Plugin Tips</title><link href="https://mosaid.xyz/articles/the-ultimate-vim-setup-my-2024-vimrc-essential-commands-configurations-and-plugin-tips-224.html" rel="alternate"></link><published>2024-04-12T18:04:14+00:00</published><updated>2024-04-12T18:04:14+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2024-04-12:/articles/the-ultimate-vim-setup-my-2024-vimrc-essential-commands-configurations-and-plugin-tips-224.html</id><summary type="html">&lt;p&gt;Explore essential Vim commands, configuration tips, and plugin setups to enhance your text editing efficiency. Learn about installing Vim, mastering plugin configurations, and optimizing Vim for various filetypes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Are you ready to level up your text editing game? If you're a Linux or Windows user looking to harness the power of Vim, you're in the right place. Vim is a highly customizable text editor known for its efficiency and versatility. However, it can be intimidating for beginners. Fear not! In this guide, we'll walk you through the process of setting up Vim for the first time, catering to both Linux and Windows users.&lt;/p&gt;

&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="#installing-vim"&gt;Installing Vim&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#linux-users"&gt;For Linux Users&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#windows-users"&gt;For Windows Users&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#essential-vim-commands"&gt;Essential Vim Commands&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#configuring-vim"&gt;Configuring Vim&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#vimrc-file"&gt; 1: Vimrc File&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#vim-plugins"&gt; 2: Vim Plugins&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#history-undo-redo"&gt; 4: History, Undo, Redo&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#more-settings"&gt; 5: More Settings&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#color-highlighting"&gt; 6: Color Highlighting&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#search-additional-settings"&gt; 7: Search and Additional Settings&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#custom-functions"&gt; 8: Custom Functions for File and Command History Management&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#plugin-configurations"&gt;Plugin Configurations&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#fzf-settings"&gt; 1: FZF Settings&lt;/a&gt;&lt;/li&gt;
  &lt;li style="margin-left: 15px;"&gt;&lt;a href="#completion-youcompleteme"&gt; 2: Completion and YouCompleteMe&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#filetype-specific-configurations"&gt;Filetype Specific Configurations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="installing-vim"&gt;Installing Vim&lt;/h2&gt;

&lt;h3 id="linux-users"&gt;For Linux Users:&lt;/h3&gt;

&lt;p&gt;Installing Vim on Linux is a breeze. Simply open your terminal and enter the following command:&lt;/p&gt;

&lt;pre class="language-bash" &gt;
&lt;code class="language-bash" &gt;

# debian based distributions
sudo apt-get install vim

# Arch based distributions
sudo pacman -S vim

&lt;/code&gt;
&lt;/pre&gt;

&lt;h3 id="windows-users"&gt;For Windows Users:&lt;/h3&gt;

&lt;p&gt;Windows users can download Vim from its official website (&lt;a href="https://www.vim.org/download.php" target="_blank"&gt;https://www.vim.org/download.php&lt;/a&gt;) or use package managers like Chocolatey (&lt;a href="https://chocolatey.org/packages/vim" target="_blank"&gt;https://chocolatey.org/packages/vim&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id="essential-vim-commands"&gt;Essential Vim Commands&lt;/h2&gt;

&lt;p&gt;One of the most distinctive features that sets Vim apart from other text editors is its powerful and efficient text manipulation capabilities, primarily driven by Vim motions. These commands streamline the editing process, allowing users to navigate, edit, and manipulate text with remarkable speed and precision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;i&lt;/strong&gt;: Enter insert mode&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Esc&lt;/strong&gt;: Exit insert mode&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;:w&lt;/strong&gt;: Save changes&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;:q&lt;/strong&gt;: Quit Vim&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;:wq&lt;/strong&gt;: Save and quit&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;h, j, k, l&lt;/strong&gt;: Navigate left, down, up, right respectively&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;yy&lt;/strong&gt;: Copy current line&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;dd&lt;/strong&gt;: Delete current line&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p&lt;/strong&gt;: Paste copied/deleted text&lt;/p&gt;

&lt;p&gt;Mastering these essential Vim commands is the first step towards becoming proficient with this powerful text editor. With practice, users can harness the efficiency and flexibility of Vim motions to edit text swiftly and effectively.&lt;/p&gt;

&lt;h2 id="configuring-vim"&gt;Configuring Vim&lt;/h2&gt;

&lt;h3 id="vimrc-file"&gt; 1: Vimrc File&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.vimrc&lt;/code&gt; file is a key component for configuring Vim according to your preferences. It contains settings, key mappings, and customizations to tailor Vim to your workflow.&lt;/p&gt;

&lt;p&gt;On Linux systems, including Ubuntu, Debian, Fedora, and others, the &lt;code&gt;.vimrc&lt;/code&gt; file is typically located in the user's home directory. You can access it using a text editor or via the command line. Here's the path:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

/home/your_username/.vimrc
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Replace &lt;code&gt;your_username&lt;/code&gt; with your actual username.&lt;/p&gt;

&lt;p&gt;For Windows users, the location of the &lt;code&gt;.vimrc&lt;/code&gt; file may vary depending on the Vim installation method:&lt;/p&gt;

&lt;p&gt;&amp;#8226;If you installed Vim using the installer, the &lt;code&gt;.vimrc&lt;/code&gt; file is usually located in the user's home directory:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

C:\Users\YourUsername\_vimrc
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;#8226;If you are using the Vim distribution called "Cream," the &lt;code&gt;_vimrc&lt;/code&gt; file may be located in:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

C:\Program Files\Vim\_vimrc
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Remember to use a text editor or the command line to edit the &lt;code&gt;.vimrc&lt;/code&gt; file and add your desired configurations.&lt;/p&gt;

&lt;h3 id="vim-plugins"&gt; 2: Vim Plugins&lt;/h3&gt;

&lt;p&gt;Vim plugins enhance the functionality of the editor by adding features, customizations, and themes. They allow users to tailor Vim to their specific needs and preferences.&lt;/p&gt;

&lt;p&gt;&lt;strong style="color: #000000;"&gt;Vim-Plug&lt;/strong&gt; is a popular plugin manager for Vim that simplifies the process of installing and managing plugins. Here's how to set it up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt; &lt;strong&gt;Download Vim-Plug:&lt;/strong&gt; Visit the Vim-Plug GitHub repository at &lt;a href="https://github.com/junegunn/vim-plug"&gt;https://github.com/junegunn/vim-plug&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt; &lt;strong&gt;Copy Vim-Plug:&lt;/strong&gt; Depending on your operating system:&lt;/p&gt;&lt;/li&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;   &lt;strong&gt;For Windows:&lt;/strong&gt; Download the &lt;code&gt;plug.vim&lt;/code&gt; file from the Vim-Plug GitHub repository. Then, copy the downloaded file to the &lt;code&gt;%USERPROFILE%\vimfiles\autoload&lt;/code&gt; directory.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;   &lt;strong&gt;For Linux:&lt;/strong&gt; Download the &lt;code&gt;plug.vim&lt;/code&gt; file from the Vim-Plug GitHub repository. Then, copy the downloaded file to the &lt;code&gt;~/.vim/autoload&lt;/code&gt; directory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/ol&gt;

&lt;p&gt;With Vim-Plug installed, you're ready to start adding and managing plugins for your Vim editor.&lt;/p&gt;

&lt;p&gt;Below is an example of configuring Vim plugins using &lt;code&gt;Vim-Plug&lt;/code&gt;:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

" enable syntax highlighting and filetype detection
syntax on
filetype plugin indent on

" Specify a directory for plugins.
call plug#begin('~/.vim/bundle')

" Gruvbox theme.
Plug 'gruvbox-community/gruvbox'

" fzf
Plug 'junegunn/fzf', { 'do': { -&amp;gt; fzf#install() } }
Plug 'junegunn/fzf.vim'

" Briefly highlight which text was yanked.
Plug 'machakann/vim-highlightedyank'

" Modify * to also work with visual selections.
Plug 'nelstrom/vim-visual-star-search'

" Better display unwanted whitespace.
Plug 'ntpeters/vim-better-whitespace'

" A bunch of useful language related snippets (ultisnips is the engine).
Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'

"C family languages completion
Plug 'Valloric/YouCompleteMe'

" IDE-like bar
Plug 'bagrat/vim-buffet'

"
"Plug 'lervag/vimtex'

call plug#end()

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the example above, the Vim-Plug plugin manager is used to manage plugins. Each &lt;code&gt;Plug&lt;/code&gt; command specifies a plugin to install or manage. After adding the desired plugins to your &lt;code&gt;.vimrc&lt;/code&gt; file, save it and reload Vim to apply the changes. You can then install the plugins by running &lt;code&gt;:PlugInstall&lt;/code&gt; command within Vim.&lt;/p&gt;

&lt;p&gt;For Windows users, ensure that you name the bundle directory appropriately. In the example above, the directory is named &lt;code&gt;bundle&lt;/code&gt;. However, on Windows systems, the directory separator is typically a backslash (\) instead of a forward slash (/). Therefore, you should name the directory &lt;code&gt;bundle&lt;/code&gt; or &lt;code&gt;bundle\&lt;/code&gt; accordingly to avoid any path-related issues.&lt;/p&gt;

&lt;p&gt;Experiment with different plugins to discover ones that best suit your workflow and enhance your Vim experience.&lt;/p&gt;

&lt;p&gt;For the plugin "YouCompleteMe," additional steps are required for both Linux and Windows users. Firstly, ensure that Python is installed and added to the system's PATH environment variable. This step is necessary because YouCompleteMe relies on Python for its functionality. Additionally, Git should also be installed and added to the PATH, as YouCompleteMe requires Git for fetching and updating its dependencies.&lt;/p&gt;

&lt;p&gt;After running &lt;code&gt;:PlugInstall&lt;/code&gt; to install the plugins, users need to navigate to the YouCompleteMe plugin directory and execute the installation script manually. This is typically located in the &lt;code&gt;.vim/bundle/YouCompleteMe&lt;/code&gt; directory. Follow the instructions provided in the plugin's documentation or README file to complete the installation process.&lt;/p&gt;

&lt;p&gt;General settings in Vim allow users to customize various aspects of the editor's behavior and appearance to suit their preferences. These settings cover a wide range of functionalities, from enabling features like backspace navigation in insert mode and cursor line highlighting to configuring auto-reading of files changed outside of Vim. Users can also adjust settings related to cursor behavior, encoding, syntax highlighting, and more to create a personalized editing environment. Experimenting with these settings can significantly enhance productivity and streamline the editing experience in Vim.&lt;/p&gt;

&lt;p&gt;In Vim, the "leader" key serves as a customizable prefix for creating custom key mappings. By default, the backslash (\) key is designated as the leader key, but users can redefine it to any other key of their choice. The leader key is often used in combination with other keys to create shortcuts for frequently used commands or custom functions. This allows users to streamline their workflow and increase efficiency by assigning intuitive and memorable key combinations for specific tasks. Utilizing the leader key effectively can significantly enhance productivity and make Vim usage more ergonomic and tailored to individual preferences.&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;


" leader
" -----------------------------------------------------------------------------

let mapleader=";"
noremap &lt;leader&gt;cc :&lt;ESC&gt;gg"+yG&lt;ESC&gt;    " copy the entire document to system clipboard
noremap &lt;leader&gt;y yiW

" General Config
" -----------------------------------------------------------------------------

set backspace=indent,eol,start  "Allow backspace in insert mode
set showcmd                     "Show incomplete cmds down the bottom
set showmode                    "Show current mode down the bottom
set gcr=a:blinkon0              "Disable cursor blink
set visualbell                  "No sounds
set autoread                    "Reload files changed outside vim
set hidden
set cursorline
set modeline
set mouse=n
set encoding=utf-8
set isfname+=32
set showmatch                   "highlight matching [{{{()}}}]
set lazyredraw
set tw=100
set complete+=kspell
"set colorcolumn=100
set formatoptions=tcqrn1
set matchpairs+=&lt;:&gt;             " Use % to jump between pairs
set nocompatible
set noerrorbells visualbell t_vb=
set noshiftround
"set nospell
set nostartofline
set regexpengine=1
set ruler
set ttimeout
set whichwrap=b,s,&lt;,&gt;
set t_Co=256
set title titlestring=
set completeopt-=preview
set splitbelow
set splitright
set formatoptions-=tc       " turn off breaking long lines
set showtabline=0
set wildoptions=pum



&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3 id="history-undo-redo"&gt; 4: History, Undo, Redo&lt;/h3&gt;

&lt;p&gt;In Vim, managing history, undo, and redo functionalities is crucial for efficient editing and reverting changes. By adjusting settings related to history, undo, and redo, users can customize Vim to suit their workflow and preferences.&lt;/p&gt;

&lt;p&gt;Below are some settings commonly used to configure history, undo, and redo in Vim:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;


" history, undo, redo and backup
" -----------------------------------------------------------------------------
set history=10000
set undofile
set undodir=~/.vim/undo
set undolevels=1000
set undoreload=10000
set backupdir=~/.vim/backup
set directory=~/.vim/backup
set viminfo='1000000

"redo
nnoremap r &amp;lt;C-r&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the provided configuration, Vim is set to maintain a history of 10,000 commands. Undo information is saved to a file using the &lt;code&gt;undofile&lt;/code&gt; option, with the directory specified as &lt;code&gt;~/.vim/undo&lt;/code&gt;. Additionally, Vim is configured to maintain a high level of undo information with 1,000 levels and to reload undo files up to 10,000 bytes in size. Backup files are stored in &lt;code&gt;~/.vim/backup&lt;/code&gt;, and the &lt;code&gt;viminfo&lt;/code&gt; option is set to a large value for storing various information about the editing session.&lt;/p&gt;

&lt;p&gt;Furthermore, a mapping is created to allow for easy redoing of changes using the &lt;code&gt;r&lt;/code&gt; key combined with &lt;code&gt;Ctrl+r&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Customizing these settings enables users to effectively manage history, undo, and redo operations in Vim, enhancing productivity and providing greater control over the editing process. It's worth noting that Vim's history and undo/redo functionalities persist even after closing and reopening documents. With settings configured to retain thousands of history actions, users can seamlessly navigate through the editing history of their documents, ensuring a comprehensive and efficient editing experience.&lt;/p&gt;

&lt;h3 id="more-settings"&gt; 5: More Settings&lt;/h3&gt;

&lt;p&gt;Additional settings in Vim allow users to further customize their editing environment and workflow. These settings can range from defining custom key mappings for specific tasks to configuring autocmds and functions to automate certain actions.&lt;/p&gt;

&lt;p&gt;Below are some additional settings commonly used to enhance the Vim experience:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;


" more settings
" -----------------------------------------------------------------------------
"
nnoremap &amp;lt;silent&amp;gt;&amp;lt;leader&amp;gt;l :set number! relativenumber!&amp;lt;CR&amp;gt;
autocmd FocusGained * !terminal_transparency set 90

function! EngType()
  " To switch back from Arabic
  set spell
  set spelllang=en_us
  set spellcapcheck
endfunction

function! FrType()
  set spell
  set spelllang=fr
  set spellfile+=~/.vim/spell/fr.utf-8.add
endfunction

" cycle through buffers
nnoremap &amp;lt;Tab&amp;gt; :bnext&amp;lt;CR&amp;gt;

"window mouvements
nnoremap &amp;lt;down&amp;gt;   &amp;lt;C-W&amp;gt;&amp;lt;C-J&amp;gt;
nnoremap &amp;lt;up&amp;gt;     &amp;lt;C-W&amp;gt;&amp;lt;C-K&amp;gt;
nnoremap &amp;lt;right&amp;gt;  &amp;lt;C-W&amp;gt;&amp;lt;C-L&amp;gt;
nnoremap &amp;lt;left&amp;gt;   &amp;lt;C-W&amp;gt;&amp;lt;C-H&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the provided configuration, custom key mappings are defined to toggle line numbers and relative line numbers using &lt;code&gt;&amp;lt;leader&amp;gt;l&lt;/code&gt;, and to cycle through buffers using &lt;code&gt;&amp;lt;Tab&amp;gt;&lt;/code&gt;. Autocommands are utilized to adjust terminal transparency upon regaining focus, and functions are defined to switch between English and French spell checking modes. Additionally, custom mappings are set for window movements using &lt;code&gt;&amp;lt;down&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;up&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;right&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;left&amp;gt;&lt;/code&gt; keys.&lt;/p&gt;

&lt;p&gt;These additional settings offer users greater flexibility and efficiency in their editing tasks, allowing for a more personalized and streamlined Vim experience.&lt;/p&gt;

&lt;h3 id="color-highlighting"&gt; 6: Color Highlighting&lt;/h3&gt;

&lt;p&gt;Color highlighting in Vim plays a significant role in enhancing readability and visual appeal, making it easier for users to distinguish between different elements within the editor. By configuring color schemes and defining custom highlight groups, users can customize the appearance of various components such as the cursor, cursor line, status line, search results, and more.&lt;/p&gt;

&lt;p&gt;Below are some settings commonly used to configure color highlighting in Vim:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

" highlighting and cursor
" -----------------------------------------------------------------------------
"
colorscheme gruvbox

hi CursorLine       ctermbg=darkgrey  ctermfg=none    term=bold cterm=bold
hi CursorColumn     ctermbg=darkred   ctermfg=white    term=bold cterm=bold
hi StatusLine       ctermbg=darkred   ctermfg=white    term=bold cterm=bold
hi Normal           ctermbg=black     ctermfg=none     term=bold cterm=bold
hi Visual           ctermbg=lightred  ctermfg=black    term=bold cterm=bold
hi Folded           ctermbg=black     ctermfg=red      term=bold cterm=bold
hi FoldColumn       ctermbg=green     ctermfg=yellow   term=bold cterm=bold
hi Search           ctermbg=yellow    ctermfg=black    term=bold cterm=bold
hi Cursor           ctermbg=yellow    ctermfg=yellow   term=bold cterm=bold
hi iCursor          ctermbg=yellow    ctermfg=yellow   term=bold cterm=bold

hi SpellBad         cterm=underline   ctermfg=black    ctermbg=red
hi SpellLocal       cterm=underline   ctermfg=black    ctermbg=red
hi SpellRare        cterm=underline   ctermfg=black    ctermbg=red
hi SpellCap         cterm=underline   ctermfg=black    ctermbg=green

"make block cursor
let &amp;t_ti.="\e[1 q"
let &amp;t_SI.="\e[5 q"
let &amp;t_EI.="\e[1 q"
let &amp;t_te.="\e[0 q"

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the provided configuration, the Gruvbox color scheme is used to define various highlight groups for different elements such as the cursor line, cursor column, status line, search results, and more. Additionally, custom mappings are set to make the cursor block-shaped.&lt;/p&gt;

&lt;p&gt;Customizing color highlighting settings allows users to create a visually appealing and personalized editing environment in Vim, enhancing readability and productivity.&lt;/p&gt;

&lt;h3 id="search-additional-settings"&gt; 7: Search and Additional Settings&lt;/h3&gt;

&lt;p&gt;Search functionality in Vim is essential for navigating through documents and finding specific text patterns efficiently. By configuring search settings, users can enhance their searching experience and streamline their workflow. Additionally, there are various other settings that can further customize Vim to suit individual preferences and automate certain tasks.&lt;/p&gt;

&lt;p&gt;Below are some search-related settings and additional configurations commonly used in Vim:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

" Search
set incsearch       " Find the next match as we type the search
set hlsearch        " Highlight searches by default
set ignorecase      " Ignore case when searching...
set smartcase       " ...unless we type a capital

" even more settings
" -----------------------------------------------------------------------------
"
" Automatically deletes all trailing whitespace on save.
autocmd BufWritePre * %s/\s\+$//e
" remember last position
if has("autocmd")
  au BufReadPost * if line("'\"") &amp;gt; 1 &amp;amp;&amp;amp; line("'\"") &amp;lt;= line("$") | exe "normal! g'\"" | endif
endif

" set current directory to the directory of the current file
autocmd BufEnter * if expand("%:p:h") !~ '^/tmp' | silent! lcd %:p:h | endif


" IDE BAR
nnoremap &amp;lt;silent&amp;gt;&amp;lt;leader&amp;gt;k :execute 'set showtabline=' . (&amp;amp;showtabline ==# 2 ? 0 : 2)&amp;lt;CR&amp;gt;

" wordcount
function! WC()
    let filename = expand("%")
    let cmd = "detex " . filename . " | wc -w | tr -d '[:space:]'"
    let result = system(cmd)
    echo result . " words"
endfunction

command WC call WC()


&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the provided configuration, search settings such as &lt;code&gt;incsearch&lt;/code&gt; and &lt;code&gt;hlsearch&lt;/code&gt; are enabled to enhance the search experience by highlighting matches and displaying search results as you type. Additionally, settings like &lt;code&gt;ignorecase&lt;/code&gt; and &lt;code&gt;smartcase&lt;/code&gt; are configured to ignore case when searching, except when typing capital letters.&lt;/p&gt;

&lt;p&gt;Furthermore, additional settings are applied to automate tasks such as deleting trailing whitespace on save and remembering the last cursor position when reopening files.&lt;/p&gt;

&lt;p&gt;Customizing these settings allows users to optimize their searching experience and tailor Vim to their specific preferences and workflow.&lt;/p&gt;

&lt;h3 id="custom-functions"&gt; 8: Custom Functions for File and Command History Management&lt;/h3&gt;

&lt;p&gt;In Vim, custom functions provide a powerful way to automate tasks and enhance productivity. Below are three custom functions designed to streamline file and command history management:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

" this function is a great way to open old files
function! Output()
  enew | 0put =v:oldfiles| nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :e &amp;lt;C-r&amp;gt;=getline('.')&amp;lt;CR&amp;gt;&amp;lt;CR&amp;gt;|normal gg&amp;lt;CR&amp;gt;
  setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;down&amp;gt; j
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;up&amp;gt; k
endfunction
:command! Mm     call Output()
:command! MM     call Output()

function! OutputCommands()
  let history = []
  for i in range(histnr(':'), 1, -1)
    call add(history, histget(':', i))
  endfor
  enew
  call append(0, history)
  normal gg
  setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
  "nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :call setreg('+', getline('.'))&amp;lt;CR&amp;gt;
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :let command = getline('.') | bnext | execute command&amp;lt;CR&amp;gt;
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;down&amp;gt; j
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;up&amp;gt; k
endfunction

:command! Cc     call OutputCommands()
:command! CC     call OutputCommands()

function! OutputRegs()
    " Create a new buffer
    enew
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
    " Append content of non-empty registers a to z
    for reg in range(char2nr('a'), char2nr('z'))
        let reg_char = nr2char(reg)
        let reg_content = getreg(reg_char)
        if !empty(reg_content)
            call append(line('$'), [reg_char .":" , reg_content])
            call append(line('$'), "")
        endif
    endfor
    " Move the cursor to the beginning of the buffer
    normal gg
    " Output message
    echo "Contents of registers a to z "
endfunction

:command! Rr     call OutputRegs()
:command! RR     call OutputRegs()


&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;Output:&lt;/strong&gt; This function opens a new buffer to display a list of old files, allowing easy navigation and reopening of previous documents.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;OutputCommands:&lt;/strong&gt; This function creates a new buffer to display the command history, enabling users to review and execute previous commands.&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;strong&gt;OutputRegs:&lt;/strong&gt; This function generates a new buffer to display the contents of registers a to z, providing a convenient way to access and manage stored text snippets.&lt;/p&gt;

&lt;p&gt;These custom functions offer efficient ways to handle file and command history, as well as manage text snippets stored in registers. For detailed explanations and usage examples of each function, refer to the following articles:&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;a href="/articles/vim-old-files-a-better-way-203/"&gt;Exploring the Output Function For a Better Vim Old Files&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="/articles/vim-tips-simplify-command-execution-with-outputcommands-function-213/"&gt;Mastering OutputCommands for Command History Management&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="/articles/vim-register-magic-what-they-are-and-how-to-use-them-219/"&gt;Understanding Vim Registers and Advanced Usage&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="/articles/how-to-manage-vim-registers-and-retrieve-stored-text-snippets-218/"&gt;Deep Dive into OutputRegs for Register Management&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By incorporating these custom functions into your Vim workflow, you can optimize your editing experience and maximize efficiency.&lt;/p&gt;

&lt;h2 id="plugin-configurations"&gt;Plugin Configurations&lt;/h2&gt;
&lt;h3 id="fzf-settings"&gt; 1: FZF Settings&lt;/h3&gt;

&lt;p&gt;FZF is a powerful command-line fuzzy finder that integrates seamlessly with Vim, providing quick and efficient file and buffer navigation. Below are some configurations commonly used to enhance the FZF experience:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

" fzf settings
" -----------------------------------------------------------------------------
"
" Map a few common things to do with FZF.
nnoremap &amp;lt;silent&amp;gt; &amp;lt;Leader&amp;gt;, :Buffers&amp;lt;CR&amp;gt;
nnoremap &amp;lt;silent&amp;gt; &amp;lt;Leader&amp;gt;s :Files&amp;lt;CR&amp;gt;
nnoremap &amp;lt;silent&amp;gt; &amp;lt;Leader&amp;gt;j :Lines&amp;lt;CR&amp;gt;
nnoremap &amp;lt;silent&amp;gt; &amp;lt;Leader&amp;gt;h :Commands&amp;lt;CR&amp;gt;
nnoremap &amp;lt;silent&amp;gt; &amp;lt;Leader&amp;gt;&amp;lt;leader&amp;gt; :History&amp;lt;CR&amp;gt;

" set filetypes

autocmd BufEnter,BufNew,BufNewFile,BufRead *.tex set ft=tex
autocmd BufEnter,BufNew,BufNewFile,BufRead *.cpp set ft=cpp
autocmd BufEnter,BufNew,BufNewFile,BufRead *.c set ft=c

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the provided configuration, key mappings are defined to quickly access FZF functionality for navigating buffers, files, lines, and command history. Additionally, filetypes are set automatically for specific file extensions to ensure proper syntax highlighting and formatting in Vim.&lt;/p&gt;

&lt;p&gt;These configurations streamline the usage of FZF within Vim, enabling faster and more intuitive navigation through buffers and files.&lt;/p&gt;

&lt;h3 id="completion-youcompleteme"&gt; 2: Completion and YouCompleteMe&lt;/h3&gt;

&lt;p&gt;Completion in Vim is essential for enhancing productivity by providing suggestions and auto-completion for commands, keywords, and file paths. YouCompleteMe (YCM) is a popular plugin that offers intelligent code completion capabilities in Vim. Below are configurations commonly used to optimize completion settings and integrate YouCompleteMe:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim"&gt;

" Completion
" .............................................................................

set wildmode=list:longest
set wildmenu                "enable ctrl-n and ctrl-p to scroll thru matches
set wildignore=*.o,*.obj,*~ "stuff to ignore when tab completing
set wildignore+=*vim/backups*
set wildignore+=*sass-cache*
set wildignore+=*DS_Store*
set wildignore+=vendor/rails/**
set wildignore+=vendor/cache/**
set wildignore+=*.gem
set wildignore+=log/**
set wildignore+=tmp/**
set wildignore+=*.png,*.jpg,*.gif

" YouCompleteMe
" .............................................................................

let g:ycm_global_ycm_extra_conf = '~/.vim/bundle/YouCompleteMe/.ycm_extra_conf.py'
let g:ycm_add_preview_to_completeopt = 0
"let g:ycm_autoclose_preview_window_after_insertion = 1
"let g:ycm_autoclose_preview_window_after_completion = 1
let g:ycm_collect_identifiers_from_tags_files = 1
"set tags+=~/.vim/tags/testtags

let g:ycm_key_list_select_completion = ['&amp;lt;Down&amp;gt;']
let g:ycm_key_list_previous_completion = ['&amp;lt;C-k&amp;gt;', '&amp;lt;Up&amp;gt;']
let g:ycm_key_list_accept_completion = ['&amp;lt;C-y&amp;gt;']

" Additional YouCompleteMe config.
let g:ycm_complete_in_comments = 1
let g:ycm_collect_identifiers_from_comments_and_strings = 1
let g:ycm_seed_identifiers_with_syntax = 1

let g:ycm_server_python_interpreter='python3'
map &amp;lt;leader&amp;gt;g :YcmCompleter GoToDefinitionElseDeclaration&amp;lt;CR&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the provided configuration, completion settings such as &lt;code&gt;wildmode&lt;/code&gt; and &lt;code&gt;wildmenu&lt;/code&gt; are adjusted to enable enhanced tab completion behavior and ignore certain file patterns. YouCompleteMe integration settings are also defined to customize key mappings and enable additional completion features.&lt;/p&gt;

&lt;p&gt;By configuring completion settings and integrating YouCompleteMe, Vim users can enjoy enhanced code completion capabilities and improved productivity in their editing workflows.&lt;/p&gt;

&lt;h2 id="filetype-specific-configurations"&gt;Filetype Specific Configurations&lt;/h2&gt;

&lt;p&gt;Filetype-specific configurations in Vim allow users to customize editor behavior and settings based on the type of file being edited. By organizing configurations into directories such as &lt;code&gt;.vim/after/ftplugin&lt;/code&gt; and &lt;code&gt;.vim/ftplugin&lt;/code&gt;, users can maintain clean and structured configuration files tailored to specific filetypes.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;.vim/after/ftplugin&lt;/code&gt; directory, users can place configuration files that are sourced after the default filetype plugins. This allows for overriding or extending settings specific to certain filetypes, such as "C" and "C++". Similarly, the &lt;code&gt;.vim/ftplugin&lt;/code&gt; directory contains filetype-specific configuration files that are sourced when editing files of corresponding types. For example, configuration files for filetypes like "tex", "php", "sh", "html", and others can be organized here.&lt;/p&gt;

&lt;p&gt;Additionally, users can create a &lt;code&gt;.vim/mysnippets&lt;/code&gt; folder to store filetype-specific code snippets. These snippets can be quickly inserted into files while editing, providing shortcuts for commonly used code patterns or structures.&lt;/p&gt;

&lt;p&gt;If you're interested in delving deeper into each of these filetype-specific configurations, we invite you to keep an eye on this article. We'll continue updating it with more content and detailed explanations, ensuring you have access to the latest insights and configurations for optimizing your Vim experience.&lt;/p&gt;

&lt;p&gt;&amp;#8226;&lt;a href="#" class="pending"&gt;Exploring Vim Configuration for C and C++ Files&lt;/a&gt; (Pending)&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="#" class="pending"&gt;Mastering Vim Configuration for Tex Files&lt;/a&gt; (Pending)&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="#" class="pending"&gt;Advanced Vim Configuration for PHP Files&lt;/a&gt; (Pending)&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="#" class="pending"&gt;Customizing Vim Configuration for Shell Scripts&lt;/a&gt; (Pending)&lt;/p&gt;
&lt;p&gt;&amp;#8226;&lt;a href="#" class="pending"&gt;Optimizing Vim Configuration for HTML Files&lt;/a&gt; (Pending)&lt;/p&gt;
&lt;!-- Add more filetypes as needed --&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In conclusion, Vim is a powerful and versatile text editor that offers extensive customization options to suit individual preferences and workflows. By mastering essential commands, configuring plugins, and leveraging filetype-specific settings, users can optimize their Vim experience for maximum productivity.&lt;/p&gt;

&lt;p&gt;Whether you're a seasoned Vim user or just getting started, exploring and experimenting with different configurations can help you tailor Vim to your specific needs and enhance your editing efficiency.&lt;/p&gt;

&lt;p&gt;Remember, Vim is more than just a text editor—it's a customizable toolkit that adapts to your workflow and empowers you to edit text like a pro.&lt;/p&gt;

&lt;p&gt;Thank you for joining us on this journey through Vim customization. Happy editing!&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="Text Editor"></category><category term="commands"></category><category term="configuration"></category><category term="Plugins"></category><category term="efficiency"></category><category term="productivity"></category><category term="installation"></category><category term="Customization"></category><category term="filetype-specific"></category><category term="optimization"></category></entry><entry><title>Vim Register Magic: What They Are and How to Use Them</title><link href="https://mosaid.xyz/articles/vim-register-magic-what-they-are-and-how-to-use-them-219.html" rel="alternate"></link><published>2024-04-07T09:27:17+00:00</published><updated>2024-04-07T09:27:17+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2024-04-07:/articles/vim-register-magic-what-they-are-and-how-to-use-them-219.html</id><summary type="html">&lt;p&gt;Learn about Vim registers, their functionality, and how to utilize them efficiently. Improve your Vim workflow by mastering the use of registers for storing and manipulating text and data.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Introduction:&lt;/h2&gt;
&lt;p&gt;Vim, the venerable text editor, offers a wide array of features to enhance productivity and efficiency for its users. Among these features are registers, which serve as storage locations for text or other data. Understanding registers and how to utilize them effectively can significantly boost your productivity when working with Vim. In this article, we will explore what registers are, their types, and how to use them efficiently in your Vim workflow.&lt;/p&gt;

&lt;h2&gt;What are Registers?&lt;/h2&gt;
&lt;p&gt;Registers in Vim are like variables that store text or other data. They can hold a variety of information, including text snippets, numbers, and even operations such as macros. Vim provides numerous registers, each identified by a single letter or symbol. These registers can be accessed and manipulated to store, retrieve, and manipulate data during editing sessions.&lt;/p&gt;

&lt;h2&gt;Types of Registers:&lt;/h2&gt;
&lt;p&gt;Vim categorizes registers into different types based on their functionality and scope. Here are the main types of registers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Unnamed Register:&lt;/strong&gt; it is the default register in Vim and is represented by the double quote &lt;code&gt;"&lt;/code&gt; symbol. Whenever you delete or yank (copy) text, it gets stored in the unnamed register by default. The doube quote &lt;code&gt;"&lt;/code&gt;  symbol is also used to invoke the registers in vim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Numbered Registers:&lt;/strong&gt; Vim maintains a history of your recent deletions and yanks in numbered registers, numbered from 0 to 9. Register &lt;code&gt;"0&lt;/code&gt;  contains the most recent yank or delete operation, while registers &lt;code&gt;"1&lt;/code&gt;  through &lt;code&gt;"9&lt;/code&gt;  store older entries, with register &lt;code&gt;"1&lt;/code&gt;  being the second most recent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Named Registers:&lt;/strong&gt; Vim allows you to assign specific names to registers using any letter or symbol (except whitespace) as identifiers. Named registers provide a convenient way to store text snippets or other data for later use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Special Registers:&lt;/strong&gt; Vim includes several special-purpose registers, such as the expression register &lt;code&gt;"=&lt;/code&gt;, which allows you to evaluate expressions, and the clipboard registers &lt;code&gt;"*&lt;/code&gt; and &lt;code&gt;"+&lt;/code&gt;,  which interact with the system clipboard.&lt;/p&gt;

&lt;h3&gt;Expression Register &lt;code&gt;"=&lt;/code&gt; :&lt;/h3&gt;
&lt;p&gt;The expression register, denoted by &lt;code&gt;"=&lt;/code&gt; , allows you to evaluate expressions within Vim. This can be particularly useful for performing calculations or transformations on text. To use the expression register, enter insert mode (press &lt;code&gt;i&lt;/code&gt;  or &lt;code&gt;a&lt;/code&gt; ) and then press &lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;r&lt;/code&gt; followed by  &lt;code&gt;=&lt;/code&gt; followed by the expression you want to evaluate, you will notice that you are writing at the bottom of vim window. Once you've entered the expression,  press &lt;code&gt;Enter&lt;/code&gt; to insert the result of the expression into your document.&lt;/p&gt;
&lt;p&gt;For example, to calculate the square root of 7 and insert the result into your document, you would type in insert mode &lt;code&gt;Ctrl&lt;/code&gt;&amp;nbsp;&lt;code&gt;r&lt;/code&gt; &lt;strong style="color: #000000;"&gt;=sqrt(7)&lt;/strong&gt;  then press enter, this will insert 2.645751 in your document&lt;/p&gt;

&lt;h3&gt;Clipboard Registers ( &lt;code&gt;"*&lt;/code&gt; and &lt;code&gt;"+&lt;/code&gt; ):&lt;/h3&gt;
&lt;p&gt;Vim provides two special registers, &lt;code&gt;"*&lt;/code&gt; , and &lt;code&gt;"+&lt;/code&gt; , which interact with the system clipboard. These registers allow you to copy text from Vim to the system clipboard or paste text from the system clipboard into Vim.&lt;/p&gt;
&lt;p&gt;To copy text to the system clipboard, visually select the desired text in Vim and then type &lt;code&gt;"+y&lt;/code&gt;. This command yanks the selected text into the system clipboard. To paste text from the system clipboard into Vim, type &lt;code&gt;"+p&lt;/code&gt; at the desired insertion point. Similarly, you can use &lt;code&gt;"*y&lt;/code&gt; to copy to the primary selection (usually the same as the clipboard) and &lt;code&gt;"*p&lt;/code&gt; to paste from it.&lt;/p&gt;
&lt;p&gt;Using these clipboard registers, you can seamlessly transfer text between Vim and other applications, enhancing your workflow and productivity.&lt;/p&gt;

&lt;h2&gt;Using Registers in Vim:&lt;/h2&gt;
&lt;p&gt;Now that we've covered the types of registers in Vim, let's explore how to use them effectively:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&amp;#8226; Yanking and Pasting:&lt;/strong&gt; To yank text into a register, use the register name followed by the yank  &lt;code&gt;y&lt;/code&gt; command.  For example, to yank text into register &lt;code&gt;"a&lt;/code&gt; , use &lt;code&gt;"ay&lt;/code&gt; . Similarly, to paste text from a register, use the "p" command. For instance, &lt;code&gt;"ap&lt;/code&gt; pastes the contents of register &lt;code&gt;"a&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;#8226; Deleting and Cutting:&lt;/strong&gt; To delete text into a register, use the "d" command. For example, "da deletes text into register "a". Similarly, to cut text into a register, use the "xa &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;#8226; Accessing Numbered Registers:&lt;/strong&gt; You can access numbered registers by prefixing the command with a number. For example, "2p pastes the contents of the second most recent yank or delete operation.&lt;/p&gt;

&lt;h2&gt;Conclusion:&lt;/h2&gt;
&lt;p&gt;Registers in Vim are powerful tools that allow you to store, retrieve, and manipulate text and data efficiently. By understanding the various types of registers and how to use them effectively, you can streamline your editing workflow and increase productivity in Vim. Experiment with registers in your daily editing tasks to discover their full potential and take your Vim skills to the next level.&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="registers"></category><category term="Vim workflow"></category><category term="Text Editor"></category><category term="productivity"></category><category term="data manipulation"></category><category term="text storage"></category><category term="Vim tips"></category><category term="Vim tricks"></category></entry><entry><title>How To Manage Vim Registers and Retrieve Stored Text Snippets</title><link href="https://mosaid.xyz/articles/how-to-manage-vim-registers-and-retrieve-stored-text-snippets-218.html" rel="alternate"></link><published>2024-04-06T18:51:41+00:00</published><updated>2024-04-06T18:51:41+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2024-04-06:/articles/how-to-manage-vim-registers-and-retrieve-stored-text-snippets-218.html</id><summary type="html">&lt;p&gt;Efficiently manage Vim registers and retrieve stored text snippets effortlessly with custom functions. Streamline your editing process and boost productivity with expert tips.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Vim, a powerful text editor, offers a plethora of features designed to enhance productivity. One such feature is its registers, which allow users to store and recall text snippets for later use. However, as you accumulate more and more text snippets in different registers, it can become challenging to remember where you stored a particular piece of text. In this guide, we'll explore how to efficiently manage Vim registers and effortlessly retrieve stored snippets using a custom Vim function.&lt;/p&gt;

&lt;h2&gt;Understanding Vim Registers&lt;/h2&gt;
&lt;p&gt;Vim provides 26 named registers, labeled from "a" to "z". These registers can store text snippets of varying lengths, making them invaluable for storing commonly used phrases, code snippets, or any other frequently used text. for more details about vim registers, their types and usages check out this article :
&lt;a href="/articles/vim-register-magic-what-they-are-and-how-to-use-them-219/"&gt;Vim Register Magic: What They Are and How to Use Them&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;Managing Vim Registers&lt;/h2&gt;
&lt;p&gt;To effectively manage Vim registers, it's essential to have a system in place for organizing and retrieving stored snippets. One approach is to maintain a mental note of which register contains which text snippet. However, this method becomes impractical as the number of stored snippets increases.&lt;/p&gt;
&lt;p&gt;A more efficient approach is to leverage Vim's scripting capabilities to create a function that displays the contents of all registers containing text snippets. The provided Vim function, &lt;code&gt;OutputRegs()&lt;/code&gt;, achieves precisely that. &lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

function! OutputRegs()
    " Create a new buffer
    enew
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
    " Append content of non-empty registers a to z
    for reg in range(char2nr('a'), char2nr('z'))
        let reg_char = nr2char(reg)
        let reg_content = getreg(reg_char)
        if !empty(reg_content)
            call append(line('$'), [reg_char .":" , reg_content])
            call append(line('$'), "")
        endif
    endfor
    " Move the cursor to the beginning of the buffer
    normal gg
    " Output message
    echo "Contents of registers a to z "
endfunction

:command! Rr     call OutputRegs()
:command! RR     call OutputRegs()

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Let's break down how it works:&lt;/p&gt;

&lt;p&gt;    1.&lt;strong&gt;Creating a New Buffer:&lt;/strong&gt; The function begins by creating a new buffer where the contents of the registers will be displayed. This ensures a clean workspace for viewing the register contents without cluttering the current buffer.&lt;/p&gt;
&lt;p&gt;    2.&lt;strong&gt;Iterating Through Registers:&lt;/strong&gt; Next, the function iterates through each register from "a" to "z" using a for loop. For each register, it retrieves the contents and checks if the register is empty.&lt;/p&gt;
&lt;p&gt;    3.&lt;strong&gt;Appending Content to Buffer:&lt;/strong&gt; If a register contains text, the function appends the register name along with its content to the new buffer. It also adds an empty line for clarity between each register's content.&lt;/p&gt;
&lt;p&gt;    4.&lt;strong&gt;Moving Cursor to the Beginning:&lt;/strong&gt; Once all register contents are appended to the buffer, the function moves the cursor to the beginning of the buffer using the &lt;code&gt;gg&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;    5.&lt;strong&gt;Outputting Message:&lt;/strong&gt; Finally, the function displays a message indicating that the contents of the registers have been successfully displayed.&lt;/p&gt;

&lt;h2&gt;Using the Custom Function&lt;/h2&gt;
&lt;p&gt;To utilize the custom function, simply execute the &lt;code&gt;Rr&lt;/code&gt; or &lt;code&gt;RR&lt;/code&gt; command within Vim. This will trigger the &lt;code&gt;OutputRegs()&lt;/code&gt; function, which will create a new buffer displaying the contents of all non-empty registers from "a" to "z".&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Efficiently managing Vim registers is crucial for maintaining productivity during editing sessions. By leveraging custom functions like &lt;code&gt;OutputRegs()&lt;/code&gt;, users can effortlessly retrieve stored text snippets without the need to remember which register contains which text. Incorporating such tools into your Vim workflow can streamline your editing process and enhance overall efficiency.&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="registers"></category><category term="text snippets"></category><category term="management"></category><category term="productivity"></category><category term="editing"></category><category term="efficiency"></category><category term="custom functions"></category><category term="retrieval"></category><category term="Organization"></category></entry><entry><title>Unlock Vim's Power: Advanced Text Manipulation with Search and Replace</title><link href="https://mosaid.xyz/articles/unlock-vims-power-advanced-text-manipulation-with-search-and-replace-217.html" rel="alternate"></link><published>2024-04-05T17:23:03+00:00</published><updated>2024-04-05T17:23:03+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2024-04-05:/articles/unlock-vims-power-advanced-text-manipulation-with-search-and-replace-217.html</id><summary type="html">&lt;p&gt;Learn essential Vim search and replace commands to streamline text editing. Master basic and advanced substitution techniques, text formatting, and leveraging global commands&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Mastering Vim: Essential Search and Replace Commands&lt;/h1&gt;

&lt;p&gt;Vim, the versatile text editor, is renowned for its efficiency in handling text manipulation tasks. Among its plethora of features, the search and replace functionality stands out as one of the most powerful tools for editing text swiftly. In this article, we'll delve into some essential Vim search and replace commands that can streamline your editing workflow.&lt;/p&gt;

&lt;h2&gt;Basic Substitution with &lt;code&gt;:substitute&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;:substitute&lt;/code&gt; command, often abbreviated as &lt;code&gt;:s&lt;/code&gt;, allows you to search for a pattern within a file and replace it with another pattern. Here are some commonly used variations:&lt;/p&gt;

&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Replace the first match on the current line only:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:s/foo/bar/&lt;/code&gt;&lt;br&gt;
    This command will replace the first occurrence of 'foo' with 'bar' on the current line where the cursor is positioned.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Replace all matches on the current line only:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:s/foo/bar/g&lt;/code&gt;&lt;br&gt;
    The &lt;code&gt;g&lt;/code&gt; flag at the end of the command signifies a global substitution, replacing all occurrences of 'foo' with 'bar' on the current line.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Replace all matches in the entire file:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:%s/foo/bar/g&lt;/code&gt;&lt;br&gt;
    Using &lt;code&gt;%&lt;/code&gt; as a range specifies that the substitution should be applied to the entire file.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Interactive substitution with manual confirmation:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:%s/foo/bar/gc&lt;/code&gt;&lt;br&gt;
    The &lt;code&gt;c&lt;/code&gt; flag prompts Vim to confirm each substitution, allowing you to review and confirm or skip each instance individually.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Substitute within specific HTML tags:&lt;/strong&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;pre class="language-vim" &gt;
&lt;code class="language-vim" &gt;

:%s/&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;/&amp;lt;pre class="language-bash" &amp;gt;\r&amp;lt;code class="language-bash"&amp;gt;\r/

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;This command targets &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags and replaces them with HTML syntax highlighting classes suitable for Bash code.&lt;/p&gt;
&lt;li&gt;&lt;p&gt;
      &lt;strong&gt;Add emphasis to a specific term:&lt;/strong&gt;&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;pre class="language-vim" &gt;
&lt;code class="language-vim" &gt;

:%s/VIM Editor/&amp;lt;strong style="color: #000000;"&amp;gt;VIM Editor&amp;lt;/strong&amp;gt;/g

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Here, the command adds emphasis by wrapping instances of 'VIM Editor' in strong tags with custom styling.&lt;/p&gt;

&lt;h2&gt;Advanced Text Formatting&lt;/h2&gt;

&lt;p&gt;Vim's search and replace functionality extend beyond mere text substitution. Here are a few advanced techniques for text formatting:&lt;/p&gt;

&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Format CSS file:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:%s/[{;}]/&amp;\r/g|norm! =gg&lt;/code&gt;&lt;br&gt;
    This command formats a CSS file by inserting line breaks (&lt;code&gt;\r&lt;/code&gt;) after each opening brace, closing brace, or semicolon, and then applies indentation using the &lt;code&gt;=&lt;/code&gt; command.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Insert line numbers:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:%s/^/\=printf('%-4d', line('.'))/&lt;/code&gt;&lt;br&gt;
    This command inserts line numbers at the beginning of each line, formatted with a width of 4 characters.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Delete empty lines:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:g/^\s*$/d&lt;/code&gt;&lt;br&gt;
    Using the &lt;code&gt;:global&lt;/code&gt; command with a regular expression pattern, this command deletes all lines that contain only whitespace.
  &lt;/p&gt;&lt;/li&gt;

&lt;h2&gt;Leveraging &lt;code&gt;:global&lt;/code&gt; Command&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;:global&lt;/code&gt; command, abbreviated as &lt;code&gt;:g&lt;/code&gt;, allows you to execute commands on lines that match a specific pattern.&lt;/p&gt;

&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Insert line numbers using &lt;code&gt;nl&lt;/code&gt; command:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:%!nl -ba -w1 -s' '&lt;/code&gt;&lt;br&gt;
    This command uses the &lt;code&gt;nl&lt;/code&gt; command-line utility to insert line numbers, with options for formatting width and separator.
  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
    &lt;strong&gt;Insert line numbers directly with Vim script:&lt;/strong&gt;&lt;br&gt;
    &lt;code&gt;:%s/^/\=line('.').' '/&lt;/code&gt;&lt;br&gt;
    By leveraging Vim's scripting capabilities, this command inserts line numbers directly using Vim script, utilizing the &lt;code&gt;line()&lt;/code&gt; function to retrieve the current line number.
  &lt;/p&gt;&lt;/li&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Mastering Vim's search and replace commands can significantly enhance your text editing proficiency. By incorporating these techniques into your workflow, you can efficiently manipulate text and streamline your editing tasks. Whether it's correcting spelling errors, refactoring code, or formatting documents, Vim offers a plethora of tools to expedite your editing process.&lt;/p&gt;

&lt;p&gt;Stay tuned for more Vim tips and tricks in future articles!&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="Text Editor"></category><category term="search and replace"></category><category term="commands"></category><category term="substitution"></category><category term="text formatting"></category><category term="global commands"></category><category term="text manipulation"></category><category term="efficiency"></category><category term="productivity"></category></entry><entry><title>Vim Tips: Simplify Command Execution with OutputCommands Function</title><link href="https://mosaid.xyz/articles/vim-tips-simplify-command-execution-with-outputcommands-function-213.html" rel="alternate"></link><published>2024-04-04T22:25:13+00:00</published><updated>2024-04-04T22:25:13+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2024-04-04:/articles/vim-tips-simplify-command-execution-with-outputcommands-function-213.html</id><summary type="html">&lt;p&gt;Learn how to streamline command execution in Vim with the OutputCommands function. Execute old commands effortlessly within your current file, boosting productivity.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Introduction:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Vim, the venerable text editor, offers a myriad of features and customization options that can enhance productivity for its users. One such feature is the ability to execute old commands quickly within the current file. In this article, we will delve into a custom Vim function that I called OutputCommands, which streamlines the process of executing previous commands effortlessly.&lt;/p&gt;

&lt;h2&gt;The function:&lt;/h2&gt;
&lt;p&gt;Add the followng to your &lt;strong style="color: #000000;"&gt;.vimrc&lt;/strong&gt; file :&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

function! OutputCommands()
  let history = []
  for i in range(histnr(':'), 1, -1)
    call add(history, histget(':', i))
  endfor
  enew
  call append(0, history)
  normal gg
  setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
  "nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :call setreg('+', getline('.'))&amp;lt;CR&amp;gt;
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;CR&amp;gt; :let command = getline('.') \| bnext \| execute command&amp;lt;CR&amp;gt;
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;down&amp;gt; j
  nnoremap &amp;lt;buffer&amp;gt; &amp;lt;up&amp;gt; k
endfunction

:command! Cc     call OutputCommands()
:command! CC     call OutputCommands()

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding OutputCommands:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The OutputCommands function is a custom Vim function designed to simplify the execution of historical commands within the context of the current file being edited. Let's break down how this function works:&lt;/p&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gathering Command History:&lt;/strong&gt;&lt;br&gt;
The function starts by creating an empty list named "history". It then iterates through the command history using a for loop, starting from the most recent command (histnr(':')) and moving backwards. For each iteration, it retrieves the command using histget(':') and adds it to the "history" list.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creating a New Buffer:&lt;/strong&gt;&lt;br&gt;
After collecting the command history, the function opens a new empty buffer using the "enew" command.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Populating the Buffer:&lt;/strong&gt;&lt;br&gt;
The collected command history is appended to the newly created buffer using the "append()" function. This effectively displays the command history in the newly created buffer.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting Buffer Options:&lt;/strong&gt;&lt;br&gt;
Several buffer-local options are set using the "setlocal" command to ensure proper behavior. These options include "buftype=nofile" (indicating that the buffer is not associated with a file), "bufhidden=wipe" (ensuring the buffer is wiped when it's no longer in use), "noswapfile" (disabling swap file creation), and "nowrap" (preventing line wrapping).&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mapping Key Bindings:&lt;/strong&gt;&lt;br&gt;
Key bindings are mapped to facilitate navigation and command execution within the buffer. The "nnoremap" command is used to define mappings for the Enter key ("&amp;lt;CR&amp;gt;"), the Down arrow key ("&amp;lt;down&amp;gt;"), and the Up arrow key ("&amp;lt;up&amp;gt;"). These mappings allow users to navigate through the command history and execute commands easily.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Command Execution:&lt;/strong&gt;&lt;br&gt;
    The most crucial mapping is for the Enter key ("&amp;lt;CR&amp;gt;"). When pressed, it retrieves the command from the current line using "&lt;code&gt;getline('.')&lt;/code&gt;", stores it in a variable named "command", switches to the next buffer using "bnext", and executes the stored command using "&lt;code&gt;execute command&lt;/code&gt;".&lt;/p&gt;&lt;/li&gt;

&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To utilize the OutputCommands function, users can simply invoke it by typing "&lt;code&gt;:Cc&lt;/code&gt;" or "&lt;code&gt;:CC&lt;/code&gt;" in normal mode, depending on their preference. This will open a new buffer containing the command history, allowing them to navigate through previous commands and execute them with ease.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The OutputCommands function offers a convenient way to access and execute historical commands within Vim, streamlining the editing process and enhancing productivity. By understanding its implementation and usage, Vim users can leverage this functionality to make their editing experience more efficient and enjoyable.&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="OutputCommands"></category><category term="command execution"></category><category term="productivity"></category><category term="Text Editor"></category><category term="editing"></category><category term="streamline"></category><category term="efficiency"></category><category term="historical commands"></category><category term="Customization"></category><category term="function"></category><category term="coding workflow"></category></entry><entry><title>Vim old files: A better way</title><link href="https://mosaid.xyz/articles/vim-old-files-a-better-way-203.html" rel="alternate"></link><published>2024-02-29T20:29:15+00:00</published><updated>2024-02-29T20:29:15+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2024-02-29:/articles/vim-old-files-a-better-way-203.html</id><summary type="html">&lt;p&gt;Discover how to effortlessly access old files in Vim with a handy custom function. Simplify your workflow and boost productivity with this streamlined file management solution&lt;/p&gt;</summary><content type="html">&lt;p&gt;Vim, the versatile text editor beloved by developers and writers alike, offers a multitude of features to enhance productivity and streamline workflows. In this article I will present to you a simple yet pwoerful function that simplifies the process of accessing old files within the editor.&lt;/p&gt;

&lt;p&gt;When invoked, this function opens a new buffer containing a list of recently accessed files (&lt;code&gt;v:oldfiles&lt;/code&gt;). This list is invaluable for users who frequently navigate between various documents during their editing sessions, providing a quick and efficient method for recalling and reopening previous documents.&lt;/p&gt;

&lt;h2&gt;The function:&lt;/h2&gt;

&lt;pre class="language-vim" &gt;
&lt;code class="language-vim" &gt;

" this function is a great way to open old files
function! Output()
  enew | 0put =v:oldfiles| nnoremap &lt;buffer&gt; &lt;CR&gt; :e &lt;C-r&gt;=getline('.')&lt;CR&gt;&lt;CR&gt;|normal gg&lt;CR&gt;
  setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
  nnoremap &lt;buffer&gt; &lt;down&gt; j
  nnoremap &lt;buffer&gt; &lt;up&gt; k
endfunction
:command! Mm     call Output()
:command! MM     call Output()

&lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;Usage:&lt;/h2&gt;

&lt;p&gt;To utilize this function, users can simply execute the associated custom commands, such as &lt;code&gt;:Mm&lt;/code&gt; or &lt;code&gt;:MM&lt;/code&gt;, which are defined to call the function.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:command! Mm call Output()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:command! MM call Output()&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;Functionality:&lt;/h2&gt;

&lt;ol&gt;
    &lt;li&gt;&lt;p&gt; &lt;strong&gt;Opening Old Files:&lt;/strong&gt; Upon execution, the function creates a new buffer and populates it with the list of old files. Users can then navigate through this list and press &lt;code&gt;&amp;lt;CR&amp;gt;&lt;/code&gt; (Enter) to open the selected file.&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;&lt;p&gt; &lt;strong&gt;Buffer Configuration:&lt;/strong&gt; The function sets specific buffer-local options (&lt;code&gt;buftype&lt;/code&gt;, &lt;code&gt;bufhidden&lt;/code&gt;, &lt;code&gt;swapfile&lt;/code&gt;, &lt;code&gt;nowrap&lt;/code&gt;) to ensure a seamless experience without interference with the user's editing environment.&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;&lt;p&gt; &lt;strong&gt;Enhanced Navigation:&lt;/strong&gt; For improved usability, the function defines key mappings (&lt;code&gt;&amp;lt;down&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;up&amp;gt;&lt;/code&gt;) within the buffer to facilitate easy navigation through the list of old files.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This custom function provides a convenient solution for managing and accessing old files within Vim. By centralizing the list of previous documents and offering intuitive navigation options, it enhances the editing experience for Vim users, contributing to increased efficiency and productivity.&lt;/p&gt;

&lt;p&gt;Whether you're a seasoned Vim enthusiast or a newcomer exploring its capabilities, incorporating this custom function into your workflow can streamline your file management tasks and elevate your editing experience.&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="old files"></category><category term="file management"></category><category term="custom function"></category><category term="productivity"></category><category term="workflow optimization"></category><category term="Text Editor"></category><category term="code editing"></category><category term="efficiency"></category><category term="navigation"></category><category term="streamlining"></category><category term="productivity tools"></category></entry><entry><title>How to Compile and Run C++, Python, and LaTeX Files in Vim</title><link href="https://mosaid.xyz/articles/how-to-compile-and-run-c-python-and-latex-files-in-vim-141.html" rel="alternate"></link><published>2023-04-03T17:59:49+00:00</published><updated>2023-04-03T17:59:49+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2023-04-03:/articles/how-to-compile-and-run-c-python-and-latex-files-in-vim-141.html</id><summary type="html">&lt;p&gt;Learn how to streamline your programming workflow with these Vim functions for compiling and running C++, Python, and LaTeX files. Boost your productivity and save time with these handy tips and tricks.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hello everyone,&lt;/p&gt;
&lt;p&gt;As a programmer, I have been using Vim as my primary text editor for various programming languages, including C++, Python, and LaTeX. but it gets tiresome to siwtch back and forth between your code in vim and the terminal to compile it. So I started exploring options to compile and run C++ code directly within Vim. And, I found a fantastic solution that I think you would love too.&lt;/p&gt;

&lt;p&gt;To start with, I created a function in Vim called &lt;code&gt;RunCPP&lt;/code&gt; which compiles the current file name using g++ and runs the executable file. This has been a real-time saver for me, as I no longer have to switch between Vim and terminal continuously.&lt;/p&gt;

&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;pre class="language-vim" &gt;
   &lt;code class="language-vim" &gt;


function! RunCPP()
    let s:current_file = expand("%")
    enew|silent execute ".!g++ " . shellescape(s:current_file, 1) . " -o " . shellescape(s:current_file, 1) . ".out &amp;&amp; ./" . shellescape(s:current_file, 1) . ".out"
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
endfunction


&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;To use this function, I created a Vim mapping that calls the function when triggered with a keyboard shortcut, such as the leader key followed by the letter 'c.' This way, I can compile and run C++ code with just a couple of keystrokes.&lt;/p&gt;

&lt;p&gt;And the good news is, similar to C++, I have done the same to compile and run Python scripts and generate PDFs from LaTeX files within Vim itself.&lt;/p&gt;

&lt;pre class="language-vim" &gt;
   &lt;code class="language-vim" &gt;

function! RunPython()
    let s:current_file = expand("%")
    enew|silent execute ".!python " . shellescape(s:current_file, 1)
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
endfunction

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;p&gt;Now, moving on to LaTeX, I have two Vim functions: &lt;code&gt;RunTex&lt;/code&gt; and &lt;code&gt;ViewTex&lt;/code&gt;. The "RunTex" function compiles the current LaTeX file using the "xelatex" command, while the "ViewTex" function generates a PDF from the LaTeX file using the "okular" command and opens it in a new buffer within Vim. Both these functions are mapped to specific keyboard shortcuts for easy access.&lt;/p&gt;

&lt;pre class="language-vim" &gt;
   &lt;code class="language-vim" &gt;


function! RunTex()
    let s:current_file = expand("%")
    enew|silent execute ".!xelatex " . shellescape(s:current_file, 1)
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
    normal! G
    nnoremap &lt;buffer&gt; &lt;CR&gt; &lt;C-c&gt;:bd!&lt;CR&gt;
endfunction

function! ViewTex()
    let s:current_file = expand("%")
    let s:pdf_file = substitute(s:current_file, '\.tex$', '.pdf', '')
    enew
    silent execute "!nohup okular  " . shellescape(s:pdf_file, 1) . "  &amp;lt;/dev/null &amp;gt;/dev/null 2&amp;gt;&amp;1 &amp; "
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
    nnoremap &lt;buffer&gt; &lt;CR&gt; &lt;C-c&gt;:bd!&lt;CR&gt;
endfunction



&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;to use these functions, just create these files in  the &lt;code&gt;~/.vim/after/ftplugin&lt;/code&gt; directory. and put them in the files accordingly&lt;/p&gt;

&lt;pre class="language-vim" &gt;
   &lt;code class="language-vim" &gt;

~/.vim/after/ftplugin/python.vim
~/.vim/after/ftplugin/cpp.vim
~/.vim/after/ftplugin/tex.vim

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;p&gt;By setting up these mappings, I have been able to streamline my workflow and avoid switching between Vim and the terminal. These functions and mappings save time, making it easier to focus on programming without any distractions. those ftplugin files contain specific settings for each file type so feel free to add your own configurations to them&lt;/p&gt;

&lt;p&gt;I hope this tutorial helps you in running C++, Python and LaTeX files within Vim as it did for me. Give it a try and let me know how it goes!&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="programming workflow"></category><category term="c++"></category><category term="python"></category><category term="latex"></category><category term="compile"></category><category term="run"></category><category term="productivity"></category><category term="time-saving"></category><category term="tips and tricks"></category></entry><entry><title>Vim Editor Techniques for Effortless HTML File Editing</title><link href="https://mosaid.xyz/articles/vim-editor-techniques-for-effortless-html-file-editing-129.html" rel="alternate"></link><published>2023-03-08T15:31:52+00:00</published><updated>2023-03-08T15:31:52+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2023-03-08:/articles/vim-editor-techniques-for-effortless-html-file-editing-129.html</id><summary type="html">&lt;p&gt;Learn how to create HTML files in just a few keyboard strokes using Vim text editor. This article explains how to use Vim to create simple HTML files and define keyboard mappings for quick HTML file editing&lt;/p&gt;</summary><content type="html">&lt;p&gt;Vim is a great text editor once you overcome its steep learning curve, and understand how to configure it to your liking. You will find out how flexible and smooth it is to work with it. Often times I find myself writing my articles in simple HTML files format, usually just paragraphs and headers and The occasional code or list. In this article I will show you how in just a few keyboard strokes, I can create my HTML files.&lt;/p&gt;

&lt;h2&gt;Making a paragraph&lt;/h2&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;p&gt;All The following actions can be performed by just two keyboard key combinations: 1. Select The entire paragraph, 2 go to The beginning and insert The &amp;lt;p&amp;gt; tag, 3. Go to The end of The paragraph and add &amp;lt;/p&amp;gt; tag. &lt;br&gt; In any other editor this can become tiresome especially if repeated. But with vim: in &lt;strong style="color: #000000;"&gt;NORMAL&lt;/strong&gt; just type V (&lt;strong style="color: #000000;"&gt;shift v&lt;/strong&gt;) to select The entire line (our paragraph in HTML can be written in a single line if vim is correctly configured to line wrap). Then  I type &lt;strong style="color: #000000;"&gt;;pp&lt;/strong&gt;
    and that's it. The semi colon ";" is my defined leader in my &lt;strong style="color: #000000;"&gt;.vimrc&lt;/strong&gt; file. The &lt;strong style="color: #000000;"&gt;;pp&lt;/strong&gt;
is a mapped key combination I created using the following line:&lt;/p&gt;
&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

vnoremap &amp;lt;leader&amp;gt;pp y&amp;lt;Esc&amp;gt;`&amp;gt;a&amp;lt;/p&amp;gt;&amp;lt;Esc&amp;gt;`&amp;lt;i&amp;lt;p&amp;gt;&amp;lt;Esc&amp;gt;

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;In the above line we use &lt;strong style="color: #000000;"&gt;vnoremap&lt;/strong&gt;  to define the keyboard mapping in the &lt;strong style="color: #000000;"&gt;VISUAL&lt;/strong&gt; mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;        &lt;strong style="color: #000000;"&gt;&amp;lt;leader&amp;gt;pp&lt;/strong&gt;: the mapping.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;        &lt;strong style="color: #000000;"&gt;y&amp;lt;Esc&amp;gt;&lt;/strong&gt; : yank (copy) the visually selected text, even though we don't need it, it serves a purpose. This selected text will be known to vim by &amp;gt; and &amp;lt; symbols.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;        &lt;strong style="color: #000000;"&gt;&amp;gt;a&lt;/strong&gt;  is an instruction to insert at the end of the select text, what follows is the inserted text, in our case the end of paragraph tag &amp;lt;/p&amp;gt;  then &lt;strong style="color: #000000;"&gt;&amp;lt;Esc&amp;gt;&lt;/strong&gt; to go back to NORMAL mode.&lt;/p&gt;
&lt;/li&gt;
    &lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;li&gt;
&lt;p&gt;        &lt;strong style="color: #000000;"&gt;&amp;lt;i&amp;lt;p&amp;gt;&amp;lt;Esc&amp;gt;&lt;/strong&gt;: same as before, we insert at the beginning of the selected text ( ie : &lt;strong style="color: #000000;"&gt;&amp;lt;&lt;/strong&gt; and the inserted text is the paragraph tag &amp;lt;p&amp;gt;. Again this inserted text can be any string of text. and finally we &amp;lt;Esc&amp;gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is just one of the many small things I use when editing my files. As always here is my entire &lt;span class="myspan"&gt;~/.vim/ftplugin/html.vim&lt;/span&gt; file in which I define all the mappings associated with HTML files&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

vnoremap &amp;lt;leader&amp;gt;ss y&amp;lt;Esc&amp;gt;`&amp;gt;a&amp;lt;/strong&amp;gt;&amp;lt;Esc&amp;gt;`&amp;lt;i&amp;lt;strong style="color: #000000;"&amp;gt;&amp;lt;Esc&amp;gt;
vnoremap &amp;lt;leader&amp;gt;pp y&amp;lt;Esc&amp;gt;`&amp;gt;a&amp;lt;/p&amp;gt;&amp;lt;Esc&amp;gt;`&amp;lt;i&amp;lt;p&amp;gt;&amp;lt;Esc&amp;gt;
vnoremap &amp;lt;leader&amp;gt;hh y&amp;lt;Esc&amp;gt;`&amp;gt;a&amp;lt;/h2&amp;gt;&amp;lt;Esc&amp;gt;`&amp;lt;i&amp;lt;h2&amp;gt;&amp;lt;Esc&amp;gt;

:set wrap
:set linebreak
:set textwidth=0
:set wrapmargin=0

inoremap ;bb &amp;lt;ESC&amp;gt;:0r ~/.vim/mysnippets/php/b1&amp;lt;CR&amp;gt;
inoremap ;flex &amp;lt;ESC&amp;gt;:$r ~/.vim/mysnippets/html/flex&amp;lt;CR&amp;gt;
inoremap ;xx &amp;lt;ESC&amp;gt;:r ~/.vim/mysnippets/html/code&amp;lt;CR&amp;gt;
inoremap ;ff &amp;lt;ESC&amp;gt;:r ~/.vim/mysnippets/html/figure&amp;lt;CR&amp;gt;
inoremap ;b &amp;lt;b&amp;gt;&amp;lt;/b&amp;gt;&amp;lt;Space&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;FbT&amp;gt;i
inoremap ;it &amp;lt;em&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;Space&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;FeT&amp;gt;i
inoremap ;1 &amp;lt;h1&amp;gt;&amp;lt;/h1&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;2kf&amp;lt;i
                inoremap ;2 &amp;lt;h2&amp;gt;&amp;lt;/h2&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;2kf&amp;lt;i
                                inoremap ;3 &amp;lt;h3&amp;gt;&amp;lt;/h3&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;2kf&amp;lt;i
                                                inoremap ;p &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;02kf&amp;gt;a
inoremap ;a &amp;lt;a&amp;lt;Space&amp;gt;href=""&amp;gt;&amp;lt;++&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;Space&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;14hi
inoremap ;e &amp;lt;a&amp;lt;Space&amp;gt;target="_blank"&amp;lt;Space&amp;gt;href=""&amp;gt;&amp;lt;++&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;Space&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;14hi
inoremap ;ul &amp;lt;ul&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/ul&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;03kf&amp;lt;i
                 inoremap ;li &amp;lt;Esc&amp;gt;o&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;Esc&amp;gt;F&amp;gt;a
inoremap ;ol &amp;lt;ol&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;03kf&amp;lt;i
                 inoremap ;im &amp;lt;img src="" alt="&amp;lt;++&amp;gt;"&amp;gt;&amp;lt;++&amp;gt;&amp;lt;esc&amp;gt;Fcf"a
inoremap ;td &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;Fdcit
inoremap ;tr &amp;lt;tr&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;kf&amp;lt;i
                 inoremap ;th &amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;++&amp;gt;&amp;lt;Esc&amp;gt;Fhcit
inoremap ;tab &amp;lt;table&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/table&amp;gt;&amp;lt;Esc&amp;gt;O
inoremap ;gr &amp;lt;font color="green"&amp;gt;&amp;lt;/font&amp;gt;&amp;lt;Esc&amp;gt;F&amp;gt;a
inoremap ;rd &amp;lt;font color="red"&amp;gt;&amp;lt;/font&amp;gt;&amp;lt;Esc&amp;gt;F&amp;gt;a
inoremap ;yl &amp;lt;font color="yellow"&amp;gt;&amp;lt;/font&amp;gt;&amp;lt;Esc&amp;gt;F&amp;gt;a
inoremap ;dt &amp;lt;dt&amp;gt;&amp;lt;/dt&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;dd&amp;gt;&amp;lt;++&amp;gt;&amp;lt;/dd&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;esc&amp;gt;2kcit
inoremap ;dl &amp;lt;dl&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/dl&amp;gt;&amp;lt;enter&amp;gt;&amp;lt;enter&amp;gt;&amp;lt;++&amp;gt;&amp;lt;esc&amp;gt;3kcc
inoremap &amp;&amp;lt;space&amp;gt; &amp;amp;&amp;lt;space&amp;gt;
inoremap á &amp;aacute;
inoremap é &amp;eacute;
inoremap í &amp;iacute;
inoremap ó &amp;oacute;
inoremap ú &amp;uacute;
inoremap ä &amp;auml;
inoremap ë &amp;euml;
inoremap ï &amp;iuml;
inoremap ö &amp;ouml;
inoremap ü &amp;uuml;
inoremap ã &amp;atilde;
inoremap ẽ &amp;etilde;
inoremap ĩ &amp;itilde;
inoremap õ &amp;otilde;
inoremap ũ &amp;utilde;
inoremap ñ &amp;ntilde;
inoremap à &amp;agrave;
inoremap è &amp;egrave;
inoremap ì &amp;igrave;
inoremap ò &amp;ograve;
inoremap ù &amp;ugrave;
" vim: set ts=8 sts=4 sw=4 expandtab :

&lt;/code&gt;
&lt;/pre&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;There are some mappings involving some small files in the "mysnippets" folder. Those are files containing small code snippets as well, like the "figure" file which contains a figure tag with its img and caption tags&lt;/p&gt;
&lt;p&gt;Vim editing becomes an enjoyable experience once you combine it with your knowledge of other things like regular expressions. it is in this area where vim excels.&lt;/p&gt;

&lt;p&gt;This article was formatted with html tags in under 2 minutes using just a few repeated commands. It could have been automated further using vim macros. and a bonus mapping that allows me to surround code snippets as shown in the video bellow: &lt;/p&gt;

&lt;pre class="language-bash" &gt;
   &lt;code class="language-bash" &gt;

vnoremap &amp;lt;leader&amp;gt;cc x&amp;lt;Esc&amp;gt;i&amp;lt;pre class="language-bash" &amp;gt;&amp;lt;Enter&amp;gt;   &amp;lt;code class="language-bash" &amp;gt;&amp;lt;Esc&amp;gt;po    &amp;lt;/code&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;br&amp;gt;&amp;lt;Esc&amp;gt;

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;!--
&lt;div class="embed-responsive embed-responsive-16by9"&gt;
  &lt;video class="embed-responsive-item" controls&gt;
    &lt;source src="/static/videos/vim-html-editing.mp4" type="video/mp4"&gt;
    Your browser does not support the video tag.
  &lt;/video&gt;
&lt;/div&gt;
--&gt;

&lt;div class="embed-responsive embed-responsive-16by9"&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/IZT9mPOu7uY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="Text Editor"></category><category term="HTML files"></category><category term="keyboard strokes"></category><category term="keyboard mappings"></category><category term="HTML tags"></category><category term="yank"></category><category term="copy"></category><category term="visually selected text"></category><category term="leader"></category><category term="normal mode"></category><category term="visual mode"></category><category term="line wrap"></category><category term="configuration"></category></entry><entry><title>Vim Text Editor tricks : Spell Checking</title><link href="https://mosaid.xyz/articles/vim-text-editor-tricks-spell-checking-126.html" rel="alternate"></link><published>2023-03-07T10:31:38+00:00</published><updated>2023-03-07T10:31:38+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2023-03-07:/articles/vim-text-editor-tricks-spell-checking-126.html</id><summary type="html">&lt;p&gt;Learn how to set up spell checking in Vim for French text using the built-in spell checking capabilities and external dictionaries. Follow the steps outlined in this article to ensure that your French text is free from spelling mistakes and improve your writing skills&lt;/p&gt;</summary><content type="html">&lt;p&gt;vim is a great text editor. I use it most of the time, And most of the time I write in English. But sometimes I need to write in french. And I had to always keep reading And re-reading my text document to check for spelling mistakes. This is when I got to search if vim can deal with this. And it does&lt;/p&gt;
&lt;p&gt;I found out that Vim comes with built-in spell checking capabilities that can be customized to meet specific language requirements. In this article, we will focus on spell checking in Vim for French text. But it should be the same for any other language&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;p&gt;Before we begin, it is important to note that spell checking in Vim relies on external dictionaries. In the case of French spell checking, we will use the French dictionary provided by the system's spell checking library. On most Linux systems, this library is called &lt;strong style="color: #000000;"&gt;Hunspell&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To enable spell checking in Vim for French text, we need to follow these steps:&lt;/p&gt;

&lt;p&gt;Ensure that &lt;strong style="color: #000000;"&gt;Hunspell&lt;/strong&gt; is installed on your system. On most Linux distributions, Hunspell can be installed using the package manager. For example, on Ubuntu, you can install Hunspell by running the following command in the terminal:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

sudo apt install hunspell

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Set the spell checking language in Vim to French. This can be done by adding the following line to your &lt;strong style="color: #000000;"&gt;.vimrc&lt;/strong&gt; file:&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

set spelllang=fr
" or
set spelllang+=fr

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Enable spell checking in Vim by adding the following line to your &lt;strong style="color: #000000;"&gt;.vimrc&lt;/strong&gt; file:&lt;/p&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

set spell

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;With these settings in place, Vim will highlight any spelling mistakes in your text as you type. To correct a spelling mistake, move the cursor to the misspelled word And type the &lt;strong style="color: #000000;"&gt;z=&lt;/strong&gt; command. Vim will display a list of suggested corrections, And you can choose the correct spelling by typing the corresponding number.&lt;/p&gt;

&lt;p&gt;If you encounter a word that is not recognized by the French dictionary, you can add it to Vim's custom dictionary. To do this, move the cursor to the word And type the &lt;strong style="color: #000000;"&gt;zg&lt;/strong&gt; command. Vim will add the word to the custom dictionary, And it will no longer be flagged as a spelling mistake.&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;p&gt;In addition to the &lt;strong style="color: #000000;"&gt;z=&lt;/strong&gt; And &lt;strong style="color: #000000;"&gt;zg&lt;/strong&gt; commands, Vim provides other useful commands for spell checking. For example, you can use the &lt;strong style="color: #000000;"&gt;]s&lt;/strong&gt; command to jump to the next spelling mistake, or the &lt;strong style="color: #000000;"&gt;[s&lt;/strong&gt; command to jump to the previous spelling mistake.&lt;/p&gt;

&lt;h2&gt;Here is the relevant parts of my vimrc file about the spell checking&lt;/h2&gt;

&lt;pre class="language-vim" &gt;
    &lt;code class="language-vim" &gt;

" Switch to English - function
function! EngType()
  set spell
  set spelllang=en
endfunction

function! FrType()
  set spell
  set spelllang=fr
endfunction

" highlighting colors
hi SpellBad cterm=underline ctermfg=black ctermbg=red
hi SpellLocal cterm=underline ctermfg=black ctermbg=red
hi SpellRare cterm=underline ctermfg=black ctermbg=red
hi SpellCap cterm=underline ctermfg=black ctermbg=green

&lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;Here is an example of the highlighting&lt;/h2&gt;

&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;figure&gt;
    &lt;img src="/theme/images/articles/images/123-img-1.png" alt="Spell checking in vim text editor" style="max-width:100%;height:auto;" &gt;
    &lt;figcaption&gt;Spell checking in vim text editor&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In conclusion, spell checking in Vim for French text is easy to set up And use. By following the steps outlined in this article, you can ensure that your French text is free from spelling mistakes, And you can improve your writing skills in the process.&lt;/p&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="Text Editor"></category><category term="spell checking"></category><category term="French"></category><category term="Hunspell"></category><category term="dictionary"></category><category term=".vimrc file"></category><category term="z="></category><category term="zg"></category><category term="commands"></category><category term="highlighting"></category><category term="writing skills"></category></entry><entry><title>Vim Text Editor: ftplugin and snippets</title><link href="https://mosaid.xyz/articles/vim-text-editor-ftplugin-and-snippets-81.html" rel="alternate"></link><published>2023-02-12T22:32:44+00:00</published><updated>2023-02-12T22:32:44+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2023-02-12:/articles/vim-text-editor-ftplugin-and-snippets-81.html</id><summary type="html">&lt;p&gt;Vim is a highly efficient and versatile text editor that has been around for over two decades. It is widely used by developers, sysadmins, and power users for its unique features and capabilities. Vim offers a rich set of features for editing text, programming, and automation that make it a favorite among users who want to get more done with fewer keystrokes.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Vim is a highly efficient and versatile text editor that has been around for over two decades. It is widely used by developers, sysadmins, and power users for its unique features and capabilities. Vim offers a rich set of features for editing text, programming, and automation that make it a favorite among users who want to get more done with fewer keystrokes.&lt;/p&gt;
&lt;h2&gt;History of Vim&lt;/h2&gt;
&lt;p&gt;Vim was originally created as a clone of the original Vi text editor, which was written in the 1970s. Since its initial release, Vim has undergone many improvements and modifications, making it a highly advanced text editor that is widely used today. Vim was written by Bram Moolenaar in 1991 and is free and open-source software.&lt;/p&gt;
&lt;h2&gt;Features of Vim&lt;/h2&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Modal Editing: Vim uses a modal editing system, which allows users to switch between different modes, such as normal mode, insert mode, and visual mode. This feature provides users with more control over their text and helps them to perform editing tasks more efficiently.&lt;/li&gt;
&lt;li&gt;Command Line Interface: Vim offers a powerful command line interface that makes it easy for users to perform complex editing tasks with just a few keystrokes. This interface allows users to automate editing tasks, making it a great tool for repetitive or complex editing tasks.&lt;/li&gt;
&lt;li&gt;Customizability: Vim is highly customizable, allowing users to tailor the editor to their specific needs. Vim users can create custom macros, key bindings, and plugins that make the editor work exactly the way they want it to.&lt;/li&gt;
&lt;li&gt;Support for Multiple Languages: Vim supports a wide range of programming languages, making it an ideal choice for developers who need to work with multiple languages in a single text editor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;here are some of my personal configs for vim&lt;/h2&gt;
&lt;h3&gt;1. ftplugins&lt;/h3&gt;
&lt;p&gt;create a directory named "ftplugin" inside your .vim directory and create the following files in it . ftplugin stands for file type plugin, ie a set of commands that will be available for specific filetypes. vim reads the contents of this directory on startup so no need to config anything else&lt;/p&gt;
&lt;h3&gt;sh.vim&lt;/h3&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;pre class="language-vim"   &gt;
    &lt;code class="language-vim"  &gt;

" filename sh.vim
" the following function will run the shell script code in another
" buffer, I use it a lot when making tutorials about shell scripting
" as the next line after endfunction is a keybinding to run the script
" &amp;lt;tab&amp;gt; takes me to and back from the script and its output

" the last line is to &amp;lt;ESC&amp;gt; escape to NORMAL mode, ":0r"
" means read the file given by its fullpath into the first line of the script file
" the file simply contains the shabang that is written on top of bash scripts
" #!/bin/bash

function! RunBash()
    let s:current_file = expand("%")
    enew|silent execute ".!bash " . shellescape(s:current_file, 1)
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
endfunction

nnoremap &amp;lt;Leader&amp;gt;c :call RunBash()&amp;lt;CR&amp;gt;
inoremap ;sh &amp;lt;ESC&amp;gt;:0r ~/.vim/mysnippets/bash/shabang&amp;lt;CR&amp;gt;

&lt;/code&gt;
&lt;/pre&gt;

&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;h3&gt;cpp.vim&lt;/h3&gt;
&lt;pre class="language-vim"   &gt;
    &lt;code class="language-vim"  &gt;

" the same principle
" this one is for cpp files
" the function will compile the code, execute and show the output, or errors


function! RunCPP()
    let s:current_file = expand("%")
    enew|silent execute ".!g++ " . shellescape(s:current_file, 1) . " -o " . shellescape(s:current_file, 1) . ".out &amp;&amp; ./" . shellescape(s:current_file, 1) . ".out"
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
endfunction


nnoremap &amp;lt;Leader&amp;gt;c :call RunCPP()&amp;lt;CR&amp;gt;

" the following keybindings work in insert mode, for example typing ;io
" will insert the directive #include&amp;lt;iostream&amp;gt; and go to the next line
" the code snippets are the entire main function

inoremap ;io #include&amp;lt;iostream&amp;gt;&amp;lt;enter&amp;gt;
inoremap ;st using namespace std;&amp;lt;enter&amp;gt;&amp;lt;enter&amp;gt;
inoremap ;li #include&amp;lt;stdlib.h&amp;gt;&amp;lt;enter&amp;gt;
inoremap ;mm #include&amp;lt;math.h&amp;gt;&amp;lt;enter&amp;gt;
inoremap ;ma &amp;lt;ESC&amp;gt;:0r ~/.vim/mysnippets/cpp/main&amp;lt;CR&amp;gt;
inoremap ;maa &amp;lt;ESC&amp;gt;:0r ~/.vim/mysnippets/cpp/main1&amp;lt;CR&amp;gt;
inoremap ;mz &amp;lt;ESC&amp;gt;:0r ~/.vim/mysnippets/cpp/main1&amp;lt;CR&amp;gt;


&lt;/code&gt;
&lt;/pre&gt;

&lt;figure&gt;
  &lt;img src="/theme/images/articles/images/81-img-1.gif" alt="an example of using c++ snippets and compiling and running c++ code from vim" style="max-width:100%;height:auto;" &gt;
  &lt;figcaption&gt;an example of using c++ snippets and compiling and running c++ code from vim &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Vim is a powerful and versatile text editor that is widely used by developers, sysadmins, and power users for its unique features and capabilities. Whether you are a beginner or an experienced user, Vim offers something for everyone. So, if you're looking for a text editor that can help you get more done with fewer keystrokes, give Vim a try!&lt;/p&gt;
&lt;div&gt;
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category></entry><entry><title>How can Python help you edit your files even faster with vim</title><link href="https://mosaid.xyz/articles/how-can-python-help-you-edit-your-files-even-faster-with-vim-13.html" rel="alternate"></link><published>2020-07-19T23:41:13+00:00</published><updated>2020-07-19T23:41:13+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2020-07-19:/articles/how-can-python-help-you-edit-your-files-even-faster-with-vim-13.html</id><summary type="html">&lt;p&gt;Often times when we are editing our files, we need a faster way to produce hundreds, even thousands of lines with repetitive content or lines that have some variables that change on every line and it would be tedious to write them manually. Here comes Python.&lt;/p&gt;</summary><content type="html">&lt;p&gt;
&lt;p&gt;Often times when we are editing our files, we need a faster way to produce hundreds, even
thousands of lines with repetitive content or lines that have some variables
that change on every line and it would be tedious to write them manually. Here
comes Python.
&lt;/p&gt;

&lt;p&gt; lets say I wanted to tag 29 articles in my website with the same 8 tags;
that's 232 instructions. I
could go about them one by one and add 8 tags to each one but that would be
extremely tiresome and quite frankly boring and it would take me half a day to
complete. so instead I'll make text file lets
call it &lt;strong&gt;mytable.sql&lt;/strong&gt; and in it I'll put the mysql instructions
to add those tags. First we write this line:&lt;/p&gt;


&lt;pre class=" language-sql" style="background-color:black;"&gt;
    &lt;code class=" language-sql" style="background-color:black;" &gt;
insert into mytable (id,tagid) values
    &lt;/code&gt;
&lt;/pre&gt;


&lt;div class="w-100" &gt; 
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;p&gt;And then in another vim buffer we write in a file lets call it
&lt;strong&gt;a.py&lt;/strong&gt; these 3 lines of code:&lt;/p&gt;


&lt;pre class=" language-python" style="background-color:black;"&gt;
    &lt;code class=" language-python" style="background-color:black;" &gt;
for i in range(1,30):
    for j in range(1,9):
        print("(",i,",",j,"),")
    &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;All I have to do now is to type ";c" with ";" being my vim leader in my vimrc
file and ";c" is a keymap to execute the Python code above to produce the 232
lines I need to add my tags Once Python code was executed I'll copy the output
from the buffer (an easy task with vim, just another keymap in my vimrc file)
    and copy it to my first file. The result will be like this:&lt;/p&gt;


&lt;pre class=" language-sql" style="background-color:black;max-height:300px;overflow-y:scroll;"&gt;
    &lt;code class=" language-sql" style="background-color:black;" &gt;
insert into mytable (id,tagid) values
( 1 , 1 ),
( 1 , 2 ),
( 1 , 3 ),
( 1 , 4 ),
( 1 , 5 ),
( 1 , 6 ),
( 1 , 7 ),
( 1 , 8 ),
( 2 , 1 ),
( 2 , 2 ),
( 2 , 3 ),
( 2 , 4 ),
( 2 , 5 ),
( 2 , 6 ),
( 2 , 7 ),
( 2 , 8 ),
( 3 , 1 ),
( 3 , 2 ),
( 3 , 3 ),
( 3 , 4 ),
( 3 , 5 ),
( 3 , 6 ),
( 3 , 7 ),
( 3 , 8 ),
( 4 , 1 ),
( 4 , 2 ),
( 4 , 3 ),
( 4 , 4 ),
( 4 , 5 ),
( 4 , 6 ),
( 4 , 7 ),
( 4 , 8 ),
( 5 , 1 ),
( 5 , 2 ),
( 5 , 3 ),
( 5 , 4 ),
( 5 , 5 ),
( 5 , 6 ),
( 5 , 7 ),
( 5 , 8 ),
( 6 , 1 ),
( 6 , 2 ),
( 6 , 3 ),
( 6 , 4 ),
( 6 , 5 ),
( 6 , 6 ),
( 6 , 7 ),
( 6 , 8 ),
( 7 , 1 ),
( 7 , 2 ),
( 7 , 3 ),
( 7 , 4 ),
( 7 , 5 ),
( 7 , 6 ),
( 7 , 7 ),
( 7 , 8 ),
( 8 , 1 ),
( 8 , 2 ),
( 8 , 3 ),
( 8 , 4 ),
( 8 , 5 ),
( 8 , 6 ),
( 8 , 7 ),
( 8 , 8 ),
( 9 , 1 ),
( 9 , 2 ),
( 9 , 3 ),
( 9 , 4 ),
( 9 , 5 ),
( 9 , 6 ),
( 9 , 7 ),
( 9 , 8 ),
( 10 , 1 ),
( 10 , 2 ),
( 10 , 3 ),
( 10 , 4 ),
( 10 , 5 ),
( 10 , 6 ),
( 10 , 7 ),
( 10 , 8 ),
( 11 , 1 ),
( 11 , 2 ),
( 11 , 3 ),
( 11 , 4 ),
( 11 , 5 ),
( 11 , 6 ),
( 11 , 7 ),
( 11 , 8 ),
( 12 , 1 ),
( 12 , 2 ),
( 12 , 3 ),
( 12 , 4 ),
( 12 , 5 ),
( 12 , 6 ),
( 12 , 7 ),
( 12 , 8 ),
( 13 , 1 ),
( 13 , 2 ),
( 13 , 3 ),
( 13 , 4 ),
( 13 , 5 ),
( 13 , 6 ),
( 13 , 7 ),
( 13 , 8 ),
( 14 , 1 ),
( 14 , 2 ),
( 14 , 3 ),
( 14 , 4 ),
( 14 , 5 ),
( 14 , 6 ),
( 14 , 7 ),
( 14 , 8 ),
( 15 , 1 ),
( 15 , 2 ),
( 15 , 3 ),
( 15 , 4 ),
( 15 , 5 ),
( 15 , 6 ),
( 15 , 7 ),
( 15 , 8 ),
( 16 , 1 ),
( 16 , 2 ),
( 16 , 3 ),
( 16 , 4 ),
( 16 , 5 ),
( 16 , 6 ),
( 16 , 7 ),
( 16 , 8 ),
( 17 , 1 ),
( 17 , 2 ),
( 17 , 3 ),
( 17 , 4 ),
( 17 , 5 ),
( 17 , 6 ),
( 17 , 7 ),
( 17 , 8 ),
( 18 , 1 ),
( 18 , 2 ),
( 18 , 3 ),
( 18 , 4 ),
( 18 , 5 ),
( 18 , 6 ),
( 18 , 7 ),
( 18 , 8 ),
( 19 , 1 ),
( 19 , 2 ),
( 19 , 3 ),
( 19 , 4 ),
( 19 , 5 ),
( 19 , 6 ),
( 19 , 7 ),
( 19 , 8 ),
( 20 , 1 ),
( 20 , 2 ),
( 20 , 3 ),
( 20 , 4 ),
( 20 , 5 ),
( 20 , 6 ),
( 20 , 7 ),
( 20 , 8 ),
( 21 , 1 ),
( 21 , 2 ),
( 21 , 3 ),
( 21 , 4 ),
( 21 , 5 ),
( 21 , 6 ),
( 21 , 7 ),
( 21 , 8 ),
( 22 , 1 ),
( 22 , 2 ),
( 22 , 3 ),
( 22 , 4 ),
( 22 , 5 ),
( 22 , 6 ),
( 22 , 7 ),
( 22 , 8 ),
( 23 , 1 ),
( 23 , 2 ),
( 23 , 3 ),
( 23 , 4 ),
( 23 , 5 ),
( 23 , 6 ),
( 23 , 7 ),
( 23 , 8 ),
( 24 , 1 ),
( 24 , 2 ),
( 24 , 3 ),
( 24 , 4 ),
( 24 , 5 ),
( 24 , 6 ),
( 24 , 7 ),
( 24 , 8 ),
( 25 , 1 ),
( 25 , 2 ),
( 25 , 3 ),
( 25 , 4 ),
( 25 , 5 ),
( 25 , 6 ),
( 25 , 7 ),
( 25 , 8 ),
( 26 , 1 ),
( 26 , 2 ),
( 26 , 3 ),
( 26 , 4 ),
( 26 , 5 ),
( 26 , 6 ),
( 26 , 7 ),
( 26 , 8 ),
( 27 , 1 ),
( 27 , 2 ),
( 27 , 3 ),
( 27 , 4 ),
( 27 , 5 ),
( 27 , 6 ),
( 27 , 7 ),
( 27 , 8 ),
( 28 , 1 ),
( 28 , 2 ),
( 28 , 3 ),
( 28 , 4 ),
( 28 , 5 ),
( 28 , 6 ),
( 28 , 7 ),
( 28 , 8 ),
( 29 , 1 ),
( 29 , 2 ),
( 29 , 3 ),
( 29 , 4 ),
( 29 , 5 ),
( 29 , 6 ),
( 29 , 7 ),
( 29 , 8 );
    &lt;/code&gt;
&lt;/pre&gt;
&lt;p&gt;And now &lt;strong&gt;mytable.sql&lt;/strong&gt; is ready to be executed by mysql server
and the tags to be addedd into mytable. I should point out that the numbers 1 to 29
are the unique ids of my articles and 1 to 8 are the unique ids of my tags&lt;/p&gt;

&lt;div&gt; 
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;p&gt;Now its time to show you the relevant part of my vimrc file and the vim magic
to do this:&lt;br&gt;
we go to the &lt;strong&gt;~/.vim/ftplugin&lt;/strong&gt; folder or create it if it does not exist.&lt;br&gt;
we create a file: &lt;strong&gt;python.vim&lt;/strong&gt;&lt;br&gt;
and we put this vim code in it:&lt;/p&gt;


&lt;pre class=" language-vim" style="background-color:black;"&gt;
    &lt;code class=" language-vim" style="background-color:black;" &gt;
"file : ~/.vim/ftplugin/python.vim

function! RunPython()
    let s:current_file = expand("%")
    enew|silent execute ".!python " . shellescape(s:current_file, 1)
    setlocal buftype=nofile bufhidden=wipe noswapfile nowrap
endfunction

setlocal foldmethod=indent
setlocal foldnestmax=1
setlocal colorcolumn=0
setlocal completeopt-=preview

nnoremap &amp;lt;Leader&amp;gt;c :call RunPython()&amp;lt;CR&amp;gt;
    &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;And that's it. and you can do the same with all the scripting languages like
shell scripting .&lt;/p&gt;


&lt;div class="w-100" &gt; 
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;



&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="programming"></category><category term="vim"></category><category term="python"></category><category term="Text Editor"></category></entry><entry><title>Advanced vim macros: Vim recursive macros</title><link href="https://mosaid.xyz/articles/advanced-vim-macros-vim-recursive-macros-12.html" rel="alternate"></link><published>2020-07-06T17:28:45+00:00</published><updated>2020-07-06T17:28:45+00:00</updated><author><name>mosaid</name></author><id>tag:mosaid.xyz,2020-07-06:/articles/advanced-vim-macros-vim-recursive-macros-12.html</id><summary type="html">&lt;p&gt;Text editors are an important part of any operating system, learning how to  use them and mastering them is must have skill for anyone who wants to use a  computer, especially if you want to do some programming and code writing.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Text editors are an important part of any operating system, learning how to
use them and mastering them is must have skill for anyone who wants to use a
computer, especially if you want to do some programming and code writing.
&lt;/p&gt;

&lt;p&gt; Vim comes at the top of all other text editors, even though it takes some
time to learn how to use it, but its worth it, for when you do , editing texts
becomes an art and a real fun activity . for programmers; what would take hours
to write in other text editors can take minutes with vim and in this area where
vim really excels . one of the features of vim is &lt;strong&gt;macros&lt;/strong&gt; . vim
macros can make editing semi automatic, you simply make some editing
changes/commands to change a part of your text, and instruct vim to record them.
after that you can use the same changes/commands to the rest of your text for as
long as you want &lt;/p&gt;

&lt;div class="w-100" &gt; 
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;h3&gt;So how to record a vim macro ?&lt;/h3&gt;

&lt;div&gt; 
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;
&lt;p&gt;In vim Normal mode we type "q" and then another letter, this letter
represents the register in which our macro will be recorded, lets say we chose
"a", now a message will show in the bottom of the window that says: "recording
@a" which means that from now on every action we make, every insertion or
deletion will be recorded in register "a" untill we hit "q" once again to finish
recording our macro&lt;br&gt;
from now on everytime we hit "@a" , the instructions recorded in our macro will
be executed.&lt;br&gt;
to make our macro a recursive macro, ie a macro that executes itself . we paste
the content of register "a"  by hitting : "ap  (doublequote included) and then
we append : "@a" at the end of the line, and then we copy the text once again to
the register "a" , by hitting : "ayy  (doublequote included).&lt;br&gt;
the video bellow explains all this in details:&lt;/p&gt;

&lt;div class="w-100" &gt; 
&lt;script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;!-- viewarticle --&gt;
&lt;ins class="adsbygoogle"
    style="display:block"
    data-ad-client="ca-pub-9705701149690337"
    data-ad-slot="7092311090"
    data-ad-format="auto"
    data-full-width-responsive="true"&gt;&lt;/ins&gt;
&lt;script&gt;
    (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt; &lt;/div&gt;

&lt;div class="framediv" &gt;
   &lt;iframe width="560" height="315" src="https://www.youtube.com/embed/W2ZMi_r5wsM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;  
&lt;/div&gt;

&lt;!-- This markdown file was generated automatically from the DB --&gt;</content><category term="vim"></category><category term="vim"></category><category term="macro"></category><category term="recording"></category><category term="normal mode"></category></entry></feed>