Refactors the input controller.

This commit is contained in:
zonble 2022-01-27 22:54:53 +08:00
parent 6fe2fc59f3
commit 1ad9e23918
7 changed files with 1000 additions and 1094 deletions

View File

@ -52,7 +52,7 @@
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
D456576E279E4F7B00DF6BC9 /* KeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandler.swift */; };
D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; };
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
@ -206,7 +206,7 @@
D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; };
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
D456576D279E4F7B00DF6BC9 /* KeyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler.swift; sourceTree = "<group>"; };
D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerInput.swift; sourceTree = "<group>"; };
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
@ -304,13 +304,13 @@
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
D461B791279DAC010070E734 /* InputState.swift */,
D456576D279E4F7B00DF6BC9 /* KeyHandler.swift */,
D47B92BF27972AC800458394 /* main.swift */,
D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */,
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
D44FB74427915555003C80A6 /* Preferences.swift */,
D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */,
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */,
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */,
D47B92BF27972AC800458394 /* main.swift */,
6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */,
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */,
);
@ -708,7 +708,7 @@
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
D456576E279E4F7B00DF6BC9 /* KeyHandler.swift in Sources */,
D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */,
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,

View File

@ -53,11 +53,6 @@ namespace Formosa {
void setJoinSeparator(const string& separator);
const string joinSeparator() const;
// TODO: Remove these later.
size_t markerCursorIndex() const;
// TODO: Remove these later.
void setMarkerCursorIndex(size_t inNewIndex);
vector<string> readingsAtRange(size_t begin, size_t end) const;
Grid& grid();
@ -71,7 +66,6 @@ namespace Formosa {
static const size_t MaximumBuildSpanLength = 6;
size_t m_cursorIndex;
size_t m_markerCursorIndex;
vector<string> m_readings;
Grid m_grid;
@ -82,14 +76,12 @@ namespace Formosa {
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM)
: m_LM(inLM)
, m_cursorIndex(0)
, m_markerCursorIndex(SIZE_MAX)
{
}
inline void BlockReadingBuilder::clear()
{
m_cursorIndex = 0;
m_markerCursorIndex = SIZE_MAX;
m_readings.clear();
m_grid.clear();
}
@ -108,21 +100,6 @@ namespace Formosa {
{
m_cursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex;
}
inline size_t BlockReadingBuilder::markerCursorIndex() const
{
return m_markerCursorIndex;
}
inline void BlockReadingBuilder::setMarkerCursorIndex(size_t inNewIndex)
{
if (inNewIndex == SIZE_MAX) {
m_markerCursorIndex = SIZE_MAX;
return;
}
m_markerCursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex;
}
inline void BlockReadingBuilder::insertReadingAtCursor(const string& inReading)
{

View File

@ -27,12 +27,12 @@
#import "Gramambular.h"
#import "McBopomofoLM.h"
#import "UserOverrideModel.h"
#import "McBopomofo-Swift.h"
@interface McBopomofoInputMethodController : IMKInputController
{
@interface McBopomofoInputMethodController : IMKInputController {
@private
// the reading buffer that takes user input
Formosa::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer;
Formosa::Mandarin::BopomofoReadingBuffer *_bpmfReadingBuffer;
// language model
McBopomofo::McBopomofoLM *_languageModel;
@ -41,26 +41,19 @@
McBopomofo::UserOverrideModel *_userOverrideModel;
// the grid (lattice) builder for the unigrams (and bigrams)
Formosa::Gramambular::BlockReadingBuilder* _builder;
Formosa::Gramambular::BlockReadingBuilder *_builder;
// latest walked path (trellis) using the Viterbi algorithm
std::vector<Formosa::Gramambular::NodeAnchor> _walkedNodes;
// the latest composing buffer that is updated to the foreground app
NSMutableString *_composingBuffer;
NSInteger _latestReadingCursor;
// the current text input client; we need to keep this when candidate panel is on
id _currentCandidateClient;
// a special deferred client for Terminal.app fix
id _currentDeferredClient;
// current available candidates
NSMutableArray *_candidates;
// current input mode
NSString *_inputMode;
}
- (BOOL)handleInput:(KeyHandlerInput *)input
state:(InputState *)state
stateCallback:(void (^)(InputState *))stateCallback
candidateSelectionCallback:(void (^)(void))candidateSelectionCallback
errorCallback:(void (^)(void))errorCallback;
- (void)handleState:(InputState *)newState
client:(id)client;
@end

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,18 @@
import Cocoa
/// Represents the states for the input controller.
class InputState: NSObject {
}
/// Represents that the input controller is deactive.
class InputStateDeactive: InputState {
}
/// Represents that the composing buffer is empty.
class InputStateEmpty: InputState {
}
/// Represents that the input controller is committing text into client app.
class InputStateCommitting: InputState {
@objc private(set) var poppedText: String = ""
@ -18,17 +22,28 @@ class InputStateCommitting: InputState {
}
}
class InputStateInputting: InputState {
/// Represents that the composing buffer is not empty.
class InputStateNotEmpty: InputState {
@objc private(set) var composingBuffer: String = ""
@objc private(set) var cursorIndex: UInt = 0
@objc var poppedText: String = ""
@objc init(composingBuffer: String, cursorIndex: UInt) {
self.composingBuffer = composingBuffer
self.cursorIndex = cursorIndex
}
}
@objc var attributedSting: NSAttributedString {
/// Represents that the user is inputting text.
class InputStateInputting: InputStateNotEmpty {
@objc var bpmfReading: String = ""
@objc var bpmfReadingCursotIndex: UInt8 = 0
@objc var poppedText: String = ""
@objc override init(composingBuffer: String, cursorIndex: UInt) {
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
}
@objc var attributedString: NSAttributedString {
let attrs: [NSAttributedString.Key : Any] = [
.underlineStyle: NSUnderlineStyle.single,
.markedClauseSegment: 0
@ -38,13 +53,37 @@ class InputStateInputting: InputState {
}
}
class InputStateMarking: InputStateInputting {
private let kMinMarkRangeLength = 2
private let kMaxMarkRangeLength = 6
/// Represents that the user is marking a range in the composing buffer.
class InputStateMarking: InputStateNotEmpty {
@objc private(set) var readings: [String] = []
@objc private(set) var markerIndex: UInt = 0
@objc private(set) var markedRange: NSRange = NSRange(location: 0, length: 0)
@objc var tooltip: String {
return ""
if Preferences.phraseReplacementEnabled {
return NSLocalizedString("Phrase replacement mode is on. Not suggested to add phrase in the mode.", comment: "")
}
if Preferences.chineseConversionStyle == 1 && Preferences.chineseConversionEnabled {
return NSLocalizedString("Model based Chinese conversion is on. Not suggested to add phrase in the mode.", comment: "")
}
if markedRange.length == 0 {
return ""
}
let text = (composingBuffer as NSString).substring(with: markedRange)
if markedRange.length < kMinMarkRangeLength {
return String(format: NSLocalizedString("You are now selecting \"%@\". You can add a phrase with two or more characters.", comment: ""), text)
} else if (markedRange.length > kMaxMarkRangeLength) {
return String(format: NSLocalizedString("You are now selecting \"%@\". A phrase cannot be longer than %d characters.", comment: ""), text, kMaxMarkRangeLength)
}
return String(format: NSLocalizedString("You are now selecting \"%@\". Press enter to add a new phrase.", comment: ""), text)
}
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt) {
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
self.markerIndex = markerIndex
@ -53,7 +92,7 @@ class InputStateMarking: InputStateInputting {
self.markedRange = NSMakeRange(Int(begin), Int(end - begin))
}
@objc override var attributedSting: NSAttributedString {
@objc var attributedString: NSAttributedString {
let attributedSting = NSMutableAttributedString(string: composingBuffer)
attributedSting.setAttributes([
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single,
@ -72,7 +111,23 @@ class InputStateMarking: InputStateInputting {
}
}
class InputStateChoosingCandidate: InputStateInputting {
var markingRang: NSRange = NSRange(location: 0, length: 0)
var candidates: [String] = []
/// Represents that the user is choosing in a candidates list.
class InputStateChoosingCandidate: InputStateNotEmpty {
@objc private(set) var candidates: [String] = []
@objc private(set) var useVerticalMode: Bool = false
@objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) {
self.candidates = candidates
self.useVerticalMode = useVerticalMode
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
}
@objc var attributedString: NSAttributedString {
let attrs: [NSAttributedString.Key : Any] = [
.underlineStyle: NSUnderlineStyle.single,
.markedClauseSegment: 0
]
let attributedSting = NSAttributedString(string: composingBuffer, attributes: attrs)
return attributedSting
}
}

View File

@ -1,65 +0,0 @@
import Cocoa
@objc enum KeyCode: UInt16 {
case none = 0
case enter = 76
case up = 126
case down = 125
case left = 123
case right = 124
case pageUp = 116
case pageDown = 121
case home = 115
case end = 119
case delete = 117
}
class KeyHandlerInput: NSObject {
private (set) var event: NSEvent
private (set) var useVerticalMode: Bool
@objc var inputText: String? {
event.characters
}
@objc var charCode: UInt16 {
guard let inputText = inputText, inputText.count > 0 else {
return 0
}
let first = inputText[inputText.startIndex].utf16.first!
return first
}
@objc var keyCode: UInt16 {
event.keyCode
}
@objc var flags: NSEvent.ModifierFlags {
event.modifierFlags
}
@objc var cursorForwardKey: KeyCode {
useVerticalMode ? .down : .right
}
@objc var cursorBackwardKey: KeyCode {
useVerticalMode ? .up : .left
}
@objc var extraChooseCandidateKey: KeyCode {
useVerticalMode ? .left : .down
}
@objc var absorbedArrowKey: KeyCode {
useVerticalMode ? .right : .up
}
@objc var verticalModeOnlyChooseCandidateKey: KeyCode {
useVerticalMode ? absorbedArrowKey : .none
}
init(event: NSEvent, isVerticalMode: Bool) {
self.event = event
self.useVerticalMode = isVerticalMode
}
}

View File

@ -0,0 +1,61 @@
import Cocoa
@objc enum KeyCode: UInt16 {
case none = 0
case enter = 76
case up = 126
case down = 125
case left = 123
case right = 124
case pageUp = 116
case pageDown = 121
case home = 115
case end = 119
case delete = 117
}
@objc class KeyHandlerInput: NSObject {
@objc private (set) var event: NSEvent
@objc private (set) var useVerticalMode: Bool
@objc var inputText: String? {
event.characters
}
@objc var keyCode: UInt16 {
event.keyCode
}
@objc var flags: NSEvent.ModifierFlags {
event.modifierFlags
}
@objc private (set) var charCode: UInt16
@objc private (set) var cursorForwardKey: KeyCode
@objc private (set) var cursorBackwardKey: KeyCode
@objc private (set) var extraChooseCandidateKey: KeyCode
@objc private (set) var absorbedArrowKey: KeyCode
@objc private (set) var verticalModeOnlyChooseCandidateKey: KeyCode
@objc private (set) var emacsKey: McBopomofoEmacsKey
@objc init(event: NSEvent, isVerticalMode: Bool) {
self.event = event
self.useVerticalMode = isVerticalMode
let charCode: UInt16 = {
guard let inputText = event.characters, inputText.count > 0 else {
return 0
}
let first = inputText[inputText.startIndex].utf16.first!
return first
}()
self.charCode = charCode
self.emacsKey = EmacsKeyHelper.detect(charCode: charCode, flags: event.modifierFlags)
self.cursorForwardKey = useVerticalMode ? .up : .left
self.cursorBackwardKey = useVerticalMode ? .up : .left
self.extraChooseCandidateKey = useVerticalMode ? .left : .down
self.absorbedArrowKey = useVerticalMode ? .right : .up
self.verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none
super.init()
}
}