How to Select Candidates Using Number Keys 1‑9, Enter, and Space in Hallelujah IM

Hallelujah IM selects candidates by mapping digit keys 1‑9 to indices in the _candidates array, then commits the chosen word immediately with commitComposition: (Space) or commitCompositionWithoutSpace: (Enter).

The open‑source input method dongyuwei/hallelujahim provides a streamlined typing experience where users generate phonetic or abbreviated candidates and finalize them with minimal keystrokes. Understanding the keyboard interaction model in src/InputController.mm reveals how the engine translates number pad selections into committed text.

Candidate Generation in ConversionEngine

Before selection can occur, the system must populate the candidate list. The ConversionEngine class in src/ConversionEngine.mm (lines 182‑200) generates suggestions via the getCandidates: method.

The pipeline lower‑cases the input buffer, expands user‑defined substitutions, queries the Marisa trie for prefix matches, and sorts results by frequency. When the trie returns no matches, the engine falls back to spell‑checker and phonex suggestions. The resulting array is stored in _candidates, a mutable array exposed to the controller.

Number Key Selection (1‑9) Logic

When the candidate window is visible (isCandidatesVisible), the onKeyEvent: method intercepts digit keys ('0''9') in src/InputController.mm (lines 153‑168).

The controller calculates the target index using zero‑based arithmetic:

if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:ch]) {
    if (isCandidatesVisible) {
        int pressedNumber = characters.intValue;
        NSString *candidate;
        int pageSize = 9;
        
        if (_currentCandidateIndex <= pageSize) {
            candidate = _candidates[pressedNumber - 1];
        } else {
            candidate = _candidates[pageSize * (_currentCandidateIndex / pageSize - 1)
                                   + (_currentCandidateIndex % pageSize) + pressedNumber - 1];
        }
        
        [self cancelComposition];
        [self setComposedBuffer:candidate];
        [self setOriginalBuffer:candidate];
        [self commitComposition:sender];
        return YES;
    }
}

The selected candidate replaces the composition buffer and is committed instantly.

Pagination Handling

The logic distinguishes between single‑page and multi‑page lists. When _currentCandidateIndex exceeds the pageSize of 9, the index calculation offsets by the current page (pageSize * (page‑1)) to ensure digit keys map correctly across pagination boundaries.

Committing Candidates with Enter and Space

After a candidate is chosen—or when the user accepts the raw buffer without selecting from the list—two keys handle finalization differently.

Enter Key Behavior

Pressing Enter (KEY_RETURN) commits the composition without appending a trailing space. This is useful when the user already typed a separator or wants to continue typing immediately after the word.

// src/InputController.mm – lines 106‑112
if (keyCode == KEY_RETURN && hasBufferedText) {
    [self commitCompositionWithoutSpace:sender];
}

Space Key Behavior

Pressing Space (KEY_SPACE) commits the composition with a trailing space (default behavior), allowing rapid entry of multiple words.

// src/InputController.mm – lines 98‑104
if (keyCode == KEY_SPACE && hasBufferedText) {
    [self commitComposition:sender];
}

Complete Selection Flow

The end‑to‑end candidate selection pipeline works as follows:

  1. Input bufferingonKeyEvent: appends keystrokes to originalInput and triggers [sharedCandidates updateCandidates].
  2. List population – The candidates: method invokes [engine getCandidates:originalInput] and stores the result in _candidates.
  3. Window display[sharedCandidates show:kIMKLocateCandidatesBelowHint] renders the list.
  4. Digit selection – While isCandidatesVisible is true, keys 1‑9 map to _candidates indices and immediately commit via commitComposition:.
  5. FinalizationEnter or Space handles cases where the user accepts the buffer without explicit candidate selection, differing only in whitespace handling.

Summary

  • src/ConversionEngine.mm generates ranked candidates using trie lookups, frequency sorting, and fallback spell‑checking.
  • src/InputController.mm manages the UI state, intercepts digit keys 1‑9, and maps them to the _candidates array with pagination‑aware index math.
  • Number keys 1‑9 commit the selected candidate immediately, replacing the composition buffer.
  • Enter (KEY_RETURN) finalizes text without trailing spaces, while Space (KEY_SPACE) appends a space after commitment.
  • The default page size is 9 candidates, and the selection logic adjusts indexing when multiple pages exist.

Frequently Asked Questions

How does Hallelujah IM handle candidate pagination?

When more than 9 candidates exist, the controller tracks _currentCandidateIndex and calculates the effective index as pageSize * (page‑1) + pressedNumber‑1. This ensures digits 1‑9 consistently map to the visible slice of the _candidates array regardless of which page is displayed.

What happens when I press Enter vs Space?

Pressing Enter invokes commitCompositionWithoutSpace:, inserting the candidate without trailing whitespace. Pressing Space invokes commitComposition:, which inserts the candidate followed by a space. Both actions clear the composition buffer and hide the candidate window.

Where is the candidate selection logic implemented?

The primary selection logic resides in src/InputController.mm within the onKeyEvent: method (lines 153‑168). Candidate generation occurs in src/ConversionEngine.mm via getCandidates: (lines 182‑200). These files define the complete pipeline from keystroke to committed text.

Can I customize the number of candidates shown per page?

The source code defines pageSize as a fixed integer (9) within the digit‑key handling block in src/InputController.mm. Modifying this constant requires recompiling the project, as the value is hardcoded for the 1‑9 number key mapping.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →