Sheet 01 · Plugin SDK

Draft your own tools.

Hammer plugins add export formats, menu actions, text checks and command-line commands to the editor. Write them in Kotlin, Rust or C, and compile them to WebAssembly. They run in a sandbox and can only do what the writer allows.

SDK
Hammer Plugin SDK
v0.8.0
Target
WebAssembly · Extism
Host API
1
Kits
Kotlin · Rust · C
License
MIT
02 · Quick start

From nothing to a running plugin

Each language has a template: the same small plugin, ready to build, test, and make your own.

  1. 01
    Choose a language
  2. 02
    Start from the template The Kotlin template is a working plugin, a word count, with tests and a release workflow, built with the kit from Maven Central. To try it, clone it; to make it yours, choose Use this template on its GitHub page. Start from the template The Rust template is a working plugin, a word count, with tests and a release workflow, built with the kit from crates.io. To try it, clone it; to make it yours, choose Use this template on its GitHub page. Start from the template The C template is a working plugin, a word count, with tests and a release workflow, with the kit’s headers included. To try it, clone it; to make it yours, choose Use this template on its GitHub page.
  3. 03
    Build a plugin The Gradle wrapper runs its tests and builds the package; you need only a JDK. To make it your own plugin, rename it in settings.gradle.kts and manifest.toml and replace the example. The Kotlin kit covers the rest. cargo test runs its tests, and build.sh builds it for WebAssembly and packages it; you need rustup and zip. To make it your own plugin, rename it in Cargo.toml and manifest.toml and replace the example. The Rust kit covers the rest. test.sh runs its tests natively, and build.sh compiles it to WebAssembly with clang, no libc needed, and packages it; you need clang, lld and zip. To make it your own plugin, rename it in manifest.toml and replace the example. The C kit covers the rest.
  4. 04
    Install it in Hammer On desktop: Settings, Plugins, Install plugin. Pick the .hammerplugin, approve its permissions, and restart Hammer.
terminal
$ git clone https://github.com/Darkrock-Studios/hammer-plugin-kotlin
$ cd hammer-plugin-kotlin
$ ./gradlew jvmTest hammerPlugin
→ build/word-count.hammerplugin
$ git clone https://github.com/Darkrock-Studios/hammer-plugin-rust
$ cd hammer-plugin-rust
$ rustup target add wasm32-unknown-unknown
$ cargo test && ./build.sh
→ build/word-count.hammerplugin
$ git clone https://github.com/Darkrock-Studios/hammer-plugin-c
$ cd hammer-plugin-c
$ ./test.sh && ./build.sh
→ build/word-count.hammerplugin
A text check that underlines “very”
FIG. 2a
src/commonMain/kotlin/Very.kt
import hammer.DiagnoseRequest
import hammer.Diagnostic
import hammer.Fix
import hammer.Severity
import hammer.words

fun findVery(request: DiagnoseRequest): List<Diagnostic> = request.paragraphs.flatMapIndexed { paragraph, text ->
    text.words().filter { it.text.equals("very", ignoreCase = true) }.map { word ->
        val end = if (text.getOrNull(word.end) == ' ') word.end + 1 else word.end
        Diagnostic(paragraph, word.start, end, "“Very” rarely helps", listOf(Fix("", "Remove “very”")), Severity.Suggestion)
    }
}
src/wasmWasiMain/kotlin/Exports.kt
import hammer.diagnose
import kotlin.wasm.WasmExport

@WasmExport
fun diagnose(): Int = diagnose(::findVery)
src/lib.rs
use hammer::diagnose::{Diagnostic, Fix, Reply, Request, Severity};

fn check(request: &Request) -> Reply {
    let mut reply = Reply::default();
    for (paragraph, text) in request.paragraphs.iter().enumerate() {
        for word in hammer::words(text).filter(|it| it.text.eq_ignore_ascii_case("very")) {
            let end = word.end + text[word.end..].starts_with(' ') as usize;
            reply.diagnostics.push(Diagnostic {
                paragraph,
                start: word.start,
                end,
                message: "“Very” rarely helps".into(),
                fixes: vec![Fix::Labelled { replacement: String::new(), label: "Remove “very”".into() }],
                severity: Severity::Suggestion,
            });
        }
    }
    reply
}

#[unsafe(no_mangle)]
pub extern "C" fn diagnose() -> i32 {
    hammer::run(|| {
        let request: Request = hammer::input_json()?;
        Ok(serde_json::to_vec(&check(&request))?)
    })
}
very.c
#define HAMMER_IMPLEMENTATION
#include "hammer.h"
#include "hammer_diagnose.h"

int hammer_diagnose_begin(const HammerDiagnoseRequest *request) {
  (void)request;
  return 1;
}

void hammer_diagnose_paragraph(HammerText text) {
  uint64_t count;
  HammerWord *words = hammer_words(text, &count);
  for (uint64_t i = 0; i < count; i++) {
    if (!hammer_word_is(text, words[i], "very")) continue;
    uint64_t end = words[i].end;
    if (end < text.length && text.data[end] == ' ') end++;
    HammerFix fix = {"", 0, "Remove “very”"};
    hammer_suggest(words[i].start, end, "“Very” rarely helps", &fix, 1);
  }
}
manifest.toml FIG. 2b
id = "very"
name = "Very"
version = "0.1.0"
api = 1
description = "Underlines “very”, which rarely helps."

[[diagnostics]]
name = "very"
label = "Very"
03 · Anatomy

What’s in a plugin

A .hammerplugin file is a zip archive. The build tools make it for you.

manifest.toml
Manifest Names your plugin, says what it adds to Hammer, and lists the operations it needs. Writers see that list and approve it when they install your plugin.
plugin.wasm
Module Your compiled code, as WebAssembly. It follows the Extism plugin convention, and exports a function for each kind of extension it provides.
settings.toml
Settings Optional. Declare switches, numbers, text or choices, and Hammer gives writers a form for them in Settings. Your plugin gets the current values with every call.
locales/ resources/
Translations and files Optional. Files in locales/ translate your plugin’s labels, and resources/ can hold word lists or other data your plugin reads while it runs (up to 1,000 files and 64 MiB).
04 · Extension points

Where a plugin can attach

You declare each one in your manifest and handle it with a function in your plugin. One plugin can provide any mix of them.

05 · Publish

Getting it in front of writers

Step 1 Package ./gradlew hammerPlugin The template’s build.sh The template’s build.sh builds the .hammerplugin file, with your manifest, your compiled code, and any settings, translations and resources.
Step 2 Share the file Attach the file to a release and share the link. Writers install it from Settings, Plugins, and see what it asks permission for before it’s installed.
Step 3 Listed Coming A directory of reviewed plugins at plugins.hammer.ink, and later, installing them from inside Hammer.
Submissions open soon Read the official plugins