-
Notifications
You must be signed in to change notification settings - Fork 1
/
ClientManager.swift
105 lines (91 loc) · 4.2 KB
/
ClientManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*
* VarnamIME is a user-configurable phonetic Input Method Engine for Mac OS X.
* Copyright (C) 2018 Ranganath Atreya
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
import InputMethodKit
class ClientManager: CustomStringConvertible {
private let notFoundRange = NSMakeRange(NSNotFound, NSNotFound)
private let config = VarnamConfig()
private let client: IMKTextInput
private var candidatesWindow: IMKCandidates { return (NSApp.delegate as! AppDelegate).candidatesWindow }
private (set) var candidates = autoreleasepool { return [String]() }
private var tableCursorPos = 0 // Candidates table cursor position
// Cache, otherwise clients quitting can sometimes SEGFAULT us
private var _description: String
var description: String {
return _description
}
private var attributes: [NSAttributedString.Key: Any]! {
var rect = NSMakeRect(0, 0, 0, 0)
return client.attributes(forCharacterIndex: 0, lineHeightRectangle: &rect) as? [NSAttributedString.Key : Any]
}
init?(client: IMKTextInput) {
guard let bundleId = client.bundleIdentifier(), let clientId = client.uniqueClientIdentifierString() else {
Logger.log.warning("bundleIdentifier: \(client.bundleIdentifier() ?? "nil") or uniqueClientIdentifierString: \(client.uniqueClientIdentifierString() ?? "nil") - failing ClientManager.init()")
return nil
}
Logger.log.debug("Initializing client: \(bundleId) with Id: \(clientId)")
self.client = client
if !client.supportsUnicode() {
Logger.log.warning("Client: \(bundleId) does not support Unicode!")
}
if !client.supportsProperty(TSMDocumentPropertyTag(kTSMDocumentSupportDocumentAccessPropertyTag)) {
Logger.log.warning("Client: \(bundleId) does not support Document Access!")
}
_description = "\(bundleId) with Id: \(clientId)"
}
func setGlobalCursorLocation(_ location: Int) {
Logger.log.debug("Setting global cursor location to: \(location)")
client.setMarkedText("|", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(location, 0))
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(location, 0))
}
func updatePreedit(_ text: NSAttributedString, _ cursorPos: Int? = nil) {
client.setMarkedText(text, selectionRange: NSMakeRange(cursorPos ?? text.length, 0), replacementRange: notFoundRange)
}
func updateCandidates(_ sugs: [String]) {
// Remove duplicates
// For some weird reason, when there are duplicates,
// candidate window makes them hidden
candidates = sugs.uniqued()
updateLookupTable()
}
func updateLookupTable() {
tableCursorPos = 0
candidatesWindow.update()
candidatesWindow.show()
}
// For moving between items of candidate table
func tableMoveEvent(_ event: NSEvent) {
if event.keyCode == kVK_UpArrow && tableCursorPos > 0 {
// TODO allow moving to the end
// This would need a custom candidate window
// https://github.com/lennylxx/google-input-tools-macos/blob/main/GoogleInputTools/CandidatesWindow.swift
tableCursorPos -= 1
} else if event.keyCode == kVK_DownArrow && tableCursorPos < candidates.count - 1 {
tableCursorPos += 1
}
candidatesWindow.interpretKeyEvents([event])
}
func getCandidate() -> String? {
if candidates.count == 0 {
return nil
} else {
return candidates[tableCursorPos]
}
}
func finalize(_ output: String) {
Logger.log.debug("Finalizing with: \(output)")
client.insertText(output, replacementRange: notFoundRange)
candidatesWindow.hide()
}
func clear() {
Logger.log.debug("Clearing MarkedText and Candidate window")
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: notFoundRange)
candidates = []
candidatesWindow.hide()
}
}