谈谈子135 downloadsPick code block languages from a floating two-column panel, with built-in languages, Mermaid, plugin-registered names, and reusable content templates.
English · 简体中文
Code blocks in Obsidian, as handy as they are in Typora: hover a code block and pick the language.
Tips is an Obsidian plugin that solves two things:
```java), which is easy to get wrong and tedious to type.mechanism, wordcloud, …).Tips gathers built-in languages + Obsidian's own processors (mermaid / math / query) + code block names registered by your installed plugins + identifiers from their bundled tips.json + your own custom entries into a single two-column panel.
It also lets you store reusable templates — type ? to bring them up whenever you need them.
Hover any code block and a button appears in the bottom-right corner showing that block's current language — an instant answer to "what am I supposed to write here?". Click it to open the picker and switch languages.
Clicking the button does not move your cursor; picking a language only rewrites the language identifier, so your editing position is never disturbed.
Type three backticks on an empty line and the picker appears immediately:
| Left column: languages / built-in | Right column: plugins / custom |
|---|---|
mermaid, python, java, bash, … |
Code block names registered by plugins (such as mechanism, wordcloud, …) plus your own entries |
Long entries are truncated with an ellipsis; hovering shows the full "entry — note" tooltip.
Keep typing to filter: after j, both columns narrow to matching entries. Filtering prefers prefix matches and also supports fuzzy matching, so wc still finds wordcloud.
Keyboard:
| Key | Action |
|---|---|
↑ / ↓ |
Move within the current column; at the end it jumps to the other column |
← / → |
Switch columns |
Enter / Tab |
Confirm the selection |
Esc |
Close the panel |
You can also click an entry with the mouse.
Put the cursor on an existing info line (for example ```java) and the panel opens with that line's content as the filter; editing the line updates results live.
The panel always follows the cursor. Even when it was opened from the corner button, as soon as you go back to editing the info line it moves from the button to the line you're editing — it never stays behind.
Entries are sorted alphabetically so you can find them by name (this can be switched back to the built-in frequency order in settings).
The panel is always anchored directly below the cursor or the button (flipping above when there isn't room below) and never shifts sideways. It stays flush against its anchor, so shrinking the content won't leave a gap; it closes automatically when you scroll the note or move away from the info line.
mermaid, math, query.main.js under the plugins folder, extracts names from registerMarkdownCodeBlockProcessor("xxx", …) and shows which plugin they belong to (disabled plugins are labelled). Two extra clues are used when the literal name is not enough: the .block-language-* classes in the plugin's styles.css, and string constants that are later passed to the registration call.tips.json are added as candidates too. This covers plugins that register their code blocks dynamically, where the name only exists at runtime and cannot be read from the source.Picking the right category is only the first step. Mermaid alone has a dozen diagram types, and other plugins come with their own variants — each with different syntax. Store the ones you use as content templates, then type ? as the first character inside a code block to bring them up:
```mermaid
?实体关系图
```
A panel appears (names on the left, a full preview on the right). Pick with ↑↓ and insert with Enter — the whole code block body is replaced and the cursor lands at the end of the new content.
? must be the first character of the body, with no leading space, so a ? b : c never triggers it by accident? filters by name (the body is not searched), e.g. ?实体关系图``` is stripped for you{
"plugins": { "mechanism": [{ "name": "Four-bar linkage", "body": "type: fourbar" }] },
"builtin": { "mermaid": [{ "name": "Pie chart", "body": "pie title X" }] },
"custom": { "myblock": [{ "name": "Private template", "body": "..." }] }
}
The three groups are treated differently on import:
| Group | Behaviour |
|---|---|
plugins |
Imported when the plugin is installed, otherwise skipped entirely with a notice |
builtin |
Imported when the processor exists, otherwise skipped entirely with a notice |
custom |
Controlled by the "import non-plugin templates" switch; missing entries are created automatically with a blank note |
tips.json file, or select the JSON shown in the export dialog and copy it; importing accepts pasted text or a chosen fileIf your plugin also works through code blocks, you can put reference templates in a tips.json inside your plugin folder — users get them automatically after installing, with no manual import:
The structure of tips.json:
{
"mermaid": [
{ "name": "erDiagram","body": "erDiagram\n CUSTOMER ||--o{ ORDER : places\n ORDER ||--|{ ORDER-ITEM : includes"
},
{ "name": "......", "body": "........" }
]
}
A few conventions:
name and body)body``` pickermain.js, manifest.json and styles.css.<vault>/.obsidian/plugins/tips/ inside your vault.npm install
npm run dev # watch mode, rebuilds on change
npm run build # type check + production build
Copy the generated main.js along with manifest.json and styles.css into the plugin's tips folder.
During development you can also clone this repository straight into <vault>/.obsidian/plugins/tips/ and reload the plugin in Obsidian after npm run dev.
| Setting | Default | Description |
|---|---|---|
| Interface language 🌏️ | Follow Obsidian | Choose "Follow Obsidian" or 简体中文 / English / Русский / Français / Español / العربية |
| Floating code block button | On | When off, the corner button is no longer shown |
| Auto-open after typing ``` | On | When off, the panel can only be opened from the button or the command |
| Open while editing the info line | On | Opens when the cursor lands on the info line or while you edit it |
| Sort candidates alphabetically | On | When off, the language column keeps the built-in frequency order |
| Candidate panel width | 580 px | Range 280–920 px, shared by the language and template panels |
| Include the built-in language list | On | When off, only the entries you need remain in the left column |
| Scan installed plugins for code block names | On | When off, no plugin files are read |
| Read templates bundled with plugins | On | Scans plugin folders for tips.json; its identifiers join the candidate list and its templates are merged into yours on rescan |
| Rescan now | —— | Triggers a scan manually and reports the result |
| Custom code block entries | Empty | Entry + note, add or remove at any time |
| Content templates | Empty | Reusable code block bodies per entry, invoked with ? on the first line; supports import / export |
The plugin also registers two commands (search for "Tips" in the command palette):
ViewPlugin registered through registerEditorExtension adds a line decoration to the last line of every fenced code block and places a WidgetType button at the end of that line; the picker is a fixed-position layer attached to document.body.``` and ~~~ are supported), and unclosed fences are handled correctly, so the panel reacts the moment the third backtick is typed.Vault.configDir and Vault.adapter are both public, so it works on desktop and mobile alike. The enabled-plugin list comes from the vault config file community-plugins.json.tips.json is where an author can distribute reference templates. Its identifiers are fed into the candidate list and its templates are merged into the local template table, which is what makes plugins with runtime-registered code block names usable.main.js. A plugin that builds its registration name from variables cannot be read from the source — if it ships a tips.json, its identifiers still reach the picker; otherwise add them as custom entries. Plugins in that situation are listed in settings.main.js (usually tens of KB to a few MB). It runs asynchronously in the background on first load and can be a little slow on mobile with large vaults; it can be turned off in settings.tips/
├── src/
│ ├── main.ts # Plugin entry: settings, commands, scan scheduling
│ ├── settings.ts # Settings tab
│ ├── i18n.ts # Strings and language resolution
│ ├── types.ts # Settings and candidate types
│ ├── data/languages.ts # Built-in languages and Obsidian processors
│ ├── core/
│ │ ├── preferences.ts # Settings merge and normalisation
│ │ ├── scanner.ts # Scans installed plugins for code block names
│ │ ├── snippets.ts # Templates: fence stripping, normalisation, import merge
│ │ └── store.ts # Candidate merging, filtering and sorting
│ ├── editor/
│ │ ├── codeblocks.ts # Fence detection, ? trigger point and replacement maths
│ │ └── extension.ts # CodeMirror 6 extension: button + panel timing
│ └── ui/
│ ├── picker.ts # Two-column language picker
│ ├── position.ts # Shared positioning and scrolling helpers
│ ├── snippet-picker.ts # Template panel (names / preview)
│ └── snippet-modals.ts # Template editing and import/export modals
├── styles.css
├── manifest.json
└── esbuild.config.mjs
Settings
.block-language-* class → tips.json identifier.Fixed
createEl('div') calls in the template dialogs were replaced with createDiv(), as required by Obsidian's prefer-create-el rule.Changed
Docs
CONTRIBUTING.md, linked from both READMEs.Fixed: templates bundled by plugins now actually reach the picker
A plugin's tips.json was being read, counted and shown in settings, but the identifiers inside it never made it into the ``` picker — so a plugin that registers its code blocks dynamically (from a variable, which static analysis cannot resolve) looked "detected" yet was unusable. The bundled identifiers are now merged into the candidate list as well, subject to Read templates bundled with plugins and de-duplicated against names found by the scan.
Scanning
.block-language-* classes in the plugin's styles.css, and string constants that get passed to registerMarkdownCodeBlockProcessor() later on (only when the variable resolves to exactly one string literal).excalidraw and excalidraw-script-install are detected, with nothing missed.Settings
LMath (13), so you can see at a glance which groups have content.Changed
createDiv / createSpan / createEl), and section titles use Setting.setHeading().getLanguage() API instead of reading localStorage.setDynamicTooltip() call.minAppVersion raised to 1.8.7, required by getLanguage().README_ZH.md.Content templates (new)
? on the first line of a code block to bring up the template panel: names on the left, a full preview on the right, ↑↓ to select and Enter to insert? must be the first character of the body, so a ? b : c never triggers it; typing after ? filters by name``` is stripped automaticallyImport / export
tips.json file; importing accepts pasted text or a chosen filetips.json inside a plugin folder is shown automatically (read-only, never written into your list)Other
``` opens the two-column picker (languages / built-in, plugins / custom)Bug reports and pull requests are welcome — see CONTRIBUTING.md. It covers what to include in a bug report, how to test changes to the logic modules without a test framework, and the review rules the code has to follow.
MIT © 2026 Tan-TanZi