From 05e01ab9ac9e73105077398c3f13a5edcc7ab098 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 14 Jul 2025 16:49:04 +0800
Subject: [PATCH 1/6] Change double pinyin panel design
---
.../Views/SettingsPaneGeneral.xaml | 47 ++++++++-----------
1 file changed, 20 insertions(+), 27 deletions(-)
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index df0243ce8..38fc8df54 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -371,44 +371,37 @@
OnContent="{DynamicResource enable}" />
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
+
Date: Mon, 14 Jul 2025 19:29:12 +0800
Subject: [PATCH 2/6] Reload on all settings change
---
Flow.Launcher.Infrastructure/PinyinAlphabet.cs | 13 ++++++++++---
.../UserSettings/Settings.cs | 14 +++++++++++++-
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
index cc4eccdc5..0f6d00014 100644
--- a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
+++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
@@ -25,10 +25,17 @@ namespace Flow.Launcher.Infrastructure
_settings.PropertyChanged += (sender, e) =>
{
- if (e.PropertyName == nameof(Settings.UseDoublePinyin) ||
- e.PropertyName == nameof(Settings.DoublePinyinSchema))
+ switch (e.PropertyName)
{
- Reload();
+ case nameof(Settings.ShouldUsePinyin):
+ Reload();
+ break;
+ case nameof(Settings.UseDoublePinyin):
+ Reload();
+ break;
+ case nameof(Settings.DoublePinyinSchema):
+ Reload();
+ break;
}
};
}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 6b10d693d..726a0023b 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -328,7 +328,19 @@ namespace Flow.Launcher.Infrastructure.UserSettings
///
/// when false Alphabet static service will always return empty results
///
- public bool ShouldUsePinyin { get; set; } = false;
+ private bool _useAlphabet = true;
+ public bool ShouldUsePinyin
+ {
+ get => _useAlphabet;
+ set
+ {
+ if (_useAlphabet != value)
+ {
+ _useAlphabet = value;
+ OnPropertyChanged();
+ }
+ }
+ }
private bool _useDoublePinyin = false;
public bool UseDoublePinyin
From 8c56c0bddf4a4b7b361b26a3a6b32038144c2f35 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 14 Jul 2025 19:30:16 +0800
Subject: [PATCH 3/6] Fix logic
---
Flow.Launcher.Infrastructure/PinyinAlphabet.cs | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
index 0f6d00014..1c0cc6872 100644
--- a/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
+++ b/Flow.Launcher.Infrastructure/PinyinAlphabet.cs
@@ -27,14 +27,18 @@ namespace Flow.Launcher.Infrastructure
{
switch (e.PropertyName)
{
- case nameof(Settings.ShouldUsePinyin):
- Reload();
+ case nameof (Settings.ShouldUsePinyin):
+ if (_settings.ShouldUsePinyin)
+ {
+ Reload();
+ }
break;
case nameof(Settings.UseDoublePinyin):
- Reload();
- break;
case nameof(Settings.DoublePinyinSchema):
- Reload();
+ if (_settings.UseDoublePinyin)
+ {
+ Reload();
+ }
break;
}
};
From fd4efe009cf2839b5eb01c95aaceb025a84edda2 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 14 Jul 2025 21:07:25 +0800
Subject: [PATCH 4/6] Hide double pin card when use pinyin is false
---
Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index 38fc8df54..a879007c3 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -386,6 +386,8 @@
Date: Mon, 14 Jul 2025 21:21:47 +0800
Subject: [PATCH 5/6] Update spell check
---
.github/actions/spelling/expect.txt | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index 0fea6d9ab..d8c99bce9 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -104,3 +104,12 @@ metadatas
WMP
VSTHRD
CJK
+XiaoHe
+ZiRanMa
+WeiRuan
+ZhiNengABC
+ZiGuangPinYin
+PinYinJiaJia
+XingKongJianDao
+DaNiu
+XiaoLang
From 970aa5eefe89d3a3009753ed76a8bec64cd1d827 Mon Sep 17 00:00:00 2001
From: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
Date: Mon, 14 Jul 2025 21:24:11 +0800
Subject: [PATCH 6/6] Fix typo
---
.../UserSettings/Settings.cs | 2 +-
.../ChineseDetectionPerformanceTest.cs | 265 ++++++++++++++++++
2 files changed, 266 insertions(+), 1 deletion(-)
create mode 100644 Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 726a0023b..271f618da 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -514,7 +514,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
var list = FixedHotkeys();
- // Customizeable hotkeys
+ // Customizable hotkeys
if (!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if (!string.IsNullOrEmpty(PreviewHotkey))
diff --git a/Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs b/Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs
new file mode 100644
index 000000000..1747f2b4a
--- /dev/null
+++ b/Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs
@@ -0,0 +1,265 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+using Flow.Launcher.Infrastructure;
+using ToolGood.Words.Pinyin;
+
+namespace Flow.Launcher.Test
+{
+ ///
+ /// Performance test comparing ContainsChinese() vs WordsHelper.HasChinese()
+ ///
+ /// This test verifies:
+ /// 1. Both methods produce identical results (correctness)
+ /// 2. Performance characteristics of both implementations
+ /// 3. Memory allocation patterns
+ ///
+ /// The ContainsChinese() method uses optimized Unicode range checking with ReadOnlySpan
+ /// while WordsHelper.HasChinese() uses the ToolGood.Words library implementation.
+ ///
+ [TestFixture]
+ public class ChineseDetectionPerformanceTest
+ {
+ private readonly List _testStrings = new()
+ {
+ // Pure English - should return false
+ "Hello World",
+ "Visual Studio Code",
+ "Microsoft Office 2023",
+ "Adobe Photoshop Creative Suite",
+ "Google Chrome Browser Application",
+
+ // Pure Chinese - should return true
+ "你好世界",
+ "微软办公软件",
+ "谷歌浏览器",
+ "北京大学计算机科学与技术学院",
+ "中华人民共和国国家发展和改革委员会",
+
+ // Mixed content - should return true
+ "Hello 世界",
+ "Visual Studio 代码编辑器",
+ "QQ音乐 Music Player",
+ "Windows 10 操作系统",
+ "GitHub 代码仓库管理平台",
+
+ // Edge cases
+ "",
+ " ",
+ "123456",
+ "!@#$%^&*()",
+ "café résumé naïve", // Accented characters (not Chinese)
+
+ // Long strings for performance testing
+ "This is a very long English string that contains no Chinese characters but is designed to test performance with longer text content that might appear in file names or application descriptions",
+ "这是一个非常长的中文字符串,包含了很多汉字,用来测试在处理较长中文文本时的性能表现,比如可能出现在文件名或应用程序描述中的文本内容",
+ "This is a mixed 混合内容的字符串 that contains both English and Chinese characters 中英文混合 to test performance with 复杂的文本内容 in real-world scenarios 真实场景中的应用"
+ };
+
+ [Test]
+ public void ContainsChinese_CorrectnessTest()
+ {
+ // Verify ContainsChinese works correctly for known cases
+ ClassicAssert.IsFalse(ContainsChinese("Hello World"), "Pure English should return false");
+ ClassicAssert.IsTrue(ContainsChinese("你好世界"), "Pure Chinese should return true");
+ ClassicAssert.IsTrue(ContainsChinese("Hello 世界"), "Mixed content should return true");
+ ClassicAssert.IsFalse(ContainsChinese(""), "Empty string should return false");
+ ClassicAssert.IsFalse(ContainsChinese("123456"), "Numbers should return false");
+ ClassicAssert.IsFalse(ContainsChinese("café résumé"), "Accented characters should return false");
+ }
+
+ [Test]
+ public void WordsHelper_CorrectnessTest()
+ {
+ // Verify WordsHelper.HasChinese works correctly for known cases
+ ClassicAssert.IsFalse(WordsHelper.HasChinese("Hello World"), "Pure English should return false");
+ ClassicAssert.IsTrue(WordsHelper.HasChinese("你好世界"), "Pure Chinese should return true");
+ ClassicAssert.IsTrue(WordsHelper.HasChinese("Hello 世界"), "Mixed content should return true");
+ ClassicAssert.IsFalse(WordsHelper.HasChinese(""), "Empty string should return false");
+ ClassicAssert.IsFalse(WordsHelper.HasChinese("123456"), "Numbers should return false");
+ ClassicAssert.IsFalse(WordsHelper.HasChinese("café résumé"), "Accented characters should return false");
+ }
+
+ [Test]
+ public void BothMethods_ShouldProduceSameResults()
+ {
+ // Critical test: verify both methods produce identical results for all test cases
+ foreach (var testString in _testStrings)
+ {
+ var wordsHelperResult = WordsHelper.HasChinese(testString);
+ var containsChineseResult = ContainsChinese(testString);
+
+ ClassicAssert.AreEqual(wordsHelperResult, containsChineseResult,
+ $"Results differ for string: '{testString}'. WordsHelper: {wordsHelperResult}, ContainsChinese: {containsChineseResult}");
+ }
+
+ Console.WriteLine($"✓ Both methods produce identical results for all {_testStrings.Count} test cases");
+ }
+
+ [Test]
+ public void PerformanceComparison_BasicBenchmark()
+ {
+ const int iterations = 1000000;
+
+ Console.WriteLine("=== CHINESE CHARACTER DETECTION PERFORMANCE TEST ===");
+ Console.WriteLine($"Test iterations: {iterations:N0}");
+ Console.WriteLine($"Test strings: {_testStrings.Count}");
+ Console.WriteLine($"Total operations: {iterations * _testStrings.Count:N0}");
+ Console.WriteLine();
+
+ // Warmup to ensure JIT compilation
+ Console.WriteLine("Warming up...");
+ for (int i = 0; i < 1000; i++)
+ {
+ foreach (var testString in _testStrings)
+ {
+ _ = ContainsChinese(testString);
+ _ = WordsHelper.HasChinese(testString);
+ }
+ }
+
+ // Benchmark ContainsChinese method
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ var sw1 = System.Diagnostics.Stopwatch.StartNew();
+ for (int i = 0; i < iterations; i++)
+ {
+ foreach (var testString in _testStrings)
+ {
+ _ = ContainsChinese(testString);
+ }
+ }
+ sw1.Stop();
+
+ // Benchmark WordsHelper.HasChinese method
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ var sw2 = System.Diagnostics.Stopwatch.StartNew();
+ for (int i = 0; i < iterations; i++)
+ {
+ foreach (var testString in _testStrings)
+ {
+ _ = WordsHelper.HasChinese(testString);
+ }
+ }
+ sw2.Stop();
+
+ // Calculate and display results
+ var containsChineseMs = sw1.Elapsed.TotalMilliseconds;
+ var wordsHelperMs = sw2.Elapsed.TotalMilliseconds;
+ var speedRatio = wordsHelperMs / containsChineseMs;
+ var timeDifference = wordsHelperMs - containsChineseMs;
+
+ Console.WriteLine("RESULTS:");
+ Console.WriteLine($"ContainsChinese(): {containsChineseMs:F3} ms");
+ Console.WriteLine($"WordsHelper.HasChinese(): {wordsHelperMs:F3} ms");
+ Console.WriteLine($"Time difference: {timeDifference:F3} ms");
+ Console.WriteLine($"Speed improvement: {speedRatio:F2}x");
+ Console.WriteLine($"Performance gain: {((speedRatio - 1) * 100):F1}%");
+ Console.WriteLine();
+
+ if (speedRatio > 1.0)
+ {
+ Console.WriteLine($"✓ ContainsChinese() is {speedRatio:F2}x faster than WordsHelper.HasChinese()");
+ }
+ else
+ {
+ Console.WriteLine($"⚠ WordsHelper.HasChinese() is {(1/speedRatio):F2}x faster than ContainsChinese()");
+ }
+
+ // Test always passes - this is a measurement test
+ ClassicAssert.IsTrue(true);
+ }
+
+ [Test]
+ public void PerformanceComparison_ByStringType()
+ {
+ Console.WriteLine("=== PERFORMANCE BY STRING TYPE ===");
+
+ var categories = new Dictionary>
+ {
+ ["Pure English"] = _testStrings.Where(s => !ContainsChinese(s) && s.All(c => c <= 127)).ToList(),
+ ["Pure Chinese"] = _testStrings.Where(s => ContainsChinese(s) && s.All(c => IsChineseCharacter(c) || char.IsWhiteSpace(c))).ToList(),
+ ["Mixed Content"] = _testStrings.Where(s => ContainsChinese(s) && s.Any(c => c <= 127 && char.IsLetter(c))).ToList(),
+ ["Edge Cases"] = _testStrings.Where(s => string.IsNullOrWhiteSpace(s) || s.All(c => !char.IsLetter(c))).ToList()
+ };
+
+ foreach (var category in categories)
+ {
+ if (category.Value.Count == 0) continue;
+
+ Console.WriteLine($"\n{category.Key} ({category.Value.Count} strings):");
+
+ var sample = category.Value.First();
+ var displayText = sample.Length > 40 ? sample.Substring(0, 40) + "..." : sample;
+ Console.WriteLine($" Sample: '{displayText}'");
+
+ const int categoryIterations = 5000;
+
+ // Test each method
+ var sw1 = System.Diagnostics.Stopwatch.StartNew();
+ for (int i = 0; i < categoryIterations; i++)
+ {
+ foreach (var str in category.Value)
+ {
+ _ = ContainsChinese(str);
+ }
+ }
+ sw1.Stop();
+
+ var sw2 = System.Diagnostics.Stopwatch.StartNew();
+ for (int i = 0; i < categoryIterations; i++)
+ {
+ foreach (var str in category.Value)
+ {
+ _ = WordsHelper.HasChinese(str);
+ }
+ }
+ sw2.Stop();
+
+ var ratio = (double)sw2.ElapsedTicks / sw1.ElapsedTicks;
+ Console.WriteLine($" Performance: ContainsChinese is {ratio:F2}x faster");
+ }
+
+ ClassicAssert.IsTrue(true);
+ }
+
+ ///
+ /// Optimized Chinese character detection using comprehensive CJK Unicode ranges
+ /// This method uses ReadOnlySpan for better performance and covers all CJK character ranges
+ ///
+ private static bool ContainsChinese(ReadOnlySpan text)
+ {
+ foreach (var c in text)
+ {
+ if (IsChineseCharacter(c))
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Check if a character is a Chinese character using comprehensive Unicode ranges
+ /// Covers CJK Unified Ideographs and all extension blocks
+ ///
+ private static bool IsChineseCharacter(char c)
+ {
+ return (c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs (most common Chinese characters)
+ (c >= 0x3400 && c <= 0x4DBF) || // CJK Extension A
+ (c >= 0x20000 && c <= 0x2A6DF) || // CJK Extension B
+ (c >= 0x2A700 && c <= 0x2B73F) || // CJK Extension C
+ (c >= 0x2B740 && c <= 0x2B81F) || // CJK Extension D
+ (c >= 0x2B820 && c <= 0x2CEAF) || // CJK Extension E
+ (c >= 0x2CEB0 && c <= 0x2EBEF) || // CJK Extension F
+ (c >= 0xF900 && c <= 0xFAFF) || // CJK Compatibility Ideographs
+ (c >= 0x2F800 && c <= 0x2FA1F); // CJK Compatibility Supplement
+ }
+ }
+}