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

> Learn how Hallelujah IM selects candidates using number keys 1-9, Enter, and Space. Discover the commitComposition and commitCompositionWithoutSpace methods for efficient input.

- Repository: [dongyuwei/hallelujahim](https://github.com/dongyuwei/hallelujahim)
- Tags: how-to-guide
- Published: 2026-02-28

---

**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:

```objc
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.

```objc
// 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.

```objc
// 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 buffering** – `onKeyEvent:` 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. **Finalization** – **Enter** 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.