Skip to content

Commit 354e5ec

Browse files
authored
Merge pull request #2427 from Flow-Launcher/double-pin
Double pinyin query
2 parents df9be30 + 44d9eb8 commit 354e5ec

File tree

11 files changed

+327
-169
lines changed

11 files changed

+327
-169
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Flow.Launcher.Infrastructure
2+
{
3+
/// <summary>
4+
/// Translate a language to English letters using a given rule.
5+
/// </summary>
6+
public interface IAlphabet
7+
{
8+
/// <summary>
9+
/// Translate a string to English letters, using a given rule.
10+
/// </summary>
11+
/// <param name="stringToTranslate">String to translate.</param>
12+
/// <returns></returns>
13+
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
14+
15+
/// <summary>
16+
/// Determine if a string should be translated to English letter with this Alphabet.
17+
/// </summary>
18+
/// <param name="stringToTranslate">String to translate.</param>
19+
/// <returns></returns>
20+
public bool ShouldTranslate(string stringToTranslate);
21+
}
22+
}
Lines changed: 90 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,209 +1,148 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4-
using System.Linq;
4+
using System.Collections.ObjectModel;
5+
using System.IO;
56
using System.Text;
6-
using JetBrains.Annotations;
7+
using System.Text.Json;
8+
using CommunityToolkit.Mvvm.DependencyInjection;
79
using Flow.Launcher.Infrastructure.UserSettings;
810
using ToolGood.Words.Pinyin;
9-
using CommunityToolkit.Mvvm.DependencyInjection;
11+
using Flow.Launcher.Infrastructure.Logger;
1012

1113
namespace Flow.Launcher.Infrastructure
1214
{
13-
public class TranslationMapping
15+
public class PinyinAlphabet : IAlphabet
1416
{
15-
private bool constructed;
17+
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
18+
new();
1619

17-
private List<int> originalIndexs = new List<int>();
18-
private List<int> translatedIndexs = new List<int>();
19-
private int translatedLength = 0;
20+
private readonly Settings _settings;
2021

21-
public string key { get; private set; }
22+
private ReadOnlyDictionary<string, string> currentDoublePinyinTable;
2223

23-
public void setKey(string key)
24+
public PinyinAlphabet()
2425
{
25-
this.key = key;
26+
_settings = Ioc.Default.GetRequiredService<Settings>();
27+
LoadDoublePinyinTable();
28+
29+
_settings.PropertyChanged += (sender, e) =>
30+
{
31+
if (e.PropertyName == nameof(Settings.UseDoublePinyin) ||
32+
e.PropertyName == nameof(Settings.DoublePinyinSchema))
33+
{
34+
Reload();
35+
}
36+
};
2637
}
2738

28-
public void AddNewIndex(int originalIndex, int translatedIndex, int length)
39+
public void Reload()
2940
{
30-
if (constructed)
31-
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
32-
33-
originalIndexs.Add(originalIndex);
34-
translatedIndexs.Add(translatedIndex);
35-
translatedIndexs.Add(translatedIndex + length);
36-
translatedLength += length - 1;
41+
LoadDoublePinyinTable();
42+
_pinyinCache.Clear();
3743
}
3844

39-
public int MapToOriginalIndex(int translatedIndex)
45+
private void CreateDoublePinyinTableFromStream(Stream jsonStream)
4046
{
41-
if (translatedIndex > translatedIndexs.Last())
42-
return translatedIndex - translatedLength - 1;
43-
44-
int lowerBound = 0;
45-
int upperBound = originalIndexs.Count - 1;
46-
47-
int count = 0;
48-
49-
// Corner case handle
50-
if (translatedIndex < translatedIndexs[0])
51-
return translatedIndex;
52-
if (translatedIndex > translatedIndexs.Last())
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))
5350
{
54-
int indexDef = 0;
55-
for (int k = 0; k < originalIndexs.Count; k++)
56-
{
57-
indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2];
58-
}
59-
60-
return translatedIndex - indexDef - 1;
51+
throw new ArgumentException("DoublePinyinSchema is invalid or double pinyin table is broken.");
6152
}
53+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(value);
54+
}
6255

63-
// Binary Search with Range
64-
for (int i = originalIndexs.Count / 2;; count++)
56+
private void LoadDoublePinyinTable()
57+
{
58+
if (_settings.UseDoublePinyin)
6559
{
66-
if (translatedIndex < translatedIndexs[i * 2])
60+
var tablePath = Path.Join(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
61+
try
6762
{
68-
// move to lower middle
69-
upperBound = i;
70-
i = (i + lowerBound) / 2;
63+
using var fs = File.OpenRead(tablePath);
64+
CreateDoublePinyinTableFromStream(fs);
7165
}
72-
else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1)
66+
catch (System.Exception e)
7367
{
74-
lowerBound = i;
75-
// move to upper middle
76-
// due to floor of integer division, move one up on corner case
77-
i = (i + upperBound + 1) / 2;
78-
}
79-
else
80-
return originalIndexs[i];
81-
82-
if (upperBound - lowerBound <= 1 &&
83-
translatedIndex > translatedIndexs[lowerBound * 2 + 1] &&
84-
translatedIndex < translatedIndexs[upperBound * 2])
85-
{
86-
int indexDef = 0;
87-
88-
for (int j = 0; j < upperBound; j++)
89-
{
90-
indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
91-
}
92-
93-
return translatedIndex - indexDef - 1;
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>());
9470
}
9571
}
72+
else
73+
{
74+
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
75+
}
9676
}
9777

98-
public void endConstruct()
99-
{
100-
if (constructed)
101-
throw new InvalidOperationException("Mapping has already been constructed");
102-
constructed = true;
103-
}
104-
}
105-
106-
/// <summary>
107-
/// Translate a language to English letters using a given rule.
108-
/// </summary>
109-
public interface IAlphabet
110-
{
111-
/// <summary>
112-
/// Translate a string to English letters, using a given rule.
113-
/// </summary>
114-
/// <param name="stringToTranslate">String to translate.</param>
115-
/// <returns></returns>
116-
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
117-
118-
/// <summary>
119-
/// Determine if a string can be translated to English letter with this Alphabet.
120-
/// </summary>
121-
/// <param name="stringToTranslate">String to translate.</param>
122-
/// <returns></returns>
123-
public bool CanBeTranslated(string stringToTranslate);
124-
}
125-
126-
public class PinyinAlphabet : IAlphabet
127-
{
128-
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
129-
new ConcurrentDictionary<string, (string translation, TranslationMapping map)>();
130-
131-
private Settings _settings;
132-
133-
public PinyinAlphabet()
134-
{
135-
Initialize(Ioc.Default.GetRequiredService<Settings>());
136-
}
137-
138-
private void Initialize([NotNull] Settings settings)
139-
{
140-
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
141-
}
142-
143-
public bool CanBeTranslated(string stringToTranslate)
78+
public bool ShouldTranslate(string stringToTranslate)
14479
{
145-
return WordsHelper.HasChinese(stringToTranslate);
80+
// If a string has Chinese characters, we don't need to translate it to pinyin.
81+
return _settings.ShouldUsePinyin && !WordsHelper.HasChinese(stringToTranslate);
14682
}
14783

14884
public (string translation, TranslationMapping map) Translate(string content)
14985
{
150-
if (_settings.ShouldUsePinyin)
151-
{
152-
if (!_pinyinCache.ContainsKey(content))
153-
{
154-
return BuildCacheFromContent(content);
155-
}
156-
else
157-
{
158-
return _pinyinCache[content];
159-
}
160-
}
161-
return (content, null);
86+
if (!_settings.ShouldUsePinyin || !WordsHelper.HasChinese(content))
87+
return (content, null);
88+
89+
return _pinyinCache.TryGetValue(content, out var value)
90+
? value
91+
: BuildCacheFromContent(content);
16292
}
16393

16494
private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
16595
{
166-
if (WordsHelper.HasChinese(content))
167-
{
168-
var resultList = WordsHelper.GetPinyinList(content);
96+
var resultList = WordsHelper.GetPinyinList(content);
16997

170-
StringBuilder resultBuilder = new StringBuilder();
171-
TranslationMapping map = new TranslationMapping();
98+
var resultBuilder = new StringBuilder();
99+
var map = new TranslationMapping();
172100

173-
bool pre = false;
101+
var previousIsChinese = false;
174102

175-
for (int i = 0; i < resultList.Length; i++)
103+
for (var i = 0; i < resultList.Length; i++)
104+
{
105+
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
176106
{
177-
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
107+
string translated = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i];
108+
if (i > 0)
178109
{
179-
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
180110
resultBuilder.Append(' ');
181-
resultBuilder.Append(resultList[i]);
182-
pre = true;
183111
}
184-
else
112+
map.AddNewIndex(resultBuilder.Length, translated.Length);
113+
resultBuilder.Append(translated);
114+
previousIsChinese = true;
115+
}
116+
else
117+
{
118+
if (previousIsChinese)
185119
{
186-
if (pre)
187-
{
188-
pre = false;
189-
resultBuilder.Append(' ');
190-
}
191-
192-
resultBuilder.Append(resultList[i]);
120+
previousIsChinese = false;
121+
resultBuilder.Append(' ');
193122
}
123+
map.AddNewIndex(resultBuilder.Length, resultList[i].Length);
124+
resultBuilder.Append(resultList[i]);
194125
}
126+
}
195127

196-
map.endConstruct();
128+
map.endConstruct();
197129

198-
var key = resultBuilder.ToString();
199-
map.setKey(key);
130+
var key = resultBuilder.ToString();
200131

201-
return _pinyinCache[content] = (key, map);
202-
}
203-
else
132+
return _pinyinCache[content] = (key, map);
133+
}
134+
135+
#region Double Pinyin
136+
137+
private string ToDoublePin(string fullPinyin)
138+
{
139+
if (currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue))
204140
{
205-
return (content, null);
141+
return doublePinyinValue;
206142
}
143+
return fullPinyin;
207144
}
145+
146+
#endregion
208147
}
209148
}

Flow.Launcher.Infrastructure/StringMatcher.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
6868

6969
query = query.Trim();
7070
TranslationMapping translationMapping = null;
71-
if (_alphabet is not null && !_alphabet.CanBeTranslated(query))
71+
if (_alphabet is not null && _alphabet.ShouldTranslate(query))
7272
{
7373
// We assume that if a query can be translated (containing characters of a language, like Chinese)
7474
// it actually means user doesn't want it to be translated to English letters.
@@ -228,7 +228,7 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
228228
return new MatchResult(false, UserSettingSearchPrecision);
229229
}
230230

231-
private bool IsAcronym(string stringToCompare, int compareStringIndex)
231+
private static bool IsAcronym(string stringToCompare, int compareStringIndex)
232232
{
233233
if (IsAcronymChar(stringToCompare, compareStringIndex) || IsAcronymNumber(stringToCompare, compareStringIndex))
234234
return true;
@@ -237,7 +237,7 @@ private bool IsAcronym(string stringToCompare, int compareStringIndex)
237237
}
238238

239239
// When counting acronyms, treat a set of numbers as one acronym ie. Visual 2019 as 2 acronyms instead of 5
240-
private bool IsAcronymCount(string stringToCompare, int compareStringIndex)
240+
private static bool IsAcronymCount(string stringToCompare, int compareStringIndex)
241241
{
242242
if (IsAcronymChar(stringToCompare, compareStringIndex))
243243
return true;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Flow.Launcher.Infrastructure
5+
{
6+
public class TranslationMapping
7+
{
8+
private bool constructed;
9+
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();
13+
14+
public void AddNewIndex(int translatedIndex, int length)
15+
{
16+
if (constructed)
17+
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
18+
19+
originalToTranslated.Add(translatedIndex + length);
20+
}
21+
22+
public int MapToOriginalIndex(int translatedIndex)
23+
{
24+
int loc = originalToTranslated.BinarySearch(translatedIndex);
25+
return loc >= 0 ? loc : ~loc;
26+
}
27+
28+
public void endConstruct()
29+
{
30+
if (constructed)
31+
throw new InvalidOperationException("Mapping has already been constructed");
32+
constructed = true;
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)