Skip to content

Contributions ABI (contrib)

Source: Plugins/SDK/contrib.h — this page is generated from that header by docs/scripts/gen-api-reference.py; edit the header, not this page.

contrib.h — Peach Commander plugin contributions, behavior ABI (SPEC-013).

The declarative side of a contribution (WHAT commands/views a plugin offers and WHERE they appear: menus, context menus, keybindings, view containers) lives in the plugin's Info.plist PCContributions. This header is ONLY the behavior ABI: the host runs a declared command or builds a declared view by id. It is id-based and supersedes PTX's index-based tool ABI (PtxGetToolCount/Info/Execute).

A single plugin of ANY type (pcx/pfx/plx/pdx) may also carry contributions — the layer is orthogonal to the file-op ABIs. Self-contained C11 on pc_common.h; all char* are UTF-8; version-checked via PcGetApiVersion.

Entry points & functions

  • PcCloseView
  • PcConfigure
  • PcGetApiVersion
  • PcInvokeTool
  • PcMakeView
  • PcNotifyThemeChanged
  • PcNotifyView
  • PcRunCommand

Callbacks & service members

  • cursorPath
  • localCursorPath
  • selectionCount
  • selectionPath
  • moveToTrash
  • deletePermanently
  • reloadActivePanel
  • presentInfo
  • getContext
  • invokeCommand
  • crypt
  • registerToolWindow
  • openPath
  • openPathInPanel
  • presentSidebarView
  • dismissSidebarView
  • compareFiles
  • automationToolsJson
  • automationContextJson
  • automationInvoke
  • automationConfirm
  • automationFree
  • getFileComment
  • setFileComment
  • beginProgress
  • updateProgress
  • endProgress
  • annotateLines
  • automationPlanItems
  • automationConfirmRejecting
// SPDX-License-Identifier: Apache-2.0
/*
 * contrib.h — Peach Commander plugin contributions, behavior ABI (SPEC-013).
 *
 * The declarative side of a contribution (WHAT commands/views a plugin offers and
 * WHERE they appear: menus, context menus, keybindings, view containers) lives in
 * the plugin's Info.plist `PCContributions`. This header is ONLY the behavior ABI:
 * the host runs a declared command or builds a declared view by id. It is id-based
 * and supersedes PTX's index-based tool ABI (PtxGetToolCount/Info/Execute).
 *
 * A single plugin of ANY type (pcx/pfx/plx/pdx) may also carry contributions — the
 * layer is orthogonal to the file-op ABIs. Self-contained C11 on pc_common.h; all
 * char* are UTF-8; version-checked via PcGetApiVersion.
 */

#ifndef PC_CONTRIB_H
#define PC_CONTRIB_H

#include <stdint.h>
#include "pc_common.h"

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Unified host-services table — the superset of the per-type service tables
 * (PtxHostServices / PfxHostServices). `host` is an opaque token passed back to
 * each callback. "get" callbacks fill a caller-allocated UTF-8 buffer and return
 * 1 on success / 0 if unavailable.
 */
typedef struct PcHostServices {
    void *host;

    /* Cursor & selection. */
    int  (*cursorPath)(void *host, char *out, int maxlen);        /* full path            */
    int  (*localCursorPath)(void *host, char *out, int maxlen);   /* local (VFS→temp)     */
    int  (*selectionCount)(void *host);
    int  (*selectionPath)(void *host, int index, char *out, int maxlen);

    /* Mutations (routed through the host's op engine). */
    void (*moveToTrash)(void *host, const char *const *paths, int count);
    void (*deletePermanently)(void *host, const char *const *paths, int count);
    void (*reloadActivePanel)(void *host);

    /* UI + composition. */
    void (*presentInfo)(void *host, const char *title, const char *message);
    /* Read a context value by key (same keys the declarative `when` sees), e.g.
       "cursorPath", "selectionCount", "panelScheme". 1 on success.

       Colour theme (F-338): "theme.id", "theme.isDark" ("1"/"0"), and colours as
       "#RRGGBB" — or "#RRGGBBAA" when translucent. A semantic set a plugin view can
       draw with directly: theme.background, theme.windowBackground, theme.text,
       theme.secondaryText, theme.accent, theme.separator,
       theme.selectionBackground, theme.selectionText, theme.markedText,
       theme.controlBackground, theme.controlText. Every host panel colour is also
       available raw as "theme.color.<name>" (the names a user theme file uses).
       Read them when building a view and again on PcNotifyThemeChanged; fall back to
       the system colours if a key is absent, which is what an older host returns. */
    int  (*getContext)(void *host, const char *key, char *out, int maxlen);
    /* Trigger any host or plugin command by id (composition). */
    void (*invokeCommand)(void *host, const char *commandId);
    /* Keychain-backed credential store (mode is a PC_CRYPT_* value). */
    int  (*crypt)(void *host, int mode, const char *store, char *password, int maxlen);

    /* Register a plugin WINDOW's own menu-bar menus so the host installs them
       while that window is key (like the built-in tool windows). Call this once,
       synchronously, while opening the window; `window` is an NSWindow pointer and
       `editMenu` / `contentMenu` are NSMenu pointers (the plugin owns them and
       keeps the window alive). The host removes them automatically when the window
       closes - no teardown call is needed. `title` names the content menu (may be
       NULL). */
    void (*registerToolWindow)(void *host, void *window, void *editMenu,
                               void *contentMenu, const char *title);

    void *parentWindow;   /* NSWindow* to present sheets over (may be NULL)          */
    void *parentView;     /* NSView* container for view commands (may be NULL)       */

    /* Resolve an internal link: navigate to a folder, or reveal/open a file, in
       the active panel. Appended at the end so the struct stays ABI-compatible
       with plugins built before this field existed. */
    void (*openPath)(void *host, const char *path);

    /* Open `path` in a SPECIFIC main panel (side: 0 = left, 1 = right). If `path`
       is a file, the host navigates to its parent folder and selects it.
       Appended at the end for ABI compatibility. */
    void (*openPathInPanel)(void *host, int side, const char *path);

    /* On-demand sidebar view. `presentSidebarView` mounts the plugin's view
       `viewId` in the sidebar immediately, rooted at `rootPath`; the view reads
       its root via getContext("sidebarViewRoot"). `dismissSidebarView` removes it
       again. Both appended at the end for ABI compatibility. */
    void (*presentSidebarView)(void *host, const char *viewId, const char *rootPath);
    void (*dismissSidebarView)(void *host, const char *viewId);

    /* Compare two files in the host's own compare window (F-416). `titleA`/`titleB`
       name the sides — a plugin usually has one side in a temp file whose name means
       nothing to the reader ("HEAD:src/app.swift" vs "Working tree"); NULL falls back
       to the file names. Appended at the end for ABI compatibility.

       This exists so a plugin does not have to bring its own diff view: the host
       already diffs two files with syntax highlighting, and a second implementation
       of that in the same application would be a second set of defects. */
    void (*compareFiles)(void *host, const char *pathA, const char *pathB,
                         const char *titleA, const char *titleB);

    /* Automation core — the host's file-manager tool engine (the same one that
       drives the MCP server), so a plugin (e.g. the AI assistant) can run tools
       under the host's permission policy. Each returns a host-allocated UTF-8
       JSON string the caller MUST release with `automationFree` (NULL on error).
       The host applies its configured autonomy policy: `automationInvoke` returns
       an outcome envelope {"status":"ok","payloadB64":..} / {"status":
       "needsConfirmation","plan":..,"token":..} / {"status":"refused","reason":..}
       / {"status":"failed","error":..}; a "needsConfirmation" is executed by
       calling `automationConfirm` with its token. `automationToolsJson` returns the
       tool catalogue (JSON array); `automationContextJson` the live UI context.
       IMPORTANT: these BLOCK until the async host op completes, so they must be
       called OFF the main thread. Appended at the end for ABI compatibility. */
    char *(*automationToolsJson)(void *host);
    char *(*automationContextJson)(void *host);
    char *(*automationInvoke)(void *host, const char *toolName, const char *argumentsJson);
    char *(*automationConfirm)(void *host, const char *token);
    void  (*automationFree)(void *host, char *str);

    /* Per-file comments — the `descript.ion` entry the host shows in its Comment
       column and edits with Ctrl+Z, mirrored into the macOS Finder comment for local
       files. A plugin cannot reach these on its own: they live in a file the host
       reads through the VFS, so they are available on network locations too, and the
       Finder mirror is the host's business.

       This exists so a note-taking plugin does not become a *second*, disconnected
       place where a note about a file can live. `getFileComment` writes at most
       `maxlen` bytes including the terminator and returns 1 on success (0 when there
       is no comment or the path is unknown); `setFileComment` with NULL or an empty
       string clears it. Both appended at the end for ABI compatibility. */
    int  (*getFileComment)(void *host, const char *path, char *out, int maxlen);
    void (*setFileComment)(void *host, const char *path, const char *comment);
    /* Progress and cancel for a command whose manifest entry says `"async": true` (F-422).

       `beginProgress` opens the host's own small progress window and returns an opaque handle (NULL when
       the host will not show one — a plugin must cope with that and simply not report). `updateProgress`
       sets the fraction (a value < 0 means indeterminate) and the line of text under the bar, and returns
       0 once the reader has pressed Cancel: that is the plugin's signal to stop and to call `endProgress`.
       `endProgress` closes the window; forgetting it leaves a window nobody can dismiss, so a plugin
       should call it on every path out, including its error paths.

       All three may be called from the command's background thread — which is where an asynchronous
       command runs — and only from a command declared asynchronous: on the main thread the window could
       not draw, because the thread that would draw it is the one inside the plugin.

       This exists because a `push` to an unreachable host blocked the main thread for the network's whole
       timeout, and an application that does not redraw is one the reader believes has died. Appended at
       the end for ABI compatibility. */
    void *(*beginProgress)(void *host, const char *title);
    int  (*updateProgress)(void *host, void *handle, double fraction, const char *text);
    void (*endProgress)(void *host, void *handle);
    /* Annotate a text file's lines in the host's editor gutter — blame, coverage, anything per line
       (F-426).

       `path` is the file; the host annotates an editor window already showing it and opens one if there is
       none, so a plugin does not have to know whether the reader has the file open. `annotations` is one
       record per source line, in order from line 1, separated by '\n', each record either `text` or
       `text\ttooltip`; an empty record leaves that line unannotated, and a list shorter than the file
       annotates only the lines it covers. Pass NULL or "" to clear.

       `title` names the column for the reader (a tooltip on the gutter). `commandId`, when not NULL, is a
       command the host invokes when an annotation is clicked; that command can read which line it was
       through `getContext("gutterAnnotationLine")`. Both may be NULL.

       Returns 1 when a window was annotated. This exists because the reference products draw blame in the
       gutter and a plugin cannot: the gutter belongs to the host's editor, and the alternative — a plugin
       shipping its own text view — is a second editor in the same application. Appended at the end for ABI
       compatibility. */
    int (*annotateLines)(void *host, const char *path, const char *annotations,
                         const char *title, const char *commandId);
    /* The rows of a pending plan, and confirming it with some of them struck out (F-450).

       `automationConfirm` agrees to a plan whole. That is the right shape for one action, and the wrong
       one for "clean up my Downloads", where the answer is usually "yes, except those three" — and
       without rows the only answers are all and nothing, so a user who wants all-but-three has to reject
       the plan and describe the exception in prose for the model to get right on a second attempt.

       `automationPlanItems` returns a JSON array of {"id":..,"text":..} for the token, or `[]` when the
       action cannot sensibly be divided (a single file, a `write_file`) — an empty array means "show the
       plan as you always did", not an error. `automationConfirmRejecting` takes a JSON array of the ids
       to leave out and returns the same outcome envelope as `automationConfirm`; rejecting every row is
       reported as a failure rather than as an action that touched nothing. The ids are the argument
       values the rows stand for, so what the user strikes out is what the tool skips.

       Appended at the end for ABI compatibility. */
    char *(*automationPlanItems)(void *host, const char *token);
    char *(*automationConfirmRejecting)(void *host, const char *token, const char *rejectedIdsJson);
} PcHostServices;

/* ---- Behavior entry points --------------------------------------------- */

/* ABI handshake. */
int   PcGetApiVersion(void);

/* Run the command declared with `commandId` (Info.plist PCContributions.commands).
   The plugin performs its work (may open its own windows) via `services`. */
void  PcRunCommand(const char *commandId, const PcHostServices *services);

/* Build the view declared with `viewId` for the named `containerId`
   (e.g. "sidebar", "preview"). Returns an NSView* as void*, or NULL. The plugin
   owns it until PcCloseView; `services` stays valid for the view's lifetime. */
void *PcMakeView(const char *viewId, const char *containerId, const PcHostServices *services);

/* Release a view previously returned by PcMakeView. */
void  PcCloseView(void *view);

/* Show the plugin's configuration UI (parentWindow may host a sheet). */
void  PcConfigure(void *parentWindow);

/* Execute a tool the plugin declared (Info.plist PCContributions.tools) by id, with
   JSON arguments, returning a host-allocated UTF-8 result string (the host frees it).
   Lets a plugin add tools to the assistant's catalogue; the host gates them by the
   declared capability and routes calls here. May be NULL if the plugin has no tools. */
char *PcInvokeTool(const char *toolName, const char *argumentsJson, const PcHostServices *services);

/* Notify the plugin that the host's colour theme changed, so any window or view it
   already opened can re-read the `theme.*` context keys (see getContext) and repaint.
   Optional — a plugin that does not export it is simply not called, and a plugin that
   does still works with an older host that never calls it. Called on the main thread.

   Views built with PcMakeView also receive PcNotifyView(view, "theme", <themeId>);
   this entry point exists for the plugin's OWN windows, which the host cannot address
   by view pointer. */
void  PcNotifyThemeChanged(void);

/* Notify a view previously returned by PcMakeView that a host context value
   changed, e.g. key "cursorPath" or "dir" (both UTF-8). Lets an embedded view
   (e.g. a sidebar panel) follow the active panel. Optional; called on the main
   thread. */
void  PcNotifyView(void *view, const char *key, const char *value);

#ifdef __cplusplus
}
#endif

#endif /* PC_CONTRIB_H */