Skip to content

Commit 7cc0d23

Browse files
Merge branch 'dev' into code_quality
2 parents fc6ff5a + 207355e commit 7cc0d23

File tree

6 files changed

+146
-95
lines changed

6 files changed

+146
-95
lines changed

.github/actions/spelling/expect.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,4 @@ Reloadable
103103
metadatas
104104
WMP
105105
VSTHRD
106+
CJK

Flow.Launcher.Infrastructure/PinyinAlphabet.cs

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@ namespace Flow.Launcher.Infrastructure
1414
{
1515
public class PinyinAlphabet : IAlphabet
1616
{
17-
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
18-
new();
19-
17+
private readonly ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache = new();
2018
private readonly Settings _settings;
21-
2219
private ReadOnlyDictionary<string, string> currentDoublePinyinTable;
2320

2421
public PinyinAlphabet()
@@ -28,10 +25,21 @@ public PinyinAlphabet()
2825

2926
_settings.PropertyChanged += (sender, e) =>
3027
{
31-
if (e.PropertyName == nameof(Settings.UseDoublePinyin) ||
32-
e.PropertyName == nameof(Settings.DoublePinyinSchema))
28+
switch (e.PropertyName)
3329
{
34-
Reload();
30+
case nameof (Settings.ShouldUsePinyin):
31+
if (_settings.ShouldUsePinyin)
32+
{
33+
Reload();
34+
}
35+
break;
36+
case nameof(Settings.UseDoublePinyin):
37+
case nameof(Settings.DoublePinyinSchema):
38+
if (_settings.UseDoublePinyin)
39+
{
40+
Reload();
41+
}
42+
break;
3543
}
3644
};
3745
}
@@ -44,105 +52,142 @@ public void Reload()
4452

4553
private void CreateDoublePinyinTableFromStream(Stream jsonStream)
4654
{
47-
Dictionary<string, Dictionary<string, string>> table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream);
48-
string schemaKey = _settings.DoublePinyinSchema.ToString(); // Convert enum to string
49-
if (!table.TryGetValue(schemaKey, out var value))
55+
var table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream) ??
56+
throw new InvalidOperationException("Failed to deserialize double pinyin table: result is null");
57+
58+
var schemaKey = _settings.DoublePinyinSchema.ToString();
59+
if (!table.TryGetValue(schemaKey, out var schemaDict))
5060
{
51-
throw new ArgumentException("DoublePinyinSchema is invalid or double pinyin table is broken.");
61+
throw new ArgumentException($"DoublePinyinSchema '{schemaKey}' is invalid or double pinyin table is broken.");
5262
}
53-
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(value);
63+
64+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(schemaDict);
5465
}
5566

5667
private void LoadDoublePinyinTable()
5768
{
58-
if (_settings.UseDoublePinyin)
69+
if (!_settings.UseDoublePinyin)
5970
{
60-
var tablePath = Path.Join(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
61-
try
62-
{
63-
using var fs = File.OpenRead(tablePath);
64-
CreateDoublePinyinTableFromStream(fs);
65-
}
66-
catch (System.Exception e)
67-
{
68-
Log.Exception(nameof(PinyinAlphabet), "Failed to load double pinyin table from file: " + tablePath, e);
69-
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
70-
}
71+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
72+
return;
73+
}
74+
75+
var tablePath = Path.Combine(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
76+
try
77+
{
78+
using var fs = File.OpenRead(tablePath);
79+
CreateDoublePinyinTableFromStream(fs);
80+
}
81+
catch (FileNotFoundException e)
82+
{
83+
Log.Exception(nameof(PinyinAlphabet), $"Double pinyin table file not found: {tablePath}", e);
84+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
7185
}
72-
else
86+
catch (DirectoryNotFoundException e)
7387
{
88+
Log.Exception(nameof(PinyinAlphabet), $"Directory not found for double pinyin table: {tablePath}", e);
89+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
90+
}
91+
catch (UnauthorizedAccessException e)
92+
{
93+
Log.Exception(nameof(PinyinAlphabet), $"Access denied to double pinyin table: {tablePath}", e);
94+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
95+
}
96+
catch (System.Exception e)
97+
{
98+
Log.Exception(nameof(PinyinAlphabet), $"Failed to load double pinyin table from file: {tablePath}", e);
7499
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
75100
}
76101
}
77102

78103
public bool ShouldTranslate(string stringToTranslate)
79104
{
80-
// If a string has Chinese characters, we don't need to translate it to pinyin.
81-
return _settings.ShouldUsePinyin && !WordsHelper.HasChinese(stringToTranslate);
105+
// If the query (stringToTranslate) does NOT contain Chinese characters,
106+
// we should translate the target string to pinyin for matching
107+
return _settings.ShouldUsePinyin && !ContainsChinese(stringToTranslate);
82108
}
83109

84110
public (string translation, TranslationMapping map) Translate(string content)
85111
{
86-
if (!_settings.ShouldUsePinyin || !WordsHelper.HasChinese(content))
112+
if (!_settings.ShouldUsePinyin || !ContainsChinese(content))
87113
return (content, null);
88114

89-
return _pinyinCache.TryGetValue(content, out var value)
90-
? value
91-
: BuildCacheFromContent(content);
115+
return _pinyinCache.TryGetValue(content, out var cached) ? cached : BuildCacheFromContent(content);
92116
}
93117

94118
private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
95119
{
96120
var resultList = WordsHelper.GetPinyinList(content);
97-
98-
var resultBuilder = new StringBuilder();
121+
var resultBuilder = new StringBuilder(_settings.UseDoublePinyin ? 3 : 4); // Pre-allocate with estimated capacity
99122
var map = new TranslationMapping();
100123

101124
var previousIsChinese = false;
102125

103126
for (var i = 0; i < resultList.Length; i++)
104127
{
105-
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
128+
if (IsChineseCharacter(content[i]))
106129
{
107-
string translated = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i];
130+
var translated = _settings.UseDoublePinyin ? ToDoublePinyin(resultList[i]) : resultList[i];
131+
108132
if (i > 0)
109133
{
110134
resultBuilder.Append(' ');
111135
}
136+
112137
map.AddNewIndex(resultBuilder.Length, translated.Length);
113138
resultBuilder.Append(translated);
114139
previousIsChinese = true;
115140
}
116141
else
117142
{
143+
// Add space after Chinese characters before non-Chinese characters
118144
if (previousIsChinese)
119145
{
120146
previousIsChinese = false;
121147
resultBuilder.Append(' ');
122148
}
149+
123150
map.AddNewIndex(resultBuilder.Length, resultList[i].Length);
124151
resultBuilder.Append(resultList[i]);
125152
}
126153
}
127154

128-
map.endConstruct();
155+
map.EndConstruct();
129156

130-
var key = resultBuilder.ToString();
131-
132-
return _pinyinCache[content] = (key, map);
157+
var translation = resultBuilder.ToString();
158+
var result = (translation, map);
159+
160+
return _pinyinCache[content] = result;
133161
}
134162

135-
#region Double Pinyin
136-
137-
private string ToDoublePin(string fullPinyin)
163+
/// <summary>
164+
/// Optimized Chinese character detection using the comprehensive CJK Unicode ranges
165+
/// </summary>
166+
private static bool ContainsChinese(ReadOnlySpan<char> text)
138167
{
139-
if (currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue))
168+
foreach (var c in text)
140169
{
141-
return doublePinyinValue;
170+
if (IsChineseCharacter(c))
171+
return true;
142172
}
143-
return fullPinyin;
173+
return false;
144174
}
145175

146-
#endregion
176+
/// <summary>
177+
/// Check if a character is a Chinese character using comprehensive Unicode ranges
178+
/// Covers CJK Unified Ideographs, Extension A
179+
/// </summary>
180+
private static bool IsChineseCharacter(char c)
181+
{
182+
return (c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs
183+
(c >= 0x3400 && c <= 0x4DBF); // CJK Extension A
184+
}
185+
186+
private string ToDoublePinyin(string fullPinyin)
187+
{
188+
return currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue)
189+
? doublePinyinValue
190+
: fullPinyin;
191+
}
147192
}
148193
}

Flow.Launcher.Infrastructure/TranslationMapping.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,30 @@ namespace Flow.Launcher.Infrastructure
55
{
66
public class TranslationMapping
77
{
8-
private bool constructed;
8+
private bool _isConstructed;
99

10-
// Assuming one original item maps to multi translated items
11-
// list[i] is the last translated index + 1 of original index i
12-
private readonly List<int> originalToTranslated = new();
10+
// Assuming one original item maps to multi translated items
11+
// list[i] is the last translated index + 1 of original index i
12+
private readonly List<int> _originalToTranslated = new();
1313

1414
public void AddNewIndex(int translatedIndex, int length)
1515
{
16-
if (constructed)
17-
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
18-
19-
originalToTranslated.Add(translatedIndex + length);
16+
if (_isConstructed)
17+
throw new InvalidOperationException("Mapping shouldn't be changed after construction");
18+
_originalToTranslated.Add(translatedIndex + length);
2019
}
2120

2221
public int MapToOriginalIndex(int translatedIndex)
2322
{
24-
int loc = originalToTranslated.BinarySearch(translatedIndex);
25-
return loc >= 0 ? loc : ~loc;
23+
var searchResult = _originalToTranslated.BinarySearch(translatedIndex);
24+
return searchResult >= 0 ? searchResult : ~searchResult;
2625
}
2726

28-
public void endConstruct()
27+
public void EndConstruct()
2928
{
30-
if (constructed)
29+
if (_isConstructed)
3130
throw new InvalidOperationException("Mapping has already been constructed");
32-
constructed = true;
31+
_isConstructed = true;
3332
}
3433
}
3534
}

Flow.Launcher.Infrastructure/UserSettings/Settings.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,19 @@ public CustomBrowserViewModel CustomBrowser
328328
/// <summary>
329329
/// when false Alphabet static service will always return empty results
330330
/// </summary>
331-
public bool ShouldUsePinyin { get; set; } = false;
331+
private bool _useAlphabet = true;
332+
public bool ShouldUsePinyin
333+
{
334+
get => _useAlphabet;
335+
set
336+
{
337+
if (_useAlphabet != value)
338+
{
339+
_useAlphabet = value;
340+
OnPropertyChanged();
341+
}
342+
}
343+
}
332344

333345
private bool _useDoublePinyin = false;
334346
public bool UseDoublePinyin

Flow.Launcher.Test/TranslationMappingTest.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Flow.Launcher.Infrastructure;
1+
using System.Collections.Generic;
2+
using System.Reflection;
3+
using Flow.Launcher.Infrastructure;
24
using NUnit.Framework;
35
using NUnit.Framework.Legacy;
46

@@ -34,22 +36,21 @@ public void MapToOriginalIndex_ShouldReturnExpectedIndex(int translatedIndex, in
3436
mapping.AddNewIndex(2, 2);
3537
mapping.AddNewIndex(5, 3);
3638

37-
3839
var result = mapping.MapToOriginalIndex(translatedIndex);
3940
ClassicAssert.AreEqual(expectedOriginalIndex, result);
4041
}
4142

42-
private int GetOriginalToTranslatedCount(TranslationMapping mapping)
43+
private static int GetOriginalToTranslatedCount(TranslationMapping mapping)
4344
{
44-
var field = typeof(TranslationMapping).GetField("originalToTranslated", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
45-
var list = (System.Collections.Generic.List<int>)field.GetValue(mapping);
45+
var field = typeof(TranslationMapping).GetField("_originalToTranslated", BindingFlags.NonPublic | BindingFlags.Instance);
46+
var list = (List<int>)field.GetValue(mapping);
4647
return list.Count;
4748
}
4849

49-
private int GetOriginalToTranslatedAt(TranslationMapping mapping, int index)
50+
private static int GetOriginalToTranslatedAt(TranslationMapping mapping, int index)
5051
{
51-
var field = typeof(TranslationMapping).GetField("originalToTranslated", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
52-
var list = (System.Collections.Generic.List<int>)field.GetValue(mapping);
52+
var field = typeof(TranslationMapping).GetField("_originalToTranslated", BindingFlags.NonPublic | BindingFlags.Instance);
53+
var list = (List<int>)field.GetValue(mapping);
5354
return list[index];
5455
}
5556
}

Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -371,44 +371,37 @@
371371
OnContent="{DynamicResource enable}" />
372372
</cc:Card>
373373

374-
<cc:CardGroup Margin="0 4 0 0">
375-
<cc:Card
376-
Title="{DynamicResource ShouldUsePinyin}"
377-
Icon="&#xe98a;"
378-
Sub="{DynamicResource ShouldUsePinyinToolTip}"
379-
Type="First">
380-
<ui:ToggleSwitch
381-
IsOn="{Binding ShouldUsePinyin}"
382-
OffContent="{DynamicResource disable}"
383-
OnContent="{DynamicResource enable}"
384-
ToolTip="{DynamicResource ShouldUsePinyinToolTip}" />
385-
</cc:Card>
386-
<cc:Card
387-
Visibility="{ext:VisibleWhen {Binding ShouldUsePinyin},
388-
IsEqualToBool=True}"
389-
Title="{DynamicResource ShouldUseDoublePinyin}"
390-
Icon="&#xf085;"
391-
Sub="{DynamicResource ShouldUseDoublePinyinToolTip}"
392-
Type="Middle">
374+
<cc:Card
375+
Title="{DynamicResource ShouldUsePinyin}"
376+
Margin="0 4 0 0"
377+
Icon="&#xe98a;"
378+
Sub="{DynamicResource ShouldUsePinyinToolTip}">
379+
<ui:ToggleSwitch
380+
IsOn="{Binding ShouldUsePinyin}"
381+
OffContent="{DynamicResource disable}"
382+
OnContent="{DynamicResource enable}"
383+
ToolTip="{DynamicResource ShouldUsePinyinToolTip}" />
384+
</cc:Card>
385+
386+
<cc:ExCard
387+
Title="{DynamicResource ShouldUseDoublePinyin}"
388+
Icon="&#xf085;"
389+
Sub="{DynamicResource ShouldUseDoublePinyinToolTip}">
390+
<cc:ExCard.SideContent>
393391
<ui:ToggleSwitch
394392
IsOn="{Binding UseDoublePinyin}"
395393
OffContent="{DynamicResource disable}"
396394
OnContent="{DynamicResource enable}"
397395
ToolTip="{DynamicResource ShouldUseDoublePinyinToolTip}" />
398-
</cc:Card>
399-
<cc:Card
400-
Visibility="{ext:VisibleWhen {Binding UseDoublePinyin},
401-
IsEqualToBool=True}"
402-
Title="{DynamicResource DoublePinyinSchema}"
403-
Sub="{DynamicResource DoublePinyinSchemaToolTip}"
404-
Type="Last">
396+
</cc:ExCard.SideContent>
397+
<cc:Card Title="{DynamicResource DoublePinyinSchema}" Type="InsideFit">
405398
<ComboBox
406399
DisplayMemberPath="Display"
407400
ItemsSource="{Binding DoublePinyinSchemas}"
408401
SelectedValue="{Binding Settings.DoublePinyinSchema}"
409402
SelectedValuePath="Value" />
410403
</cc:Card>
411-
</cc:CardGroup>
404+
</cc:ExCard>
412405

413406
<cc:Card
414407
Title="{DynamicResource language}"

0 commit comments

Comments
 (0)