Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,12 @@
WMP
VSTHRD
CJK
XiaoHe

Check failure on line 107 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`XiaoH` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
ZiRanMa

Check failure on line 108 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`ZiR` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
WeiRuan

Check failure on line 109 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`WeiR` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
ZhiNengABC

Check failure on line 110 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`engABC` matches a line_forbidden.patterns entry: `(?:[a-z]|(?!'ed$)'){3,}(?:(?:[A-Z]|(?!'ed$)'){3,}|[A-Z](?:[a-z]|(?!'ed$)'){2,})`. (forbidden-pattern)

Check failure on line 110 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`ZhiN` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
ZiGuangPinYin

Check failure on line 111 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`uangPin` matches a line_forbidden.patterns entry: `(?:[a-z]|(?!'ed$)'){3,}(?:(?:[A-Z]|(?!'ed$)'){3,}|[A-Z](?:[a-z]|(?!'ed$)'){2,})`. (forbidden-pattern)

Check failure on line 111 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`ZiG` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
PinYinJiaJia

Check failure on line 112 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`JiaJ` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)

Check failure on line 112 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`PinY` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
XingKongJianDao

Check failure on line 113 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`XingK` matches a line_forbidden.patterns entry: `[A-Z](?:[A-Z]|[a-z]|')*?[a-z](?:[a-z]|')*[A-Z]`. (forbidden-pattern)
DaNiu
XiaoLang
2 changes: 1 addition & 1 deletion Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@
}
}

private DoublePinyinSchemas _doublePinyinSchema = DoublePinyinSchemas.XiaoHe;

Check warning on line 359 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Xiao` is not a recognized word. (unrecognized-spelling)

[JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))]
public DoublePinyinSchemas DoublePinyinSchema
Expand Down Expand Up @@ -514,7 +514,7 @@
{
var list = FixedHotkeys();

// Customizeable hotkeys
// Customizable hotkeys
if (!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if (!string.IsNullOrEmpty(PreviewHotkey))
Expand Down Expand Up @@ -648,14 +648,14 @@

public enum DoublePinyinSchemas
{
XiaoHe,

Check warning on line 651 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Xiao` is not a recognized word. (unrecognized-spelling)
ZiRanMa,
WeiRuan,

Check warning on line 653 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Ruan` is not a recognized word. (unrecognized-spelling)
ZhiNengABC,

Check warning on line 654 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Zhi` is not a recognized word. (unrecognized-spelling)
ZiGuangPinYin,
PinYinJiaJia,

Check warning on line 656 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Jia` is not a recognized word. (unrecognized-spelling)
XingKongJianDao,
DaNiu,
XiaoLang

Check warning on line 659 in Flow.Launcher.Infrastructure/UserSettings/Settings.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Xiao` is not a recognized word. (unrecognized-spelling)
}
}
265 changes: 265 additions & 0 deletions Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Legacy;

Check warning on line 6 in Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`NUnit` is not a recognized word. (unrecognized-spelling)
using Flow.Launcher.Infrastructure;
using ToolGood.Words.Pinyin;

namespace Flow.Launcher.Test
{
/// <summary>
/// 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.
/// </summary>
[TestFixture]
public class ChineseDetectionPerformanceTest
{
private readonly List<string> _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();

Check warning on line 141 in Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Finalizers` is not a recognized word. (unrecognized-spelling)
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<string, List<string>>
{
["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);
}

/// <summary>
/// Optimized Chinese character detection using comprehensive CJK Unicode ranges
/// This method uses ReadOnlySpan for better performance and covers all CJK character ranges
/// </summary>
private static bool ContainsChinese(ReadOnlySpan<char> text)
{
foreach (var c in text)
{
if (IsChineseCharacter(c))
return true;
}
return false;
}

/// <summary>
/// Check if a character is a Chinese character using comprehensive Unicode ranges
/// Covers CJK Unified Ideographs and all extension blocks
/// </summary>
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
}
}
}
2 changes: 2 additions & 0 deletions Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@
<cc:ExCard
Title="{DynamicResource ShouldUseDoublePinyin}"
Icon="&#xf085;"
Visibility="{ext:VisibleWhen {Binding ShouldUsePinyin},
IsEqualToBool=True}"
Sub="{DynamicResource ShouldUseDoublePinyinToolTip}">
<cc:ExCard.SideContent>
<ui:ToggleSwitch
Expand Down
Loading