Description

Press Tab to jump past closing characters like quotes, brackets, and parentheses — just like VS Code's Tab Out extension.

Additional Information

Links:
Maintainers: mindfultatiana
Version:1.0.0
Minimum app version:3.0
Downloads: This version:4
Last updated:2026-10-15T01:53:03Z

Joplin Tab Out

A Joplin desktop plugin that lets you press Tab to jump past closing characters — quotes, brackets, parentheses — without reaching for the arrow keys. Inspired by the VS Code Tab Out extension.

Link to the plugin store page HERE

Problem Statement

Joplin's Markdown editor has no built-in way to escape closing characters after typing them. After writing "hello", (param), or `code`, you must reach for the arrow key or click past the closing character to continue — breaking typing flow.

This friction compounds when writing structured Markdown: inline code, links, bold/italic spans, and HTML tags all require the same awkward escape. Joplin's plugin system exposes the underlying CodeMirror editor, but the CM5/CM6 compatibility shim makes keyboard interception non-trivial — existing general-purpose plugins don't solve this for Joplin specifically.

Tech Stack

  • JavaScript (ES6) — content script logic
  • TypeScript — plugin entry point
  • Node.js — build tooling
  • webpack 5 — bundling
  • Joplin Plugin API — contentScripts.register, codeMirrorPlugin type
  • CodeMirror (CM5 shim / CM6 internals) — editor interception via cm.cm6.dom

How It Works

The plugin registers a codeMirrorPlugin content script. Joplin passes a CM5-compatible shim object to the content script's plugin() function. That shim exposes cm.cm6 — the real underlying CM6 EditorView.

Rather than fighting Joplin's keymap system (which intercepts Tab before CM5/CM6 bindings are checked), the plugin attaches a capture-phase DOM event listener directly to cm.cm6.dom. This fires before any other handler:

view.dom.addEventListener('keydown', function(event) {
  if (event.key !== 'Tab') return;
  const nextChar = state.doc.sliceString(pos, pos + 1);
  if (TAB_OUT_CHARS.has(nextChar)) {
    event.preventDefault();
    event.stopPropagation();
    view.dispatch({ selection: { anchor: pos + 1 } });
  }
}, true); // capture: true

When Tab is pressed before a closing character, the cursor moves past it. Otherwise the event propagates normally and Joplin's indent/list-continuation behavior takes over.

Tab-Out Characters

Character Description
" Double quote
' Single quote
` Backtick (inline code)
) Closing parenthesis
] Closing bracket
} Closing brace
> Closing angle bracket

Exception: Triple backtick (```) is never a tab-out target — Tab works normally for indentation inside fenced code blocks.

Repository Structure

joplin-tabout/
├── manifest.json          # Plugin metadata (id, version, app_min_version)
├── package.json           # npm package metadata + build dependencies
├── package-lock.json      # Locked dependency tree (committed)
├── tsconfig.json          # TypeScript config
├── webpack.config.js      # Bundle config — aliases api/ stub, no libraryTarget
├── .gitignore             # Ignores node_modules/ and dist/ — NOT publish/
├── api/
│   ├── index.js           # Runtime stub: exports global `joplin` object
│   └── index.d.ts         # TypeScript type declarations
├── scripts/
│   └── dist.js            # Validates manifest, packages dist/ into publish/
├── src/
│   ├── index.ts           # Plugin entry — registers the content script
│   └── contentScript.js   # Tab interception logic (CM5 shim + CM6 internals)
├── dist/                  # Build output — generated, gitignored
│   ├── index.js           # Bundled plugin entry
│   ├── contentScript.js   # Copied verbatim by CopyPlugin
│   └── manifest.json      # Copied verbatim by CopyPlugin
└── publish/               # Release artifacts — generated, committed
    ├── joplin.plugin.tabout.jpl   # TAR of dist/ — the installable plugin
    └── joplin.plugin.tabout.json  # Manifest copy — read by the Joplin repo

dist/ and publish/ are both produced by npm run dist, but they are treated differently: dist/ is a disposable intermediate, while publish/ holds the release artifacts. Both files in publish/ must be present in the published npm package for the plugin to be picked up by the official Joplin plugin repository.

Installation

Pre-built

  1. Download joplin.plugin.tabout-v1.0.0.jpl from Releases
  2. Windows: right-click → Properties → check Unblock → OK
  3. In Joplin: Tools → Options → Plugins → Install from file
  4. Restart Joplin

Build from source

git clone https://github.com/mindfultatiana/joplin-tabout
cd joplin-tabout
npm install
npm run dist

See ReproducibilityNotes for full environment details.

Future Work

  • Shift+Tab to jump back — mirror VS Code's reverse tab-out
  • Configurable character set — let users add or remove characters via plugin settings
  • Auto-pair awareness — only tab out when the closing character was auto-inserted (not manually typed)
  • Rich Text editor support — currently Markdown editor only

License

MIT