Telescope Manager
Telescope is a highly extensible fuzzy finder over a set of various sources, such as commands, notebooks and tags.
The Telescope Manager holds those sources and lets your plugin register its own.
Available as env.telescope on the Environment passed to activate(env).
Open it with core:toggle-telescope, or jump straight to one source with core:show-telescope and its scopedSourceId argument — see Opening your source directly.
Built-in sources
Each source has an id, and most also have an alias — type it followed by a Space in the Telescope input to scope the search to that source.
The space is what commits the prefix, so b on its own still searches everything while b switches to Notebooks. The id works as a prefix too, so books does the same thing.
| Source | id | Default alias | Description |
|---|---|---|---|
| Commands | commands | > | Search and run commands |
| Notebooks | books | b | Search notebooks |
| Tags | tags | t | Search tags |
| Index | index | — | Search sources |
The Index source is special: it lists the other available sources so the user can pick one, and it is reached through getIndexSource() rather than being part of the registered set.
Users can override an alias or turn a source off entirely from config.json, and your source gets the same treatment for free:
{
"telescope": {
"sources": {
"my-plugin-source": { "alias": "m", "disabled": false }
}
}
}
Creating a source
A source is a class extending TelescopeSource, which Inkdrop provides at runtime through the inkdrop module — so import it from there, and keep inkdrop out of your bundle.
You implement two methods: getItems() returns what to show for the current query, and apply() runs when the user picks one of your items.
import { TelescopeSource } from 'inkdrop'
import type {
TelescopeContext,
TelescopeResult,
TelescopeSourceItem
} from '@inkdropapp/types'
import { getEnv } from './env'
export class MyPluginSource extends TelescopeSource {
id = 'my-plugin-source'
name = 'My Plugin'
description = 'Search my plugin'
defaultAlias = 'm'
getItems(context: TelescopeContext): TelescopeResult {
return {
options: [
{
id: 'say-hello',
type: 'Action',
label: 'Say hello',
detail: context.query,
source: this.id,
actions: [{ type: 'run', label: 'Run' }]
}
]
}
}
apply(item: TelescopeSourceItem, action: string): boolean {
if (action === 'run') {
getEnv().notifications.addInfo(`Hello from ${item.label}!`)
}
return true
}
}
Register it in activate() and remove it in deactivate():
import type { Environment, IInkdropPlugin } from '@inkdropapp/types'
import { setEnv } from './env'
import { MyPluginSource } from './my-source'
class MyPlugin implements IInkdropPlugin {
source = new MyPluginSource()
activate(env: Environment) {
setEnv(env)
env.telescope.registerSource(this.source)
}
deactivate(env: Environment) {
env.telescope.unregisterSource(this.source.id)
setEnv(undefined)
}
}
export default new MyPlugin()
unregisterSource takes the source id, not the instance. getEnv() is the
small helper — shown in the Word Count plugin
walkthrough — that
captures the Environment handed to activate() so your
source class can reach it too.
Opening your source directly
Registering a source is enough for the user to reach it by typing its alias, but a source usually wants its own keystroke too.
Add a command that dispatches core:show-telescope scoped to your id — inside activate(), alongside the registerSource() call above:
this.disposable = env.commands.add(document.body, {
'my-plugin:show': () => {
env.commands.dispatch(document.body, 'core:show-telescope', {
scopedSourceId: this.source.id,
initialSelectedItemId: 'say-hello',
cancelBehavior: 'close'
})
}
})
initialSelectedItemId preselects one of your items — the id you gave it in getItems() — which is how a source can open with the row matching the current context already highlighted.
cancelBehavior: 'close' makes Esc on an empty query dismiss Telescope outright, and hides the back button, instead of unscoping back to all sources first — the right choice when your command is the only reason Telescope is open. Leave it out (or pass 'backToIndex') to keep the default step-back.
Dispose it in deactivate() next to unregisterSource(), then bind it from your plugin's keymap.
Example plugins
Two small plugins that do little besides add a source, worth reading end to end:
- telescope-toc — a table of contents for the note you are editing. Reads the editor's syntax tree in
getItems(), shows heading depth and line number throughprefixanddetail, holds document order withboost, and gives its source a keystroke that opens Telescope with the section under the cursor already selected. - telescope-themes — switches between the installed themes. Draws a colour swatch per row from the item's
icon, marks the active theme with anaccessoryView, and applies the pick fromapply().
Properties
- Name
availableSources- Type
- Map<string, TelescopeSource>
- Description
Every registered source, keyed by
id. Note that this is the raw registry — unlikegetAvailableSources(), it is not filtered by whether each source is enabled or currently available.
Register a source
Adds a source to the registry. Registering a source whose id is already taken replaces it, while keeping its original position in the source order.
Parameters
- Name
source- Type
- TelescopeSource
- Required
- Description
The source to register.
Example
env.telescope.registerSource(new MyPluginSource())
Unregister a source
Removes a source from the registry by its id.
Parameters
- Name
sourceId- Type
- string
- Required
- Description
The
idof the source to remove.
Example
env.telescope.unregisterSource('my-plugin-source')
Get a source
Looks up a registered source by its id, regardless of whether it is enabled or available.
Parameters
- Name
sourceId- Type
- string
- Required
- Description
The
idof the source to get.
Returns
The matching TelescopeSource, or undefined if nothing is registered under that id.
Example
const source = env.telescope.getSource('books')
Get all sources
Returns every registered source that is enabled, in registration order. Sources the user has disabled in config.json are left out.
Returns
An Array of TelescopeSource.
Example
const ids = env.telescope.getAllSources().map(source => source.id)
Get available sources
Same as getAllSources(), narrowed further to the sources whose isAvailable() returns true for the current app state — this is the set Telescope actually offers the user right now.
Returns
An Array of TelescopeSource.
Example
const sources = env.telescope.getAvailableSources()
Get the source for a prefix
Finds the available source matching a typed prefix — the prefix matches either a source's id or its alias.
Parameters
- Name
prefix- Type
- string
- Required
- Description
The prefix to match, e.g.
"b"or"books".
Returns
The matching TelescopeSource, or null if no available source matches.
Example
const source = env.telescope.getActiveSourceForPrefix('b')
// -> the Notebooks source
Get the index source
Returns the built-in Index source, which lists the available sources themselves so the user can pick one to scope the search to.
It is held separately from the registry, so it never appears in getAllSources().
Returns
A TelescopeSource.
Example
const index = env.telescope.getIndexSource()
Get items from every source
Calls getItems() on every source from getAllSources() in parallel and collects the results.
This is a convenience for querying every source at once; it is not what the Telescope UI runs. Typing drives each source separately from getAvailableSources() — or the index source alone while the query is empty — so a source can resolve and render as soon as it is ready.
Parameters
- Name
context- Type
- TelescopeContext
- Required
- Description
The context to pass to each source. See TelescopeContext.
Returns
A Promise resolving to an Array of TelescopeResult — one per source, in the same order — or null if any source threw.
Example
const results = await env.telescope.getSourceItems(context)
TelescopeSource
The base class for a source. Import it from the inkdrop module and extend it, implementing at least id, name, description, getItems() and apply().
import { TelescopeSource } from 'inkdrop'
Source properties
- Name
id- Type
- string
- Required
- Description
A unique identifier for the source, e.g.
"books". It doubles as a prefix the user can type, and as the key undertelescope.sourcesinconfig.json.
- Name
name- Type
- string
- Required
- Description
A human-readable name for the source, e.g.
"Notebooks". Nothing in the current UI renders it —descriptionis what the index source shows — so treat it as a label for tooling and future surfaces.
- Name
description- Type
- string
- Required
- Description
What the source searches, e.g.
"Search notebooks". It is shown as the source's label in the index source.
- Name
defaultAlias- Type
- string
- Description
A short prefix that scopes the search to this source, e.g.
"b". The user can override it withtelescope.sources.<id>.aliasinconfig.json.
Get the items
Required. Produces the items to show for the current query. It may return synchronously or as a Promise, so it is fine to hit the database or the network here — but check context.aborted before doing expensive work, since the user may have typed on.
Parameters
- Name
context- Type
- TelescopeContext
- Required
- Description
The current query and state. See TelescopeContext.
Returns
A TelescopeResult, or a Promise of one.
Example
getItems(context) {
return {
options: [
{
id: 'say-hello',
type: 'Action',
label: 'Say hello',
source: this.id
}
]
}
}
Apply an item
Required. Runs when the user picks one of your items. action is the type of the action they chose — with no modifier key held, that is the first action in the item's actions array.
Parameters
- Name
item- Type
- TelescopeSourceItem
- Required
- Description
The item the user selected. See TelescopeSourceItem.
- Name
action- Type
- string
- Required
- Description
The
typeof the chosen action.
Returns
A Boolean: true closes the Telescope bar, false leaves it open — useful when the item drills down into another view instead of finishing the interaction.
Example
apply(item, action) {
if (action === 'run') {
getEnv().notifications.addInfo(item.label)
}
return true
}
Get the icon
Optional. Returns the icon shown for the source itself, in the row the index source renders for it.
Return a React node — Inkdrop does not expose its own icon set to plugins, so ship the SVG with your plugin. The signature also permits a String, but the return value is rendered as a React child, so a string of markup comes out as escaped text.
The index source reads getIcon off your instance and calls it unbound, so this is undefined inside it. Keep the method self-contained — reach for getEnv() instead of this if it needs anything from the app.
Returns
A React node.
Example
const OutlineIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16"
fill="none" stroke="currentColor" strokeWidth="1.5">
<line x1="5.5" y1="3" x2="14" y2="3" />
<line x1="5.5" y1="8" x2="14" y2="8" />
<line x1="5.5" y1="13" x2="14" y2="13" />
</svg>
)
getIcon() {
return <OutlineIcon />
}
Get the alias
Returns the prefix that scopes the search to this source, reading telescope.sources.<id>.alias from the Config and falling back to defaultAlias.
You inherit this — override it only if the alias has to be computed.
Returns
A String, or undefined if the source has neither a configured alias nor a defaultAlias.
Is the source enabled
Is the source available
Returns whether the source makes sense in the current app state. Override it when your source depends on something that isn't always there — the built-in Notebooks source, for instance, hides itself while the sidebar is closed or distraction-free mode is on.
Returns
A Boolean. Defaults to true.
Example
isAvailable() {
const { mainLayout } = getEnv().store.getState()
return mainLayout.sidebarVisible
}
Types
TelescopeContext
The query and state handed to getItems().
- Name
query- Type
- string
- Description
The text the user has typed, with any source prefix already stripped.
- Name
activeElement- Type
- HTMLElement
- Description
The element that was focused when Telescope opened, falling back to
document.body.
- Name
workspaceId- Type
- string | null
- Description
The workspace the search is scoped to, or
nullwhen it isn't scoped.
- Name
aborted- Type
- boolean
- Description
Whether the query has been superseded. Check this in an async
getItems()to bail out of work whose result would be thrown away.
- Name
addEventListener(type, listener)- Type
- function
- Description
Registers an abort handler.
typeis always"abort".
TelescopeResult
What getItems() returns.
- Name
options- Type
- Array<TelescopeSourceItem>
- Required
- Description
The items to display. See TelescopeSourceItem.
- Name
filter- Type
- boolean
- Description
Whether Telescope should fuzzy-filter and score your items against the query. Set it to
falsewhen you have already filtered them yourself — they are then all shown, in the order given, above the filtered ones. Defaults totrue.
- Name
getMatch- Type
- (item, matched?) => readonly number[]
- Description
Computes which ranges of an item's
displayLabelto highlight. Return a flat Array of numbers where each adjacent pair is a start and end offset. Only needed for items that setdisplayLabel.
TelescopeSourceItem
A single row in the Telescope list.
- Name
id- Type
- string
- Required
- Description
A unique identifier for the item. Telescope merges every source's items into one list and resolves both the selection and the picked item by
idalone, so it has to be unique across all sources, not just yours — namespace it, the way the built-in Commands source emitscommand:core-open-note.
- Name
type- Type
- string
- Required
- Description
A short kind label shown on the right side of the row, e.g.
"Notebook".
- Name
label- Type
- string
- Required
- Description
The text shown for the item, and what the query is matched against.
- Name
source- Type
- string
- Required
- Description
The
idof the source that produced the item — set it tothis.id.
- Name
labelClass- Type
- string
- Description
An extra CSS class for the label element.
- Name
displayLabel- Type
- string
- Description
Renders instead of
labelwithout affecting matching. Pair it with the result'sgetMatchto keep characters highlighted.
- Name
prefix- Type
- string
- Description
A short piece of information shown before the label, in a dimmer style.
- Name
detail- Type
- string
- Description
A short piece of information shown after the label, in a dimmer style.
- Name
boost- Type
- number
- Description
Nudges the item's rank against equally-matching items. A number from
-99to99; positive moves it up, negative moves it down. It is added to the fuzzy-match score, so it is ignored entirely when the result setsfilter: false.Deriving it from the item's position is a handy way to keep your own ordering as a tiebreak while still letting the query filter — a table of contents can pass
boost: items.length - indexso headings stay in document order.
- Name
icon- Type
- string | () => React.ReactNode
- Description
An icon for the left side of the row. A function returning a React node is the usual choice; a String has to be raw SVG markup starting with
<svg, which is injected as HTML. Any other String renders nothing.
- Name
accessoryView- Type
- string | () => React.ReactNode
- Description
A custom view for the right side of the row, after
type. A function returning a React node, or a String rendered as plain text.
- Name
actions- Type
- Array<TelescopeAction>
- Description
What the user can do with the item. See TelescopeAction. An item with no actions cannot be picked at all — Enter does nothing and
apply()is never called — so give every selectable item at least one.
TelescopeAction
One of the things a user can do with an item. Telescope picks the first action whose modifierKeys are all held; if none matches, it falls back to actions[0].
So the first action in the array is the default — the one that runs on a bare Enter — whether or not it declares modifierKeys. Put the plain one first.
Whichever is chosen, its type is passed to apply().
- Name
type- Type
- string
- Required
- Description
The identifier passed to
apply()as itsactionargument, e.g."open".
- Name
label- Type
- string
- Required
- Description
The text shown for the action in the footer hint at the bottom of Telescope, e.g.
"Open Notebook", next to the keystroke that runs it.
- Name
icon- Type
- string
- Description
Accepted but not yet rendered anywhere.
- Name
modifierKeys- Type
- Array<TelescopeModifierKey>
- Description
The modifiers that select this action. All of them must be held for it to match. Leave it off the default action.
TelescopeModifierKey
An enum of "alt", "shift", "ctrl", "cmd", and "cmd-or-ctrl" — the last resolving to Cmd on macOS and Ctrl elsewhere.
import { TelescopeModifierKey } from '@inkdropapp/types'
actions: [
{ type: 'open', label: 'Open Notebook' },
{
type: 'open-workspace',
label: 'Open as Workspace',
modifierKeys: [TelescopeModifierKey.CmdOrCtrl]
}
]