Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add regex and case sensitive to FindReplaceMenu #480

Merged
merged 48 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c2fa26a
fix: fix `_needsLayout`
sun-jiao Sep 14, 2023
6b91334
fix: wrap `SizedBox(child: IconButton())` as a function
sun-jiao Sep 14, 2023
601b961
Merge branch 'AppFlowy-IO:main' into find-replace-fix-1
sun-jiao Sep 15, 2023
4bedca4
chore: reformat files
sun-jiao Sep 15, 2023
bba13d2
Merge branch 'AppFlowy-IO:main' into find-replace-fix-1
sun-jiao Sep 15, 2023
6e67ced
feat: support regex and case sensitive in find and replace
sun-jiao Sep 16, 2023
40845a0
fix: remove `_rSearchMethod` because it is slightly slower than `all…
sun-jiao Sep 17, 2023
666f63d
feat: add 2 `SearchAlgorithm`s
sun-jiao Sep 17, 2023
e9121b9
fix: default caseSensitive is true
sun-jiao Sep 17, 2023
2fcbcad
test: add regex and case insensitive search test
sun-jiao Sep 17, 2023
44db679
chore: reformat file
sun-jiao Sep 17, 2023
20bd13a
fix: should be insensitive
sun-jiao Sep 17, 2023
58e7565
fix: update matches when `regex` or `caseSensitive` changed.
sun-jiao Sep 17, 2023
2d0d693
feat: change Range to Match
sun-jiao Sep 17, 2023
a342d98
Merge branch 'main' into find-replace-feat
sun-jiao Sep 17, 2023
9b2b2b0
chore: add "button" to button name
sun-jiao Sep 18, 2023
d7e84a2
Merge branch 'main' into find-replace-feat
sun-jiao Sep 18, 2023
77b3f5f
Merge branch 'main' into find-replace-feat
sun-jiao Sep 18, 2023
708eac3
fix: avoid creating too many instances
sun-jiao Sep 18, 2023
4b9bd28
chore: move throw error out of "else" branch
sun-jiao Sep 18, 2023
e324d5c
chore: rename method.
sun-jiao Sep 18, 2023
2caa385
Merge branch 'find-replace-feat' of https://github.com/sun-jiao/appfl…
sun-jiao Sep 18, 2023
e110509
chore: update comment
sun-jiao Sep 18, 2023
ff06b7d
chore: camel case
sun-jiao Sep 18, 2023
ab9f7b5
Merge branch 'AppFlowy-IO:main' into find-replace-feat
sun-jiao Sep 19, 2023
4181f00
feat: use dart built-in regex search
sun-jiao Sep 19, 2023
addea23
feat: add `regexFlag` and `caseSensitiveFlag`
sun-jiao Sep 19, 2023
7cab003
fix: use flags
sun-jiao Sep 19, 2023
7fd4d49
Merge branch 'main' into find-replace-feat
sun-jiao Sep 20, 2023
476888a
feat: extract icons
sun-jiao Sep 20, 2023
90368cd
chore: reformat file
sun-jiao Sep 21, 2023
d507155
Merge remote-tracking branch 'upstream/main' into find-replace-feat
sun-jiao Sep 21, 2023
d1ea4ab
test: add regex and case test case
sun-jiao Sep 21, 2023
16af1c3
chore: reformat test cases
sun-jiao Sep 21, 2023
97f067d
Merge branch 'AppFlowy-IO:main' into find-replace-feat
sun-jiao Sep 24, 2023
21f83fa
Merge branch 'AppFlowy-IO:main' into find-replace-feat
sun-jiao Sep 27, 2023
ee39de2
merge
sun-jiao Oct 4, 2023
4701db3
revert example
sun-jiao Oct 7, 2023
d35c4e8
Update search_service_v2.dart
sun-jiao Oct 7, 2023
0a53503
update to v2
sun-jiao Oct 7, 2023
8c69ea6
wrap match in MatchWrap
sun-jiao Oct 7, 2023
ac34ee2
rename it as SearchServiceV3, restore search service v1 and v2
sun-jiao Oct 7, 2023
e0f3921
chore: reformat files
sun-jiao Oct 7, 2023
484fd67
chore: typo
sun-jiao Oct 7, 2023
4a206b1
Merge branch 'AppFlowy-IO:main' into find-replace-feat
sun-jiao Oct 7, 2023
44033f2
chore: typo
sun-jiao Oct 7, 2023
cf9a2d4
Merge branch 'find-replace-feat' of https://github.com/sun-jiao/appfl…
sun-jiao Oct 7, 2023
4574659
chore: typo
sun-jiao Oct 7, 2023
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
2 changes: 2 additions & 0 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
"closeFind": "Close",
"replace": "Replace",
"replaceAll": "Replace all",
"regex": "Regex",
"caseSensitive": "Case sensitive",
"uploadImage": "Upload Image",
"urlImage": "URL Image",
"incorrectLink": "Incorrect Link",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_zh_CN.arb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
"closeFind": "关闭",
"replace": "替换",
"replaceAll": "替换全部",
"regex": "正则表达式",
"caseSensitive": "区分大小写",
"uploadImage": "上传图片",
"urlImage": "网络图片",
"incorrectLink": "链接错误",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/intl_zh_TW.arb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
"closeFind": "關閉",
"replace": "取代",
"replaceAll": "取代全部",
"regex": "正規表示式",
"caseSensitive": "區分大小寫",
"uploadImage": "上載圖片",
"urlImage": "網路圖片",
"incorrectLink": "連結錯誤",
Expand Down
45 changes: 44 additions & 1 deletion lib/src/editor/find_replace_menu/find_replace_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,16 @@
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
IconButton(
FindMenuIconButton(
onPressed: () => setState(
() => replaceFlag = !replaceFlag,
),
iconSize: _iconSize,
icon: replaceFlag
? const Icon(Icons.expand_less)
: const Icon(Icons.expand_more),
Expand Down Expand Up @@ -126,11 +129,51 @@
tooltip: widget.localizations?.close ??
AppFlowyEditorLocalizations.current.closeFind,
),
FindMenuIconButton(
key: const Key('findRegexButton'),
iconSize: _iconSize,
onPressed: () {
setState(() {
searchService.isRegex = !searchService.isRegex;

Check warning on line 137 in lib/src/editor/find_replace_menu/find_replace_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/find_replace_widget.dart#L135-L137

Added lines #L135 - L137 were not covered by tests
});
_searchPattern();

Check warning on line 139 in lib/src/editor/find_replace_menu/find_replace_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/find_replace_widget.dart#L139

Added line #L139 was not covered by tests
},
icon: Text(
'R*',
style: TextStyle(
color: searchService.isRegex ? Colors.black : Colors.grey,
),
),
tooltip: AppFlowyEditorLocalizations.current.regex,
),
FindMenuIconButton(
key: const Key('caseSensitiveButton'),
iconSize: _iconSize,
onPressed: () {
setState(() {
searchService.caseSensitive = !searchService.caseSensitive;

Check warning on line 154 in lib/src/editor/find_replace_menu/find_replace_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/find_replace_widget.dart#L152-L154

Added lines #L152 - L154 were not covered by tests
});
_searchPattern();

Check warning on line 156 in lib/src/editor/find_replace_menu/find_replace_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/find_replace_widget.dart#L156

Added line #L156 was not covered by tests
},
icon: Text(
'Cc',
sun-jiao marked this conversation as resolved.
Show resolved Hide resolved
style: TextStyle(
color:
searchService.caseSensitive ? Colors.black : Colors.grey,
),
),
tooltip: AppFlowyEditorLocalizations.current.caseSensitive,
),
],
),
replaceFlag
? Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
width: 2 * _iconSize,
),
SizedBox(
width: 200,
height: 30,
Expand Down
70 changes: 66 additions & 4 deletions lib/src/editor/find_replace_menu/search_algorithm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,61 @@
/// `searchMethod(String pattern, String text)`, here `pattern` is the sequence of
/// characters that are to be searched within the `text`.
abstract class SearchAlgorithm {
List<int> searchMethod(String pattern, String text);
Iterable<Match> searchMethod(Pattern pattern, String text);
}

final class BoyerMooreMatch implements Match {
const BoyerMooreMatch(
this.pattern,
this.input,
this.start,
) : end = start + pattern.length;

@override
final int start;
@override
final String input;
@override
final String pattern;
@override
final int end;
@override
final int groupCount = 0;

@override
String operator [](int g) => group(g);

Check warning on line 30 in lib/src/editor/find_replace_menu/search_algorithm.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/search_algorithm.dart#L29-L30

Added lines #L29 - L30 were not covered by tests

@override

Check warning on line 32 in lib/src/editor/find_replace_menu/search_algorithm.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/search_algorithm.dart#L32

Added line #L32 was not covered by tests
String group(int group) {
if (group != 0) {
throw RangeError.value(group);

Check warning on line 35 in lib/src/editor/find_replace_menu/search_algorithm.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/search_algorithm.dart#L34-L35

Added lines #L34 - L35 were not covered by tests
}
return pattern;

Check warning on line 37 in lib/src/editor/find_replace_menu/search_algorithm.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/search_algorithm.dart#L37

Added line #L37 was not covered by tests
}

@override
List<String> groups(List<int> groups) => groups.map((e) => group(e)).toList();

Check warning on line 41 in lib/src/editor/find_replace_menu/search_algorithm.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/search_algorithm.dart#L40-L41

Added lines #L40 - L41 were not covered by tests
}

class BoyerMoore extends SearchAlgorithm {
//This is a standard algorithm used for searching patterns in long text samples
//It is more efficient than brute force searching because it is able to skip
//characters that will never possibly match with required pattern.
@override
List<int> searchMethod(String pattern, String text) {
List<Match> searchMethod(Pattern pattern, String text) {
if (pattern is String) {
return _searchMethod(pattern, text);
} else {
throw UnimplementedError();

Check warning on line 53 in lib/src/editor/find_replace_menu/search_algorithm.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/find_replace_menu/search_algorithm.dart#L53

Added line #L53 was not covered by tests
}
sun-jiao marked this conversation as resolved.
Show resolved Hide resolved
}

List<Match> _searchMethod(String pattern, String text) {
int m = pattern.length;
int n = text.length;

Map<String, int> badchar = {};
List<int> matches = [];
List<Match> matches = [];

_badCharHeuristic(pattern, m, badchar);

Expand All @@ -33,7 +74,7 @@

//if pattern is present at current shift, the index will become -1
if (j < 0) {
matches.add(s);
matches.add(BoyerMooreMatch(pattern, text, s));
s += (s + m < n) ? m - (badchar[text[s + m]] ?? -1) : 1;
} else {
s += math.max(1, j - (badchar[text[s + j]] ?? -1));
Expand All @@ -54,3 +95,24 @@
}
}
}

class DartBuiltin extends SearchAlgorithm {
sun-jiao marked this conversation as resolved.
Show resolved Hide resolved
@override
Iterable<Match> searchMethod(Pattern pattern, String text) {
return pattern.allMatches(text);
}
}

class Mixture extends SearchAlgorithm {
SearchAlgorithm boyerMoore = BoyerMoore();
SearchAlgorithm builtin = DartBuiltin();

@override
Iterable<Match> searchMethod(Pattern pattern, String text) {
if (pattern is String) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sun-jiao @Xazin, do we need to create a Mixture class?
If DartBuiltin works in all cases, we should just use that, since it is faster.
And keep BoyerMoore as an example for the developer if they want to implement their own algorithm.
Also then, we could remove the BoyerMoore implementation from the code and only show it in Docs as an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My tests showed BoyerMoore to be slightly slower. But auto-generated random strings were used in my tests.

I think BoyerMoore should be more efficient for natural language. Because most languages that use alphabetic scripts have prefixes and suffixes that are shared between different words.

Copy link
Contributor Author

@sun-jiao sun-jiao Sep 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have read some articles or papers on Boyer-Moore style regex search algorithm. But they're not as concise as the original Boyer-Moore.

Rust community also has a similar issue. rust-lang/regex#197

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They have already implemented it: rust-lang/regex#419. I'll take a look tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a brief look and this still seems to be a plain text search algorithm, not regex. Even though it's in the regex carte.

return boyerMoore.searchMethod(pattern, text);
} else {
return builtin.searchMethod(pattern, text);
}
}
}
Loading
Loading