Macotron

macotron.launcher

Add your own rows to the Macotron launcher, either as a fixed list that the launcher filters or as answers computed from the text the user types.

// Answer "2*21" with "42" right in the launcher.
macotron.launcher.query("calc", (q) => {
  if (!/^[\d\s+\-*/().]+$/.test(q)) return [];
  const n = Function("return (" + q + ")")();
  if (!isFinite(n)) return [];
  return [{
    id: "result",
    title: String(n),
    subtitle: "Return to copy",
    sfSymbol: "equal.square",
    kind: "Calculator",
    onClick: () => macotron.clipboard.set(String(n)),
  }];
});

Rows

Both calls take rows in the same shape, LauncherItem:

FieldTypeWhat it does
idstringIdentifies the row within its provider. Stars (⌘S) and keyboard shortcuts are saved against it, so keep it stable. Defaults to the row's index.
titlestringThe main text. Defaults to the id.
subtitlestringSmaller text under the title.
kindstringThe label on the right of the row, such as "Calculator". Default "Plugin".
appstringA bundle ID, such as com.apple.Notes. The row shows that app's icon.
pathstringA file path. The row shows that file's Finder icon.
sfSymbolstringAn SF Symbol name for the icon, used when there is no app or path icon.
onClick() => voidRuns when the user picks the row.

Fixed lists

set(id: string, items: LauncherItem[]): void

Replaces the rows of provider id. The launcher fuzzy-matches the typed text against each row's title and subtitle, and ranks the matches together with apps and commands. With an empty query, the launcher shows only starred rows.

macotron.launcher.set("snippets", [
  { id: "omw", title: "omw", subtitle: "On my way!", onClick: () => macotron.snippets.insert("omw") },
]);

Live answers

query(id: string, handler: (query: string) => LauncherItem[] | Promise<LauncherItem[]>, opts?: { run?, secondary? }): void

Calls handler on every keystroke with the launcher's text. Calling query again with the same id replaces the handler.

Return fast, and return [] when the text is not for you. A synchronous handler that runs longer than 250 ms is interrupted.

A row that does not match the text by name, such as 42 for 2*21, is an answer and goes to the top of the list. A row that does match by name is ranked with everything else.

The handler can return a promise. The launcher does not wait for it: the keystroke shows what is ready, and the rows appear when the promise resolves. An answer to text the user has since changed is dropped. A rejected promise clears this provider's rows.

OptionTypeWhat it does
run(rowId: string) => voidRuns a row from its id alone. The launcher calls it when the user triggers a row that is no longer on screen, for example from a keyboard shortcut after a restart. Use row ids you can act on, such as file paths.
secondarybooleanSet this for a provider that answers almost any text, such as a file search. Its rows never lead the list and lose ties to other results.
// From the file-search plugin.
macotron.launcher.query("file-search", async (query) => {
  const term = query.trim();
  if (term.length < 2) return [];
  const hits = await macotron.files.search(term, { limit: 8 });
  return hits.map((h) => ({ id: h.path, title: h.name, subtitle: h.path, path: h.path, onClick: () => open(h.path) }));
}, {
  secondary: true,
  run: (path) => open(path),
});

Removing

remove(id: string): void

Removes the provider's fixed rows and its query handler.

The launcher shows at most 20 results at a time.

See also