Skip to content

Development

weijianz edited this page Oct 11, 2026 · 6 revisions

中文

Code structure

A Swift package (Package.swift, swift-tools-version 6.0, macOS 14 or later).

Sources/
  AllInOneIMECore/        pure logic, no AppKit
    Composer.swift          two-level input state machine: pinyin, draft, @ commands and the command list, ⌃V and ⌘V, voice gestures, action key
    Command.swift           @ command definitions and parsing
    ActionKey.swift         action key
    Config.swift            config.json
    Prompt.swift            prompts
    Converter.swift         one request: reads config and credentials, calls Bedrock, parses the stream, caches
    CandidateParser.swift   parses the lines of the model's output
    BedrockClient.swift     ConverseStream client
    EventStream.swift       AWS event-stream decoding
    SigV4.swift             request signing
    AWSSharedConfig.swift   reads ~/.aws/credentials and ~/.aws/config
    RewriteStyle.swift      rewrite styles and their examples
    JargonLibrary.swift     黑话 (jargon) list
    InputSourceList.swift   adds to / removes from the user's input source list (AppleEnabledInputSources)
    LegacyData.swift        migrates data from AIPinyin's folders
    AppVersion.swift        version number
    Engine.swift, KeyMapping.swift, Language.swift
  AllInOneIMERime/        Swift wrapper for librime (Rime.swift)
  CRime/                  module map for the librime C API
  AllInOneIME/            the input method process (InputMethodKit)
    main.swift              entry point, plus --register / --disable / --status / --settings / --selftest
    InputController.swift   performs the effects Composer returns: text field, candidate panel, Bedrock, Terminal, clipboard, voice
    CandidatePanel.swift    candidate panel
    SettingsWindow.swift    settings window (SwiftUI)
    UIText.swift            interface text (Chinese / English)
    VoiceInput.swift        microphone and SpeechAnalyzer
    SecureInput.swift       secure input detection
    FileSearch.swift        search for @open
    TerminalLauncher.swift  @claude
    InputSourceRegistrar.swift  registers the input source, edits the input source list
    SelfTest.swift          make selftest
  AllInOneIMESettings/    "AllInOneIME Settings" launcher: opens the input method's settings window with --settings
  allinoneime-cli/        dev tool that runs @improve in Terminal
Tests/
  AllInOneIMECoreTests/   unit tests for Core; Fixtures/ holds event-stream test data
  AllInOneIMERimeTests/   tests with the real librime and ThirdParty/rime-data
Resources/                the two Info.plist files, localized strings, icons, entitlements, rime/default.custom.yaml
Scripts/                  screenshots, icons, real-app test, fixture generation
ThirdParty/               librime and rime-ice, downloaded by make deps (not in the repo)

For the input method's own development options, see ~/Library/Input Methods/AllInOneIME.app/Contents/MacOS/AllInOneIME --help.

make targets

Target What it does
make / make app Builds and packages build/AllInOneIME.app
make deps Downloads and verifies librime 1.16.1 and rime-ice 2026.06.30, precompiles the dictionaries (into ThirdParty/)
make build Builds the input method only (swift build)
make settings-app Packages build/AllInOneIME Settings.app
make install Packages both apps, installs them in ~/Library/Input Methods and ~/Applications, registers the input method and adds it to the input source list
make uninstall Disables it, removes it from the input source list, deletes both apps
make status Status of the installed input method; see Installation and Troubleshooting
make test Unit tests and Rime tests (swift test)
make selftest Drives the input method with a simulated text field, with real Rime and real Bedrock
make screenshots Runs make selftest, then generates the screenshots in docs/
make realtest Typing test through the installed input method, in a real text field of another app
make realtest-build / make realtest-run Only builds / only runs the real-app test program
make realtest-when-unlocked Runs realtest-run once, automatically, after the screen is unlocked (a temporary launchd job)
make realtest-cancel Cancels that job
make cli Builds allinoneime-cli
make icon Regenerates Resources/AppIcon.icns and the menu bar icon Resources/icon.tiff from Resources/AppIcon.png
make clean Deletes .build and build
make distclean Also deletes ThirdParty

Variables: CONFIG (default release), SIGN_IDENTITY (default Apple Development; ad-hoc signing if it isn't found), OUT (the selftest's output folder, default /tmp/allinoneime-selftest), REALTEST_OUT (default ~/Library/Logs/AllInOneIME/realtest).

make, make app, make selftest and make screenshots all leave build/AllInOneIME.app behind. It has the same bundle ID as the installed copy, so macOS may launch it instead of the installed one; delete it when you're done (make install moves it away).

Tests

make test: swift test, two sets of tests.

  • AllInOneIMECoreTests: pure logic, with a fake pinyin engine and a stubbed network (URLProtocol); no network access, no AWS needed. Among them, AppVersionTests checks that the versions in the two Info.plist files match AppVersion.string.
  • AllInOneIMERimeTests: uses the real librime and ThirdParty/rime-data (make test runs make deps first).

make selftest: packages build/AllInOneIME.app, then runs AllInOneIME --selftest, which drives the real input controller with a simulated text field: real Rime, real Bedrock requests (with your config.json and AWS configuration; this costs a little), Spotlight search; the voice part tests on-device recognition with speech synthesized by say (skipped without SpeechAnalyzer). It doesn't touch the clipboard, the microphone or the sentence mode setting, and doesn't actually open files or Terminal; the settings window is tested on a temporary copy of the config, leaving your config.json unchanged. At the end it prints SELFTEST PASSED or SELFTEST FAILED (n), and writes the screenshots to OUT.

make realtest: tests the installed input method, so make install first. It starts a separate app with a real NSTextView and types through the system's text input path; it switches input sources for the test and back afterwards, and restores the sentence mode switch too. The screen must be unlocked; the test window holds the foreground for about a minute, so don't type meanwhile. Results are in ~/Library/Logs/AllInOneIME/realtest/output.txt, with REALTEST PASSED when it passes. When the screen is locked, use make realtest-when-unlocked; its log is ~/Library/Logs/AllInOneIME/realtest-watch.log.

allinoneime-cli: runs @improve in Terminal with the same Core code as the input method, reading the same config.json; options override it:

make cli
.build/release/allinoneime-cli --styles 简洁,黑话 "我今天有点不舒服"
.build/release/allinoneime-cli --output zh "this bug is blocker"
.build/release/allinoneime-cli --bench     # 10 built-in sentences, reports latency

(简洁,黑话 are the Concise and Jargon styles; 我今天有点不舒服 means "I'm not feeling well today".)

Other options: --profile, --region, --model, --jargon FILE, --raw (prints the model's raw output), --dump FILE (saves the raw event-stream response, for test data), --version.

Screenshots

make screenshots is make selftest followed by swift Scripts/make-readme-images.swift "$(OUT)" docs. The selftest renders the candidate panel and the settings window, and the script combines them into the images the README uses:

Image Source
docs/demo.png 1b-sentence-pinyin.png + 4-final-light.png
docs/english.png 7-english-light.png + 7b-chinese-output.png
docs/voice.png 8-voice.png
docs/commands.png 10-palette.png + 11-question.png
docs/open.png 12-open.png + 12b-open-path.png
docs/panel-dark.png 4-final-dark.png
docs/settings.png 6-settings.png

The English screenshots are in docs/en, also made by make screenshots.

The content comes from real Bedrock answers and differs each time, so look it over before committing. It needs a working Bedrock setup and the speech models (if a render is missing, the script exits with an error). Delete build/AllInOneIME.app afterwards.

Conventions

  • The bundle ID and input source ID stay com.aipinyin.inputmethod.AIPinyin: macOS uses it to remember the added input method, microphone access and preferences, so changing it would make users add and authorize it again. It is also the log subsystem.
  • When you change the wording of a prompt, increment Prompt.version (@improve) or Prompt.commandVersion (@question) as the comments in Prompt.swift say; they are part of the cache key.

Release checklist

  1. Change the version in all three places together: CFBundleShortVersionString in Resources/Info.plist and Resources/Settings-Info.plist, and AppVersion.string in Sources/AllInOneIMECore/AppVersion.swift. AppVersionTests in make test checks that the three match.
  2. Update CHANGELOG.md.
  3. Update README.md and CHANGELOG.md (English) together with the Chinese README.zh-CN.md and CHANGELOG.zh-CN.md.
  4. Update the English wiki pages along with the Chinese ones.
  5. make screenshots, then check the images in docs/ and the English screenshots in docs/en.
  6. Delete build/AllInOneIME.app: rm -rf build/AllInOneIME.app.

Clone this wiki locally