Skip to content

Commit 5500a55

Browse files
committed
Surface user file issues
A long-staying (5 seconds) notification is shown upon any user file issues. A menu item is added to allow the user to inspect those issues.
1 parent 60a759b commit 5500a55

6 files changed

Lines changed: 163 additions & 0 deletions

File tree

Source/Base.lproj/Localizable.strings

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,21 @@
138138
"Roman Numbers (Full-width Upper Case)" = "Roman Numbers (Full-width Upper Case)";
139139

140140
"Roman Numbers (Full-width Lower Case)" = "Roman Numbers (Full-width Lower Case)";
141+
142+
"Show Issues in User Files ⚠️" = "Show Issues in User Files ⚠️";
143+
144+
"Issues were found in the following user phrase files:" = "Issues were found in the following user phrase files:";
145+
146+
"Check McBopomofo menu for user file issues" = "Check McBopomofo menu for user file issues";
147+
148+
"User phrase file" = "User phrase file";
149+
150+
"Excluded phrase file" = "Excluded phrase file";
151+
152+
"Phrase replacement file" = "Phrase replacement file";
153+
154+
"line %lu: " = "line %lu: ";
155+
156+
"Only one column was found." = "Only one column was found.";
157+
158+
"Illegal NULL character was found." = "Illegal NULL character was found.";

Source/InputMethodController.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class McBopomofoInputMethodController: IMKInputController {
5656
var state: InputState = InputState.Empty()
5757
lazy var charInfo: SystemCharacterInfo? = try? SystemCharacterInfo()
5858

59+
// Share the stored issues, so a set of issues is shown as notification only once.
60+
static var latestUserFileIssues: [String] = []
61+
5962
// MARK: - IMKInputController methods
6063

6164
override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) {
@@ -122,6 +125,16 @@ class McBopomofoInputMethodController: IMKInputController {
122125
menu.addItem(
123126
withTitle: NSLocalizedString("Reload User Phrases", comment: ""),
124127
action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
128+
129+
if !McBopomofoInputMethodController.latestUserFileIssues.isEmpty {
130+
// Setting menuItem.image does not work in input method menus even on macOS 26,
131+
// so we just use the alert emoji in the menu item title.
132+
let menuItem = NSMenuItem(
133+
title: NSLocalizedString("Show Issues in User Files ⚠️", comment: ""),
134+
action: #selector(showUserFileIssues(_:)), keyEquivalent: "")
135+
menu.addItem(menuItem)
136+
}
137+
125138
menu.addItem(NSMenuItem.separator())
126139

127140
menu.addItem(
@@ -171,6 +184,9 @@ class McBopomofoInputMethodController: IMKInputController {
171184
keyHandler.inputMode = newInputMode
172185
self.handle(state: .Empty(), client: client)
173186
}
187+
188+
// Since setValue is called after activateServer, show user file issues here, if any.
189+
checkUserFileIssues()
174190
}
175191

176192
// MARK: - IMKServerInput protocol methods
@@ -311,6 +327,34 @@ class McBopomofoInputMethodController: IMKInputController {
311327
LanguageModelManager.loadUserPhrases(
312328
enableForPlainBopomofo: Preferences.enableUserPhrasesInPlainBopomofo)
313329
LanguageModelManager.loadUserPhraseReplacement()
330+
331+
// Empty the issues so that if there are still the same issues, a
332+
// notification will be shown.
333+
McBopomofoInputMethodController.latestUserFileIssues = []
334+
checkUserFileIssues()
335+
}
336+
337+
@objc func showUserFileIssues(_ sender: Any?) {
338+
let header = NSLocalizedString(
339+
"Issues were found in the following user phrase files:", comment: "")
340+
let report =
341+
header + "\n\n"
342+
+ McBopomofoInputMethodController.latestUserFileIssues.joined(separator: "\n")
343+
let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
344+
let now = Date()
345+
let formatter = DateFormatter()
346+
formatter.locale = Locale(identifier: "en_US_POSIX")
347+
formatter.dateFormat = "yyyyMMdd-HHmmss.SSS"
348+
let dateString = formatter.string(from: now)
349+
let fileName = "UserFileIssues-\(dateString).txt"
350+
let fileURL = tempDir.appendingPathComponent(fileName)
351+
do {
352+
try report.write(to: fileURL, atomically: true, encoding: .utf8)
353+
NSWorkspace.shared.open(fileURL)
354+
} catch {
355+
NSLog("Failed to write report to temporary file: \(error)")
356+
return
357+
}
314358
}
315359

316360
@objc func showAbout(_ sender: Any?) {
@@ -849,4 +893,20 @@ extension McBopomofoInputMethodController {
849893
private func hideTooltip() {
850894
McBopomofoInputMethodController.tooltipController.hide()
851895
}
896+
897+
private func checkUserFileIssues() {
898+
let issues: [String] = keyHandler.collectUserFileIssues()
899+
900+
// McBopomofoLM caps the maximum number of issues collected, and so
901+
// we'll just do this O(n) comparison since n is small.
902+
if McBopomofoInputMethodController.latestUserFileIssues != issues {
903+
McBopomofoInputMethodController.latestUserFileIssues = issues
904+
905+
if !McBopomofoInputMethodController.latestUserFileIssues.isEmpty {
906+
NotifierController.notify(
907+
message: NSLocalizedString(
908+
"Check McBopomofo menu for user file issues", comment: ""), stay: true)
909+
}
910+
}
911+
}
852912
}

Source/KeyHandler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ extern InputMode InputModePlainBopomofo;
9292

9393
- (size_t)computeActualCursorIndex:(size_t)cursor;
9494

95+
- (NSArray<NSString *> *)collectUserFileIssues;
96+
9597
@property (strong, nonatomic) InputMode inputMode;
9698
@property (weak, nonatomic) id<KeyHandlerDelegate> delegate;
9799
@property (assign, nonatomic, readonly) NSInteger actualCandidateCursorIndex;

Source/KeyHandler.mm

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2505,4 +2505,51 @@ - (nullable InputState *)buildAssociatedPhraseStateWithPreviousState:(id)state
25052505
return [self buildAssociatedPhraseStateWithPreviousState:state prefixCursorAt:[self computeActualCursorIndex:candidtaeStateOriginalCursorIndex] reading:prefixReading value:prefixValue selectedCandidateIndex:candidateIndex useVerticalMode:useVerticalMode useShiftKey:useShiftKey];
25062506
}
25072507

2508+
- (NSArray<NSString *> *)collectUserFileIssues
2509+
{
2510+
NSMutableArray<NSString *> *array = [NSMutableArray array];
2511+
2512+
std::vector<McBopomofo::McBopomofoLM::UserFileIssue> issues = _languageModel->getUserFileIssues();
2513+
for (const auto& issue : issues) {
2514+
NSMutableString *msg = [NSMutableString string];
2515+
2516+
switch (issue.fileType) {
2517+
case McBopomofo::McBopomofoLM::UserFileType::USER_PHRASES:
2518+
[msg appendString:NSLocalizedString(@"User phrase file", "")];
2519+
break;
2520+
case McBopomofo::McBopomofoLM::UserFileType::EXCLUDED_PHRASES:
2521+
[msg appendString:NSLocalizedString(@"Excluded phrase file", "")];
2522+
break;
2523+
case McBopomofo::McBopomofoLM::UserFileType::PHRASE_REPLACEMENT_MAP:
2524+
[msg appendString:NSLocalizedString(@"Phrase replacement file", "")];
2525+
break;
2526+
default:
2527+
// Shouldn't happen, and so the string is not localized.
2528+
[msg appendString:@"Unknown user file"];
2529+
break;
2530+
}
2531+
2532+
[msg appendFormat:@" (%@) ", [NSString stringWithUTF8String:issue.path.filename().c_str()]];
2533+
[msg appendFormat:NSLocalizedString(@"line %lu: ", ""), issue.lineNumber];
2534+
2535+
switch (issue.issueType) {
2536+
case McBopomofo::McBopomofoLM::IssueType::MISSING_SECOND_COLUMN:
2537+
[msg appendString:NSLocalizedString(@"Only one column was found.", "")];
2538+
break;
2539+
case McBopomofo::McBopomofoLM::IssueType::NULL_CHARACTER_IN_TEXT:
2540+
[msg appendString:NSLocalizedString(@"Illegal NULL character was found.", "")];
2541+
break;
2542+
case McBopomofo::McBopomofoLM::IssueType::NO_ISSUE:
2543+
default:
2544+
// Shouldn't happen, and so the string is not localized.
2545+
[msg appendString:@"Unknown issue."];
2546+
break;
2547+
}
2548+
2549+
[array addObject:msg];
2550+
}
2551+
2552+
return array;
2553+
}
2554+
25082555
@end

Source/en.lproj/Localizable.strings

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,21 @@
140140
"Roman Numbers (Full-width Upper Case)" = "Roman Numbers (Full-width Upper Case)";
141141

142142
"Roman Numbers (Full-width Lower Case)" = "Roman Numbers (Full-width Lower Case)";
143+
144+
"Show Issues in User Files ⚠️" = "Show Issues in User Files ⚠️";
145+
146+
"Issues were found in the following user phrase files:" = "Issues were found in the following user phrase files:";
147+
148+
"Check McBopomofo menu for user file issues" = "Check McBopomofo menu for user file issues";
149+
150+
"User phrase file" = "User phrase file";
151+
152+
"Excluded phrase file" = "Excluded phrase file";
153+
154+
"Phrase replacement file" = "Phrase replacement file";
155+
156+
"line %lu: " = "line %lu: ";
157+
158+
"Only one column was found." = "Only one column was found.";
159+
160+
"Illegal NULL character was found." = "Illegal NULL character was found.";

Source/zh-Hant.lproj/Localizable.strings

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,21 @@
138138
"Roman Numbers (Full-width Upper Case)" = "羅馬數字 (大寫全形)";
139139

140140
"Roman Numbers (Full-width Lower Case)" = "羅馬數字 (小寫全形)";
141+
142+
"Show Issues in User Files ⚠️" = "列出自訂詞庫中的問題 ⚠️";
143+
144+
"Issues were found in the following user phrase files:" = "以下是用戶自訂詞庫檔案中發現的問題:";
145+
146+
"Check McBopomofo menu for user file issues" = "自訂詞庫有誤,請從小麥注音選單點選「列出自訂詞庫中的問題」";
147+
148+
"User phrase file" = "使用者自訂詞彙檔";
149+
150+
"Excluded phrase file" = "排除詞彙檔";
151+
152+
"Phrase replacement file" = "詞彙替換表格檔";
153+
154+
"line %lu: " = "第 %lu 行:";
155+
156+
"Only one column was found." = "一行應該有兩欄文字,這行只有一欄。";
157+
158+
"Illegal NULL character was found." = "這一行包含了不合規則的 NULL 字元 (0x00)。";

0 commit comments

Comments
 (0)