Tiny text editors for devs: plugin patterns to add structured tables to any code editor
editorspluginsstarter-kit

Tiny text editors for devs: plugin patterns to add structured tables to any code editor

ccodenscripts
2026-02-09 12:00:00
11 min read
Advertisement

Reusable plugin templates for VS Code and Neovim that add lightweight table editing and CSV import/export for fast, keyboard-first workflows.

Ship small, reuse often: add structured table editing to any editor in minutes

Too many repetitive edits, too little tooling: if you spend time aligning Markdown tables, importing CSV snippets, or converting logs into tabular data, you want a tiny, dependable tool that plugs into your editor. This guide gives you reusable plugin templates for VS Code (TypeScript) and Neovim (Lua) that add lightweight table editing and CSV import/export without swallowing your workflow.

What you'll get

  • Minimal starter kits: a VS Code extension and a Neovim Lua plugin focused on table features.
  • Actionable code: copy-pasteable TypeScript and Lua examples (insert table, align, import/export CSV).
  • Best practices: plugin architecture, testing, distribution, security and 2026 trends like WASM and Tree-sitter integration.

Why lightweight table plugins matter in 2026

Small editors and big IDEs are converging on one core expectation: the editor should help with focused content tasks without becoming opinionated. Microsoft adding tables to Notepad in late 2025 is a good signal — users expect table-first tools in tiny UIs. For developers, the sweet spot is plugins that are:

  • Non-invasive — no heavy UIs or servers.
  • Cross-editor reusable — core logic separated from UI so it can be reused (TS lib, Lua module, or WASM).
  • Keyboard-first — fast commands and small modal UIs (quick inputs, floating windows).
“Ship focused editor tools; avoid monoliths.”

Core plugin patterns

Across editors, the same plugin architecture works best. Use these patterns as templates you can clone and adapt.

1. Separation of concerns

Split logic into three layers:

  • Core table library — parsing CSV, computing column widths, formatting Markdown/ASCII tables.
  • Editor adapter — translates core actions to editor APIs (VS Code TextEditor edits, Neovim buffer ops).
  • UI layer — prompts, floating windows, or webview for richer interactions.

2. Minimal permissions and safe I/O

For VS Code, request only file-system access you need: use window.showOpenDialog and workspace.fs.readFile. For Neovim, prefer buffer-based edits and explicit commands. Never execute file contents; always parse and sanitize user input. Prefer privacy-first flows and local sandboxes inspired by ephemeral AI workspaces where possible.

3. Keyboard-first UX

Provide commands and default keybindings for quick flows: Insert Table, Align Table, Import CSV, Export CSV. Keep modal interactions minimal — ask only required parameters.

Starter kit — VS Code extension (TypeScript)

This template uses the VS Code Extension API, no webview required for basic features. The extension inserts and aligns tables, and imports/exports CSV using the workspace FS APIs.

Project layout (minimal)

my-vscode-tables/
├─ package.json
├─ src/
│  ├─ extension.ts
│  └─ table-core.ts   // reusable core logic
├─ tsconfig.json
└─ README.md

Key package.json entries

{
  "name": "my-vscode-tables",
  "displayName": "Tiny Tables",
  "publisher": "you",
  "engines": { "vscode": "^1.80.0" },
  "activationEvents": ["onCommand:tinyTables.insertTable", "onCommand:tinyTables.importCsv"],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      { "command": "tinyTables.insertTable", "title": "Insert Table" },
      { "command": "tinyTables.importCsv", "title": "Import CSV as Table" }
    ]
  }
}

Minimal extension.ts

import * as vscode from 'vscode';
import { buildMarkdownTable, parseCSV } from './table-core';

export function activate(context: vscode.ExtensionContext) {
  context.subscriptions.push(
    vscode.commands.registerCommand('tinyTables.insertTable', async () => {
      const rowsInput = await vscode.window.showInputBox({ prompt: 'Rows', value: '3' });
      const colsInput = await vscode.window.showInputBox({ prompt: 'Columns', value: '3' });
      const rows = parseInt(rowsInput || '3', 10);
      const cols = parseInt(colsInput || '3', 10);
      const editor = vscode.window.activeTextEditor;
      if (!editor) return;
      const table = buildMarkdownTable(generateEmpty(rows, cols));
      editor.edit(edit => edit.insert(editor.selection.active, table));
    }),

    vscode.commands.registerCommand('tinyTables.importCsv', async () => {
      const uris = await vscode.window.showOpenDialog({ canSelectMany: false, filters: { 'CSV': ['csv', 'txt'] } });
      if (!uris || uris.length === 0) return;
      const content = await vscode.workspace.fs.readFile(uris[0]);
      const text = Buffer.from(content).toString('utf8');
      const rows = parseCSV(text);
      const table = buildMarkdownTable(rows);
      const editor = vscode.window.activeTextEditor;
      if (!editor) return;
      editor.edit(e => e.insert(editor.selection.active, table));
    })
  );
}

function generateEmpty(r:number,c:number){
  const out = [] as string[][];
  for(let i=0;i

table-core.ts — core logic for reuse (TS)

export function parseCSV(text: string): string[][] {
  const rows: string[][] = [];
  let cur = '';
  let row: string[] = [];
  let inQuote = false;
  for (let i = 0; i < text.length; i++) {
    const ch = text[i];
    if (ch === '"') { inQuote = !inQuote; continue; }
    if (!inQuote && (ch === ',' || ch === '\n' || ch === '\r')) {
      if (ch === '\r' && text[i+1] === '\n') continue; // handled by \n
      row.push(cur.trim());
      cur = '';
      if (ch === '\n') { rows.push(row); row = []; }
      continue;
    }
    cur += ch;
  }
  if (cur.length || row.length) { row.push(cur.trim()); rows.push(row); }
  return rows.filter(r=>r.length>0);
}

export function buildMarkdownTable(rows: string[][]): string {
  if (rows.length === 0) return '';
  const cols = Math.max(...rows.map(r => r.length));
  const table = rows.map(r => r.concat(Array(cols - r.length).fill('')));
  const widths = Array(cols).fill(0);
  for (const r of table) r.forEach((c,i)=> widths[i]=Math.max(widths[i], c.length));

  const pad = (s:string,w:number)=> ' ' + s + ' '.repeat(w - s.length) + ' ';
  const header = table[0];
  const headLine = '|'+ header.map((c,i)=>pad(c,widths[i])).join('|') + '|\n';
  const sep = '|'+ widths.map(w=>' '+ '-'.repeat(w) + ' ').join('|') + '|\n';
  const body = table.slice(1).map(r=>'|'+ r.map((c,i)=>pad(c,widths[i])).join('|') + '|\n').join('');
  return headLine + sep + body;
}

Starter kit — Neovim plugin (Lua)

Neovim's Lua API makes small plugins fast to write. The example below creates user commands and a floating window for table preview/edit.

Project layout (minimal)

my-nvim-tables/
├─ lua/
│  ├─ tiny_tables.lua   -- plugin entry
│  └─ table_core.lua    -- reusable core logic
└─ plugin/
   └─ tiny_tables.vim    -- loads Lua on startup

plugin/tiny_tables.vim

if has('nvim')
  lua require('tiny_tables').setup()
endif

lua/tiny_tables.lua

local core = require('table_core')
local M = {}

function M.setup()
  vim.api.nvim_create_user_command('TinyTableInsert', function(opts)
    local rows = tonumber(vim.fn.input('Rows: ', '3')) or 3
    local cols = tonumber(vim.fn.input('Cols: ', '3')) or 3
    local tbl = core.build_markdown_table(core.generate_empty(rows, cols))
    vim.api.nvim_put(vim.split(tbl, '\n'), 'l', true, true)
  end, {})

  vim.api.nvim_create_user_command('TinyTableImportCsv', function()
    local path = vim.fn.input('CSV path: ', '', 'file')
    if path == '' then return end
    local f = io.open(path, 'r')
    if not f then return end
    local text = f:read('*a') f:close()
    local rows = core.parse_csv(text)
    local tbl = core.build_markdown_table(rows)
    vim.api.nvim_put(vim.split(tbl, '\n'), 'l', true, true)
  end, {})
end

return M

lua/table_core.lua

local M = {}

function M.generate_empty(r,c)
  local out = {}
  for i=1,r do
    local row = {}
    for j=1,c do row[j] = '' end
    out[i] = row
  end
  return out
end

function M.parse_csv(text)
  local rows = {}
  for line in text:gmatch('[^\r\n]+') do
    local cols = {}
    local cur = ''
    local inQuote = false
    for i=1,#line do
      local ch = line:sub(i,i)
      if ch == '"' then inQuote = not inQuote
      elseif ch == ',' and not inQuote then
        table.insert(cols, vim.trim(cur)) cur = ''
      else cur = cur .. ch end
    end
    table.insert(cols, vim.trim(cur))
    table.insert(rows, cols)
  end
  return rows
end

function M.build_markdown_table(rows)
  if #rows == 0 then return '' end
  local cols = 0
  for _,r in ipairs(rows) do cols = math.max(cols, #r) end
  for _,r in ipairs(rows) do while #r < cols do table.insert(r,'') end end
  local widths = {}
  for j=1,cols do widths[j]=0 end
  for _,r in ipairs(rows) do for j=1,cols do widths[j] = math.max(widths[j], #r[j]) end end
  local function pad(s,w) return ' ' .. s .. string.rep(' ', w-#s) .. ' ' end
  local out = {}
  table.insert(out, '|' .. table.concat(vim.tbl_map(function(c,i) return pad(c,widths[i]) end, rows[1]), '|') .. '|')
  local sep = '|' .. table.concat(vim.tbl_map(function(w) return ' ' .. string.rep('-', w) .. ' ' end, widths), '|') .. '|'
  table.insert(out, sep)
  for i=2,#rows do
    table.insert(out, '|' .. table.concat(vim.tbl_map(function(c,j) return pad(c,widths[j]) end, rows[i]), '|') .. '|')
  end
  return table.concat(out, '\n')
end

return M

Shared core library pattern (editor-agnostic)

To avoid rewriting formatting logic, extract the CSV parser and table formatter into a small shared core. Options in 2026:

  • Publish a tiny npm package (TypeScript) that both VS Code extension and a CLI can use.
  • Compile core logic to WASM for reuse in Neovim (via luvit or other runtimes) and browser-based editor UIs; consider ephemeral workspaces as an integration pattern.
  • Keep API surface minimal: parseCSV(text): string[][]; formatTable(rows, opts): string.

CSV edge cases and robustness

Simple split logic works for common CSV, but production needs to handle:

  • Quoted fields with commas and newlines — use a battle-tested parser (PapaParse in JS) if you expect complex CSV.
  • Character encodings — default to UTF-8; expose a parameter for other encodings when reading files.
  • Large files — don't buffer whole huge CSVs in-memory inside the editor plugin; either stream or open read-only preview and allow sampling.

Keyboard UX and commands

Make features discoverable and fast:

  • Default commands: tinyTables.insertTable / TinyTableInsert
  • Keybindings: map Insert Table to a chord like Ctrl+Alt+T (allow users to overwrite).
  • Quick toggles: offer alignment-only commands and a “convert selection to table” command.

Testing, packaging, and distribution

Testing and CI keep these tiny plugins trustworthy:

  • Unit test the core formatter (Jest for TS, busted or luaunit for Lua).
  • Use GitHub Actions to lint, run tests, and publish (vsce for VS Code, tag + release for Neovim plugin managers) — see lightweight publish playbooks for small teams.
  • Provide minimal README with usage and permissions. For VS Code, keep contribution points short and focused.

Security, licensing, and trust

Developers worry about running third-party code in their editor. Follow these rules:

  • Minimal permissions: request only what you need in package.json.
  • Open-source license: prefer permissive licenses like MIT if you want adoption. Document license clearly.
  • Static analysis: run static analysis and npm audit (or luacheck) in CI and publish SBOM-like details for dependencies in 2026 tooling.

Look beyond simple extensions for better reuse and future proofing:

WASM cross-editor cores

Compile core table logic to WebAssembly so the same code runs in VS Code (WebView or extension host), Neovim (via WASM runtime), and browser-based editors. This reduces duplication. Consider integrating with modern IDEs and editor ecosystems when designing your core.

Tree-sitter and table detection

Tree-sitter gained wider adoption by late 2025. Use it to detect CSV-like blocks in logs, Markdown fenced regions, or custom formats — then offer contextual commands to convert detected blocks into tables. You can pair detection with edge observability and telemetry tools like edge observability for larger deployments.

AI-assisted conversions

LLMs are commonly used in 2026 to infer table schemas from messy data (e.g., logs or HTML tables). Provide an opt-in AI flow that suggests headers and column types. Keep this optional and explicitly opt-in to avoid sending private data unintentionally; pair opt-in flows with robust consent patterns like those in consent architecture.

Small LSP for table schemas

If your team shares a table schema (CSV with typed columns), a lightweight language server can provide validation and conversions across editors. Only introduce this when schema enforcement is required.

Case study: From CSV to aligned Markdown in 10 seconds

Workflow using the VS Code template above:

  1. Open a CSV file or paste CSV text into a buffer.
  2. Run command palette → Insert Table or Import CSV.
  3. Plugin parses CSV, computes column widths, inserts a Markdown table.
  4. Run Align Table command after small edits — table stays aligned.

This reduces the usual manual alignment cycle and integrates cleanly with Git diffs.

Checklist before shipping

  • Core logic is in a separate module and has unit tests.
  • Editor adapters are thin wrappers over core functions.
  • Requested permissions are minimal and documented.
  • README includes quickstart, shortcuts, and example commands.
  • CI publishes artifacts and runs security checks.

Actionable takeaways

  • Start by extracting your formatter/parsers into a small core with a stable API.
  • Build a thin editor adapter for VS Code and Neovim; share the core as npm or WASM.
  • Prefer simple commands and floating UI over heavy webviews for low friction.
  • Test CSV edge cases and avoid reading huge files into memory in the editor.

Further resources & templates

To get running quickly in 2026, scaffold with these tools:

  • VS Code Extension Generator / Yeoman or a lightweight template in GitHub repo.
  • Neovim plugin skeletons (packer.nvim starter or init.lua snippets).
  • Consider a tiny WASM pipeline for maximum reuse across editors and browsers; review rapid publishing patterns for small teams shipping extensions.

Final thoughts

Tables are a small but highly repeated task for developers — they deserve tiny, trustworthy tools. By following the patterns above you get reusable, editor-agnostic building blocks that keep your editor light and your workflow fast. These templates are pragmatic: no large UI clusters, explicit permissions, and a single core library to maintain.

Ready to ship? Clone the starter kit, run tests, and publish. Start with the core formatter and add one adapter at a time — VS Code or Neovim — then publish. If you want a cross-editor distribution later, compile your core to WASM and plug it into other environments.

Call to action

Want the starter kits now? Clone the example repos (VS Code and Neovim) from the linked template, try the commands, and open a PR with improvements — add Tree-sitter detection, a Papaparse option, or an AI-assisted header guesser. Star the repo, file issues for edge cases, and share how the plugin saves you time. Join the community and help keep tiny tools reliable and reusable in 2026.

Advertisement

Related Topics

#editors#plugins#starter-kit
c

codenscripts

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-01-24T04:11:18.953Z