Skip to content

Commit

Permalink
MainAssembly // Protocolize both SessionCtl and InputHandler.
Browse files Browse the repository at this point in the history
- This defibrillates the unit tests of the MainAssembly.
  • Loading branch information
ShikiSuen committed Feb 3, 2025
1 parent 829a56c commit 4e2ba4b
Show file tree
Hide file tree
Showing 28 changed files with 1,139 additions and 804 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.

import InputMethodKit

// MARK: - IMKInputController + IMKInputControllerProtocol

extension IMKInputController: IMKInputControllerProtocol {}

// MARK: - IMKInputControllerProtocol

public protocol IMKInputControllerProtocol: IMKStateSetting {
/// Activates the input method.
func activateServer(_ sender: Any!)

/// Deactivates the input method.
func deactivateServer(_ sender: Any!)

/// Returns an object value whose key is tag. The returned object should be autoreleased.
func value(forTag tag: Int, client sender: Any!) -> Any!

/// Sets the tagged value to the object specified by value.
func setValue(_ value: Any!, forTag tag: Int, client sender: Any!)

/// This is called to obtain the input method's modes dictionary.
/// Typically, this is called to build the text input menu. By calling the input method rather than reading the modes from the info.plist, the input method can dynamically modify the modes supported. The returned dictionary should be an autoreleased object.
func modes(_ sender: Any!) -> [AnyHashable: Any]!

/// Returns an unsigned integer containing a union of event masks (see NSEvent.h).
/// A client will check with an input method to see if an event is supported by calling this method. The default implementation returns NSKeyDownMask. If your input method only handles key downs, the InputMethodKit provides default mouse handling.
/// If there's an active composition area and the user clicks outside of it, the InputMethodKit will send your input method a `commitComposition:` message. This happens only for input methods returning just NSKeyDownMask.
func recognizedEvents(_ sender: Any!) -> Int

/// Looks for a nib file containing a windowController class and a preferences utility. If found, the panel is displayed.
/// To use this method, include a menu item whose action is `showPreferences:` in your input method's menu. The method will be called automatically when the user selects the item in the Text Input Menu.
/// The default implementation looks for a nib file named `preferences.nib`. If found, a `windowController` class is allocated and the nib is loaded.
func showPreferences(_ sender: Any!)

/// Called to inform the controller that the composition has changed.
/// This method will call the protocol method `composedString:` to obtain the current composition and send it to the client using `setMarkedText:`.
func updateComposition()

/// Stops the current composition and replaces marked text with the original text.
/// Calls the `originalString` method to obtain the original text and sends it to the client via `IMKInputSession`'s `insertText:`.
func cancelComposition()

/// Called to obtain a dictionary of text attributes.
/// The default implementation returns an empty dictionary. You should override this method if your input method wants to provide font or glyph information. The returned object should be an autoreleased object.
func compositionAttributes(at range: NSRange) -> NSMutableDictionary!

/// Returns where the selection should be placed inside marked text.
/// This method is called by `updateComposition:` to obtain the selection range for marked text. The default implementation sets the selection range at the end of the marked text.
func selectionRange() -> NSRange

/// Returns the range in the client document that text should replace.
/// This method is called by `updateComposition` to obtain the range where marked text should be placed. The default implementation returns `NSNotFound`, indicating that the marked text should be placed at the current insertion point.
/// Input methods wishing to insert marked text somewhere other than the insertion point should override this method.
func replacementRange() -> NSRange

/// Returns a dictionary of text attributes that can be used to mark a range of an attributed string that is going to be sent to a client.
/// This utility function can be called by input methods to mark each range (i.e. clause) of marked text. The `style` parameter should be one of the following values: `kTSMHiliteSelectedRawText`, `kTSMHiliteConvertedText`, or `kTSMHiliteSelectedConvertedText`.
/// The default implementation calls `compositionAttributesAtRange:` to obtain extra attributes and adds underline and underline color information for the specified style.
func mark(forStyle style: Int, at range: NSRange) -> [AnyHashable: Any]!

/// Called to pass commands that are not part of text input.
/// The default implementation checks if the controller responds to the selector and sends the message `performSelector:withObject:` with the `infoDictionary`.
func doCommand(by aSelector: Selector!, command infoDictionary: [AnyHashable: Any]!)

/// Called to inform the input method that any visible UI should be closed.
func hidePalettes()

/// Returns a menu of input method specific commands.
/// This method is called whenever the menu needs to be redrawn, allowing input methods to update the menu to reflect their current state.
func menu() -> NSMenu!

/// Returns the input controller's delegate object. The returned object is autoreleased.
func delegate() -> Any!

/// Sets the input controller's delegate object.
func setDelegate(_ newDelegate: Any!)

/// Returns the server object which is managing this input controller. The returned `IMKServer` is autoreleased.
func server() -> IMKServer!

/// Returns this controller's client object, which conforms to the `IMKTextInput` protocol. The returned object is autoreleased.
func client() -> (any IMKTextInput & NSObjectProtocol)!

/// Called to notify the input controller that it is about to be closed.
@available(macOS 10.7, *)
func inputControllerWillClose()

/// Called when a user selects an annotation in a candidate window.
/// When a candidate window is displayed and the user selects an annotation, the selected annotation is sent to the input controller along with the selected candidate string.
func annotationSelected(_ annotationString: NSAttributedString!, forCandidate candidateString: NSAttributedString!)

/// Informs the input controller that the current candidate selection has changed.
/// The `candidateString` is the updated selection, but it's not the final selection yet.
func candidateSelectionChanged(_ candidateString: NSAttributedString!)

/// Called when a new candidate has been finally selected.
/// The `candidateString` is the user's final choice, and the candidate window has been closed by the time this method is called.
func candidateSelected(_ candidateString: NSAttributedString!)
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ extension VwrClientListMgr {

// 預先填寫近期用過威注音輸入法的客體軟體,最多二十筆。
theTextView.textContainer?.textView?.string = {
let recentClients = SessionCtl.recentClientBundleIdentifiers.keys.compactMap {
let recentClients = InputSession.recentClientBundleIdentifiers.keys.compactMap {
PrefMgr.shared.clientsIMKTextInputIncapable.keys.contains($0) ? nil : $0
}
return recentClients.sorted().joined(separator: "\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.

import IMKUtils
import InputMethodKit
import LangModelAssembly
import Shared
Expand Down Expand Up @@ -64,12 +65,12 @@ public struct IMEState: IMEStateProtocol {
fileprivate init(displayTextSegments: [String], cursor: Int) {
// 注意資料的設定順序,一定得先設定 displayTextSegments。
data.displayTextSegments = displayTextSegments.map {
if !SessionCtl.isVerticalTyping { return $0 }
if !InputSession.isVerticalTyping { return $0 }
guard PrefMgr.shared.hardenVerticalPunctuations else { return $0 }
var neta = $0
ChineseConverter.hardenVerticalPunctuations(
target: &neta,
convert: SessionCtl.isVerticalTyping
convert: InputSession.isVerticalTyping
)
return neta
}
Expand Down Expand Up @@ -218,7 +219,7 @@ extension IMEState {
set { data.tooltip = newValue }
}

public func attributedString(for session: IMKInputController) -> NSAttributedString {
public func attributedString(for session: IMKInputControllerProtocol) -> NSAttributedString {
switch type {
case .ofMarking: return data.attributedStringMarking(for: session)
case .ofCandidates where cursor != marker: return data.attributedStringMarking(for: session)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.

import IMKUtils
import InputMethodKit
import Shared
import Tekkon
Expand Down Expand Up @@ -134,7 +135,7 @@ public struct IMEStateData: IMEStateDataProtocol {
// MARK: - AttributedString 生成器

extension IMEStateData {
public func attributedStringNormal(for session: IMKInputController) -> NSAttributedString {
public func attributedStringNormal(for session: IMKInputControllerProtocol) -> NSAttributedString {
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
Expand All @@ -153,7 +154,7 @@ extension IMEStateData {
return attributedString
}

public func attributedStringMarking(for session: IMKInputController) -> NSAttributedString {
public func attributedStringMarking(for session: IMKInputControllerProtocol) -> NSAttributedString {
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
Expand Down Expand Up @@ -188,7 +189,7 @@ extension IMEStateData {
return attributedString
}

public func attributedStringPlaceholder(for session: IMKInputController) -> NSAttributedString {
public func attributedStringPlaceholder(for session: IMKInputControllerProtocol) -> NSAttributedString {
let attributes: [NSAttributedString.Key: Any]
= session.mark(forStyle: kTSMHiliteSelectedRawText, at: .zero)
as? [NSAttributedString.Key: Any]
Expand All @@ -212,7 +213,7 @@ extension IMEStateData {
var subNeta = subNeta
if !PrefMgr.shared.cassetteEnabled {
if PrefMgr.shared.showHanyuPinyinInCompositionBuffer,
PrefMgr.shared.alwaysShowTooltipTextsHorizontally || !SessionCtl.isVerticalTyping {
PrefMgr.shared.alwaysShowTooltipTextsHorizontally || !InputSession.isVerticalTyping {
// 恢復陰平標記->注音轉拼音->轉教科書式標調
subNeta = Tekkon.restoreToneOneInPhona(target: subNeta)
subNeta = Tekkon.cnvPhonaToHanyuPinyin(targetJoined: subNeta)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.

import AppKit
import CandidateWindow
import LangModelAssembly
import Megrez
import Shared
import Tekkon

// MARK: - InputHandler

/// InputHandler 輸入調度模組。
public final class InputHandler: InputHandlerProtocol {
// MARK: Lifecycle

/// 初期化。
public init(
lm: LMAssembly.LMInstantiator,
pref: PrefMgrProtocol,
errorCallback: ((_ message: String) -> ())? = nil,
notificationCallback: ((_ message: String) -> ())? = nil
) {
self.prefs = pref
self.currentLM = lm
self.errorCallback = errorCallback
self.notificationCallback = notificationCallback
/// 同步組字器單個詞的幅位長度上限。
Megrez.Compositor.maxSpanLength = prefs.maxCandidateLength
/// 組字器初期化。因為是首次初期化變數,所以這裡不能用 ensureCompositor() 代勞。
self.compositor = Megrez.Compositor(with: currentLM, separator: "-")
/// 注拼槽初期化。
ensureKeyboardParser()
}

// MARK: Public

public static var keySeparator: String { Megrez.Compositor.theSeparator }

/// 委任物件 (SessionCtl),以便呼叫其中的函式。
public var session: (SessionProtocol & CtlCandidateDelegate)?
public var prefs: PrefMgrProtocol
public var errorCallback: ((String) -> ())?
public var notificationCallback: ((String) -> ())?

/// 用來記錄「叫出選字窗前」的游標位置的變數。
public var backupCursor: Int?
/// 當前的打字模式。
public var currentTypingMethod: TypingMethod = .vChewingFactory

/// 半衰模組的衰減指數
public let kEpsilon: Double = 0.000_001

public var strCodePointBuffer = "" // 內碼輸入專用組碼區
public var calligrapher = "" // 磁帶專用組筆區
public var composer: Tekkon.Composer = .init() // 注拼槽
public var compositor: Megrez.Compositor // 組字器

public var currentLM: LMAssembly.LMInstantiator {
didSet {
compositor.langModel = .init(withLM: currentLM)
clear()
}
}
}
Loading

0 comments on commit 4e2ba4b

Please sign in to comment.