Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show candidate using IMKCandidates #8

Merged
merged 8 commits into from
Oct 14, 2017
Merged

Show candidate using IMKCandidates #8

merged 8 commits into from
Oct 14, 2017

Conversation

mzp
Copy link
Owner

@mzp mzp commented Oct 14, 2017

IMKCandidates provides features to show candidates and select one of them.

candidate

👀Show candidate window

By show(IMKCandidatesLocationHint) of IMKCandidates, candidate window will be shown.

let candidates: IMKCandidates = IMKCandidates(server: server,
                                              panelType: kIMKSingleColumnScrollingCandidatePanel,
                                              styleType: kIMKMain)
candidates.show(kIMKLocateCandidatesBelowHint)

Window style is specified panelType: argument. Available options are kIMKSingleColumnScrollingCandidatePanel, KIMKScrollingGridCandidatePanel, KIMKSingleRowSteppingCandidatePanel .

kIMKSingleColumnScrollingCandidatePanel:

screen shot 2017-10-09 at 22 01 58

KIMKScrollingGridCandidatePanel:

screen shot 2017-10-09 at 22 05 29

KIMKSingleRowSteppingCandidatePanel :

screen shot 2017-10-09 at 22 07 53

📖Obtain candidates

Candidates are loaded when update() of IMKCandidates is called.

candidates.update()

Candidates are obtain by candidates(_:) of IMKInputController.

open class EmojiInputController: IMKInputController {
  ...
  open override func candidates(_ sender: Any!) -> [Any] {
    return ["a", "b", "c"]
  }
}

⚡️ Candidate window

Candidate window can be manipulated through keyboard. E.g. move selection candidate by up/down key.

To achieve this, we should pass key event to candidate window.

open class EmojiInputController: IMKInputController {
  ...
  open override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {
    if /* check if candidate window is shown */ {
      candidates.interpretKeyEvents([event])
      return true
    }
    return false
  }
  ...
}

👉Select candidate

When candidate's selection moved, IMKInputController method is called.

open class EmojiInputController: IMKInputController {
  ...
  open override func candidateSelected(_ candidateString: NSAttributedString!) {
      // when candidate selection moved
  }

  open override func candidateSelectionChanged(_ candidateString: NSAttributedString!) {
      // when candidate is selected
  }
  ...
}

🍎 Apple API internal information

Some method of IMKCandidates doesn't work. In my reverse engineering, following method has empty implementation, so doesn't work.

  • func showAnnotation(NSAttributedString!)
  • func attachChild(IMKCandidates!, toCandidate: Int, type: IMKStyleType)
  • func showChild()
  • func showSublist([Any]!, subListDelegate: Any!)
  • func detachChild(Int)
  • func hideChild()
  • func candidateIdentifier(atLineNumber: Int)
  • func candidateStringIdentifier(Any!)
  • func lineNumberForCandidate(withIdentifier: Int)
  • func selectCandidate(Int)
  • func selectCandidate(withIdentifier: Int)
  • func clearSelection()

I reported this to Apple (rdar://34944196, rdar://34911503)

✏️ Design decision: EmojiAutomaton

I enhanced emoji im automaton to show emoji candidates.

Manipulation

I decided to use following manipulation.

  1. Press : into composing mode.
  2. While inputing composing text, emoji candidates is shown.
  3. Press arrow key into select mode.
  4. When InputController's method is called, state changed into normal mode.

Store NSEvent

To pass original NSEvent to candidate window, I extended user input to store its.

public class UserInput {
    enum EventType {
        case input(text: String)
        case backspace
        case enter
        case colon
    }
    let eventType: EventType
    let originalEvent: NSEvent?
}

See e88bb84 for detail.

Extended automaton

Add new user input type for non-text key(e.g. arrow key) and candidate select.

    enum EventType {
        case input(text: String)
        case backspace
        case enter
        case colon
        case other // <- NEW
        case selected(candidate: String) // <- NEW
    }

Add new state for candidate selection.

public enum InputMethodState {
    case normal
    case composing
    case selection
}

Add new transition.

let mappings: [ActionMapping<InputMethodState, UserInput>] = [
  ...
  UserInput.typeof(.other) <|> .composing => .selection,
  UserInput.isSelected <|> .selection => .normal,
  { _ in true } <|> .selection => .selection
}

🚧 WIP area

  • Emoji dictionary
  • selectionKey support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant