diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift
index caec6545..b2e3125a 100644
--- a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift
+++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift
@@ -112,7 +112,7 @@ extension KeyHandler {
// MARK: PgDn
- if input.isPageDown || input.emacsKey == EmacsKey.nextPage {
+ if input.isPageDown {
let updated: Bool = ctlCandidateCurrent.showNextPage()
if !updated {
IME.prtDebugIntel("9B691919")
@@ -150,17 +150,6 @@ extension KeyHandler {
return true
}
- // MARK: EmacsKey Backward
-
- if input.emacsKey == EmacsKey.backward {
- let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate()
- if !updated {
- IME.prtDebugIntel("9B89308D")
- errorCallback()
- }
- return true
- }
-
// MARK: Right Arrow
if input.isRight {
@@ -179,17 +168,6 @@ extension KeyHandler {
return true
}
- // MARK: EmacsKey Forward
-
- if input.emacsKey == EmacsKey.forward {
- let updated: Bool = ctlCandidateCurrent.highlightNextCandidate()
- if !updated {
- IME.prtDebugIntel("9B2428D")
- errorCallback()
- }
- return true
- }
-
// MARK: Up Arrow
if input.isUp {
@@ -228,7 +206,7 @@ extension KeyHandler {
// MARK: Home Key
- if input.isHome || input.emacsKey == EmacsKey.home {
+ if input.isHome {
if ctlCandidateCurrent.selectedCandidateIndex == 0 {
IME.prtDebugIntel("9B6EDE8D")
errorCallback()
@@ -244,7 +222,7 @@ extension KeyHandler {
if state.candidates.isEmpty {
return false
} else { // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
- if input.isEnd || input.emacsKey == EmacsKey.end {
+ if input.isEnd {
if ctlCandidateCurrent.selectedCandidateIndex == state.candidates.count - 1 {
IME.prtDebugIntel("9B69AAAD")
errorCallback()
diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift
index d708993c..91d4173f 100644
--- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift
+++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift
@@ -71,7 +71,9 @@ extension KeyHandler {
stateCallback(IMEState.ofEmpty())
// 字母鍵摁 Shift 的話,無須額外處理,因為直接就會敲出大寫字母。
- if input.isUpperCaseASCIILetterKey {
+ if (input.isUpperCaseASCIILetterKey && input.isASCIIModeInput)
+ || (input.isCapsLockOn && input.isShiftHold)
+ {
return false
}
@@ -195,7 +197,7 @@ extension KeyHandler {
// MARK: Cursor backward
- if input.isCursorBackward || input.emacsKey == EmacsKey.backward {
+ if input.isCursorBackward {
return handleBackward(
state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
)
@@ -203,7 +205,7 @@ extension KeyHandler {
// MARK: Cursor forward
- if input.isCursorForward || input.emacsKey == EmacsKey.forward {
+ if input.isCursorForward {
return handleForward(
state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
)
@@ -211,13 +213,13 @@ extension KeyHandler {
// MARK: Home
- if input.isHome || input.emacsKey == EmacsKey.home {
+ if input.isHome {
return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
}
// MARK: End
- if input.isEnd || input.emacsKey == EmacsKey.end {
+ if input.isEnd {
return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback)
}
@@ -259,7 +261,7 @@ extension KeyHandler {
// MARK: Delete
- if input.isDelete || input.emacsKey == EmacsKey.delete {
+ if input.isDelete {
return handleDelete(state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback)
}
diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift
index 8ee851db..e5153a9d 100644
--- a/Source/Modules/ControllerModules/KeyHandler_States.swift
+++ b/Source/Modules/ControllerModules/KeyHandler_States.swift
@@ -190,7 +190,7 @@ extension KeyHandler {
}
// Shift + Left
- if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold {
+ if input.isCursorBackward, input.isShiftHold {
if compositor.marker > 0 {
compositor.marker -= 1
if isCursorCuttingChar(isMarker: true) {
@@ -213,7 +213,7 @@ extension KeyHandler {
}
// Shift + Right
- if input.isCursorForward || input.emacsKey == EmacsKey.forward, input.isShiftHold {
+ if input.isCursorForward, input.isShiftHold {
if compositor.marker < compositor.width {
compositor.marker += 1
if isCursorCuttingChar(isMarker: true) {
diff --git a/Source/Modules/ControllerModules/NSEventExtension.swift b/Source/Modules/ControllerModules/NSEventExtension.swift
index 1bd242a9..98e232f9 100644
--- a/Source/Modules/ControllerModules/NSEventExtension.swift
+++ b/Source/Modules/ControllerModules/NSEventExtension.swift
@@ -35,6 +35,38 @@ extension NSEvent {
keyCode: keyCode ?? self.keyCode
)
}
+
+ /// 自 Emacs 熱鍵的 NSEvent 翻譯回標準 NSEvent。失敗的話則會返回原始 NSEvent 自身。
+ /// - Parameter isVerticalTyping: 是否按照縱排來操作。
+ /// - Returns: 翻譯結果。失敗的話則返回翻譯原文。
+ public func convertFromEmacKeyEvent(isVerticalContext: Bool) -> NSEvent {
+ guard isEmacsKey else { return self }
+ let newKeyCode: UInt16 = {
+ switch isVerticalContext {
+ case false: return IME.vChewingEmacsKey.charKeyMapHorizontal[charCode] ?? 0
+ case true: return IME.vChewingEmacsKey.charKeyMapVertical[charCode] ?? 0
+ }
+ }()
+ guard newKeyCode != 0 else { return self }
+ let newCharScalar: Unicode.Scalar = {
+ switch charCode {
+ case 6:
+ return isVerticalContext
+ ? NSEvent.SpecialKey.downArrow.unicodeScalar : NSEvent.SpecialKey.rightArrow.unicodeScalar
+ case 2:
+ return isVerticalContext
+ ? NSEvent.SpecialKey.upArrow.unicodeScalar : NSEvent.SpecialKey.leftArrow.unicodeScalar
+ case 1: return NSEvent.SpecialKey.home.unicodeScalar
+ case 5: return NSEvent.SpecialKey.end.unicodeScalar
+ case 4: return NSEvent.SpecialKey.deleteForward.unicodeScalar // Use "deleteForward" for PC delete.
+ case 22: return NSEvent.SpecialKey.pageDown.unicodeScalar
+ default: return .init(0)
+ }
+ }()
+ let newChar = String(newCharScalar)
+ return reinitiate(modifierFlags: [], characters: newChar, charactersIgnoringModifiers: newChar, keyCode: newKeyCode)
+ ?? self
+ }
}
// MARK: - NSEvent Extension - InputSignalProtocol
@@ -58,8 +90,9 @@ extension NSEvent: InputSignalProtocol {
public var isFlagChanged: Bool { type == .flagsChanged }
- public var emacsKey: EmacsKey {
- NSEvent.detectEmacsKey(charCode: charCode, flags: modifierFlags)
+ public var isEmacsKey: Bool {
+ // 這裡不能只用 isControlHold,因為這裡對修飾鍵的要求有排他性。
+ [6, 2, 1, 5, 4, 22].contains(charCode) && modifierFlags == .control
}
// 摁 Alt+Shift+主鍵盤區域數字鍵 的話,根據不同的 macOS 鍵盤佈局種類,會出現不同的符號結果。
@@ -150,13 +183,6 @@ extension NSEvent: InputSignalProtocol {
public var isSymbolMenuPhysicalKey: Bool {
[KeyCode.kSymbolMenuPhysicalKeyIntl, KeyCode.kSymbolMenuPhysicalKeyJIS].contains(KeyCode(rawValue: keyCode))
}
-
- static func detectEmacsKey(charCode: UniChar, flags: NSEvent.ModifierFlags) -> EmacsKey {
- if flags.contains(.control) {
- return EmacsKey(rawValue: charCode) ?? .none
- }
- return .none
- }
}
// MARK: - InputSignalProtocol
@@ -169,7 +195,6 @@ public protocol InputSignalProtocol {
var charCode: UInt16 { get }
var keyCode: UInt16 { get }
var isFlagChanged: Bool { get }
- var emacsKey: EmacsKey { get }
var mainAreaNumKeyChar: String? { get }
var isASCII: Bool { get }
var isInvalid: Bool { get }
@@ -319,13 +344,3 @@ enum CharCode: UInt16 {
// KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ...
// ... but only focuses on which physical key is pressed.
}
-
-public enum EmacsKey: UInt16 {
- case none = 0
- case forward = 6 // F
- case backward = 2 // B
- case home = 1 // A
- case end = 5 // E
- case delete = 4 // D
- case nextPage = 22 // V
-}
diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Common.swift b/Source/Modules/ControllerModules/ctlInputMethod_Common.swift
index 2ec05c81..85db9525 100644
--- a/Source/Modules/ControllerModules/ctlInputMethod_Common.swift
+++ b/Source/Modules/ControllerModules/ctlInputMethod_Common.swift
@@ -46,6 +46,15 @@ extension ctlInputMethod {
/// 沒有文字輸入客體的話,就不要再往下處理了。
guard client() != nil else { return false }
+ var event = event
+ // 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。
+ if event.isEmacsKey {
+ let verticalProcessing =
+ (state.isCandidateContainer)
+ ? ctlInputMethod.isVerticalCandidateSituation : ctlInputMethod.isVerticalTypingSituation
+ event = event.convertFromEmacKeyEvent(isVerticalContext: verticalProcessing)
+ }
+
/// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。
/// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。
/// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false,
diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift
index d8c90ca0..5d47caeb 100644
--- a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift
+++ b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift
@@ -36,6 +36,8 @@ class ctlInputMethod: IMKInputController {
static var isASCIIModeSituation: Bool = false
/// 當前這個 ctlInputMethod 副本是否處於縱排輸入模式(滯後項)。
static var isVerticalTypingSituation: Bool = false
+ /// 當前這個 ctlInputMethod 副本是否處於縱排選字窗模式(滯後項)。
+ static var isVerticalCandidateSituation: Bool = false
/// 當前這個 ctlInputMethod 副本是否處於英數輸入模式。
var isASCIIMode: Bool = false
/// 按鍵調度模組的副本。
@@ -221,7 +223,11 @@ class ctlInputMethod: IMKInputController {
// - 還是藉由 delegate 扔回 ctlInputMethod 給 KeyHandler 處理?
proc: if let ctlCandidateCurrent = ctlInputMethod.ctlCandidateCurrent as? ctlCandidateIMK {
guard ctlCandidateCurrent.visible else { break proc }
- let event: NSEvent = ctlCandidateIMK.replaceNumPadKeyCodes(target: event) ?? event
+ var event: NSEvent = ctlCandidateIMK.replaceNumPadKeyCodes(target: event) ?? event
+ // 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。
+ if event.isEmacsKey {
+ event = event.convertFromEmacKeyEvent(isVerticalContext: ctlInputMethod.isVerticalCandidateSituation)
+ }
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。
// 所以這裡直接將 Shift Flags 清空。
@@ -247,6 +253,7 @@ class ctlInputMethod: IMKInputController {
/// 我們不在這裡處理了,直接交給 commonEventHandler 來處理。
/// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。
+ /// 警告:這裡的 event 必須是原始 event 且不能被 var,否則會影響 Shift 中英模式判定。
return commonEventHandler(event)
}
diff --git a/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift b/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift
index 9dd74dac..8e6e9520 100644
--- a/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift
+++ b/Source/Modules/ControllerModules/ctlInputMethod_HandleDisplay.swift
@@ -58,6 +58,8 @@ extension ctlInputMethod {
// 上面這句如果是 true 的話,就會是縱排;反之則為橫排。
}
+ ctlInputMethod.isVerticalCandidateSituation = (isCandidateWindowVertical || !mgrPrefs.useHorizontalCandidateList)
+
ctlInputMethod.ctlCandidateCurrent.delegate = nil
/// 下面這一段本可直接指定 currentLayout,但這樣的話翻頁按鈕位置無法精準地重新繪製。
diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift
index 9d800519..900a9456 100644
--- a/Source/Modules/IMEModules/IME.swift
+++ b/Source/Modules/IMEModules/IME.swift
@@ -26,6 +26,13 @@ public enum IME {
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
)
+ // MARK: - vChewing Emacs CharCode-KeyCode translation tables.
+
+ public enum vChewingEmacsKey {
+ static let charKeyMapHorizontal: [UInt16: UInt16] = [6: 124, 2: 123, 1: 115, 5: 119, 4: 117, 22: 121]
+ static let charKeyMapVertical: [UInt16: UInt16] = [6: 125, 2: 126, 1: 115, 5: 119, 4: 117, 22: 121]
+ }
+
// MARK: - 瀏覽器 Bundle Identifier 關鍵詞匹配黑名單
/// 瀏覽器 Bundle Identifier 關鍵詞匹配黑名單,匹配到的瀏覽器會做出特殊的 Shift 鍵擊劍判定處理。
diff --git a/Update-Info.plist b/Update-Info.plist
index ed43c90e..ffc5d819 100644
--- a/Update-Info.plist
+++ b/Update-Info.plist
@@ -5,7 +5,7 @@
CFBundleShortVersionString
2.4.0
CFBundleVersion
- 2400
+ 2402
UpdateInfoEndpoint
https://gitee.com/vchewing/vChewing-macOS/raw/main/Update-Info.plist
UpdateInfoSite
diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj
index baf22521..96390b2f 100644
--- a/vChewing.xcodeproj/project.pbxproj
+++ b/vChewing.xcodeproj/project.pbxproj
@@ -1455,7 +1455,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -1494,7 +1494,7 @@
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -1532,7 +1532,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
@@ -1584,7 +1584,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;
@@ -1718,7 +1718,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
@@ -1777,7 +1777,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
@@ -1824,7 +1824,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
@@ -1868,7 +1868,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 2400;
+ CURRENT_PROJECT_VERSION = 2402;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;