Browse Source

Introduces TerminalSearchOptions

Fixes Release Mode pattern behaviour (value was only updated on the next TerminalState copying)
devmil 4 năm trước cách đây
mục cha
commit
3e3685b878

+ 14 - 10
lib/terminal/terminal.dart

@@ -22,6 +22,7 @@ import 'package:xterm/terminal/terminal_ui_interaction.dart';
 import 'package:xterm/theme/terminal_color.dart';
 import 'package:xterm/theme/terminal_color.dart';
 import 'package:xterm/theme/terminal_theme.dart';
 import 'package:xterm/theme/terminal_theme.dart';
 import 'package:xterm/theme/terminal_themes.dart';
 import 'package:xterm/theme/terminal_themes.dart';
+import 'package:xterm/util/constants.dart';
 import 'package:xterm/util/debug_handler.dart';
 import 'package:xterm/util/debug_handler.dart';
 import 'package:xterm/util/observable.dart';
 import 'package:xterm/util/observable.dart';
 
 
@@ -487,14 +488,6 @@ class Terminal with Observable implements TerminalUiInteraction {
     }
     }
   }
   }
 
 
-  final wordSeparatorCodes = <String>[
-    String.fromCharCode(0),
-    ' ',
-    '.',
-    ':',
-    '/'
-  ];
-
   void selectWordOrRow(Position position) {
   void selectWordOrRow(Position position) {
     if (position.y > buffer.lines.length) {
     if (position.y > buffer.lines.length) {
       return;
       return;
@@ -524,7 +517,7 @@ class Terminal with Observable implements TerminalUiInteraction {
           break;
           break;
         }
         }
         final content = line.cellGetContent(start - 1);
         final content = line.cellGetContent(start - 1);
-        if (wordSeparatorCodes.contains(String.fromCharCode(content))) {
+        if (kWordSeparators.contains(String.fromCharCode(content))) {
           break;
           break;
         }
         }
         start--;
         start--;
@@ -534,7 +527,7 @@ class Terminal with Observable implements TerminalUiInteraction {
           break;
           break;
         }
         }
         final content = line.cellGetContent(end + 1);
         final content = line.cellGetContent(end + 1);
-        if (wordSeparatorCodes.contains(String.fromCharCode(content))) {
+        if (kWordSeparators.contains(String.fromCharCode(content))) {
           break;
           break;
         }
         }
         end++;
         end++;
@@ -543,6 +536,7 @@ class Terminal with Observable implements TerminalUiInteraction {
       _selection.clear();
       _selection.clear();
       _selection.init(Position(start, row));
       _selection.init(Position(start, row));
       _selection.update(Position(end, row));
       _selection.update(Position(end, row));
+      refresh();
     }
     }
   }
   }
 
 
@@ -727,6 +721,7 @@ class Terminal with Observable implements TerminalUiInteraction {
   void selectAll() {
   void selectAll() {
     _selection.init(Position(0, 0));
     _selection.init(Position(0, 0));
     _selection.update(Position(terminalWidth, bufferHeight));
     _selection.update(Position(terminalWidth, bufferHeight));
+    refresh();
   }
   }
 
 
   String _composingString = '';
   String _composingString = '';
@@ -743,6 +738,15 @@ class Terminal with Observable implements TerminalUiInteraction {
   @override
   @override
   TerminalSearchResult get userSearchResult => _userSearchTask.searchResult;
   TerminalSearchResult get userSearchResult => _userSearchTask.searchResult;
 
 
+  @override
+  TerminalSearchOptions get userSearchOptions => _userSearchTask.options;
+
+  @override
+  void set userSearchOptions(TerminalSearchOptions options) {
+    _userSearchTask.options = options;
+    refresh();
+  }
+
   @override
   @override
   String? get userSearchPattern => _userSearchTask.pattern;
   String? get userSearchPattern => _userSearchTask.pattern;
 
 

+ 31 - 2
lib/terminal/terminal_isolate.dart

@@ -34,7 +34,8 @@ enum _IsolateCommand {
   paste,
   paste,
   terminateBackend,
   terminateBackend,
   updateComposingString,
   updateComposingString,
-  updateSearchPattern
+  updateSearchPattern,
+  updateSearchOptions,
 }
 }
 
 
 enum _IsolateEvent {
 enum _IsolateEvent {
@@ -150,6 +151,7 @@ void terminalMain(SendPort port) async {
             _terminal.composingString,
             _terminal.composingString,
             _terminal.userSearchResult,
             _terminal.userSearchResult,
             _terminal.userSearchPattern,
             _terminal.userSearchPattern,
+            _terminal.userSearchOptions,
           );
           );
           port.send([_IsolateEvent.newState, newState]);
           port.send([_IsolateEvent.newState, newState]);
           _needNotify = true;
           _needNotify = true;
@@ -167,6 +169,9 @@ void terminalMain(SendPort port) async {
       case _IsolateCommand.updateSearchPattern:
       case _IsolateCommand.updateSearchPattern:
         _terminal?.userSearchPattern = msg[1];
         _terminal?.userSearchPattern = msg[1];
         break;
         break;
+      case _IsolateCommand.updateSearchOptions:
+        _terminal?.userSearchOptions = msg[1];
+        break;
     }
     }
   }
   }
 }
 }
@@ -214,6 +219,7 @@ class TerminalState {
 
 
   TerminalSearchResult searchResult;
   TerminalSearchResult searchResult;
   String? userSearchPattern;
   String? userSearchPattern;
+  TerminalSearchOptions userSearchOptions;
 
 
   TerminalState(
   TerminalState(
     this.scrollOffsetFromBottom,
     this.scrollOffsetFromBottom,
@@ -233,6 +239,7 @@ class TerminalState {
     this.composingString,
     this.composingString,
     this.searchResult,
     this.searchResult,
     this.userSearchPattern,
     this.userSearchPattern,
+    this.userSearchOptions,
   );
   );
 }
 }
 
 
@@ -399,6 +406,7 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
           break;
           break;
         case _IsolateEvent.newState:
         case _IsolateEvent.newState:
           _lastState = message[1];
           _lastState = message[1];
+          _updateLocalCaches();
           if (!initialRefreshCompleted.isCompleted) {
           if (!initialRefreshCompleted.isCompleted) {
             initialRefreshCompleted.complete(true);
             initialRefreshCompleted.complete(true);
           }
           }
@@ -547,11 +555,26 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   TerminalSearchResult get userSearchResult =>
   TerminalSearchResult get userSearchResult =>
       _lastState?.searchResult ?? TerminalSearchResult.empty();
       _lastState?.searchResult ?? TerminalSearchResult.empty();
 
 
+  TerminalSearchOptions? _localUserSearchOptionsCache;
+
+  @override
+  TerminalSearchOptions get userSearchOptions =>
+      _localUserSearchOptionsCache ?? TerminalSearchOptions();
+
+  @override
+  void set userSearchOptions(TerminalSearchOptions options) {
+    _localUserSearchOptionsCache = options;
+    _sendPort?.send([_IsolateCommand.updateSearchOptions, options]);
+  }
+
+  String? _localUserSearchPatternCache;
+
   @override
   @override
-  String? get userSearchPattern => _lastState?.userSearchPattern;
+  String? get userSearchPattern => _localUserSearchPatternCache;
 
 
   @override
   @override
   void set userSearchPattern(String? newValue) {
   void set userSearchPattern(String? newValue) {
+    _localUserSearchPatternCache = newValue;
     _sendPort?.send([_IsolateCommand.updateSearchPattern, newValue]);
     _sendPort?.send([_IsolateCommand.updateSearchPattern, newValue]);
   }
   }
 
 
@@ -564,4 +587,10 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
   void set isUserSearchActive(bool isUserSearchActive) {
   void set isUserSearchActive(bool isUserSearchActive) {
     _isUserSearchActive = isUserSearchActive;
     _isUserSearchActive = isUserSearchActive;
   }
   }
+
+  void _updateLocalCaches() {
+    _localUserSearchPatternCache = _lastState?.userSearchPattern;
+    _localUserSearchOptionsCache =
+        _lastState?.userSearchOptions ?? TerminalSearchOptions();
+  }
 }
 }

+ 76 - 8
lib/terminal/terminal_search.dart

@@ -1,5 +1,7 @@
+import 'package:equatable/equatable.dart';
 import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/terminal/terminal.dart';
 import 'package:xterm/terminal/terminal.dart';
+import 'package:xterm/util/constants.dart';
 
 
 class TerminalSearchResult {
 class TerminalSearchResult {
   final _hitsByLine = Map<int, List<TerminalSearchHit>>();
   final _hitsByLine = Map<int, List<TerminalSearchHit>>();
@@ -68,8 +70,40 @@ class TerminalSearchHit {
   }
   }
 }
 }
 
 
+class TerminalSearchOptions extends Equatable {
+  TerminalSearchOptions({
+    this.caseSensitive = false,
+    this.matchWholeWord = false,
+    this.useRegex = false,
+  });
+
+  final bool caseSensitive;
+  final bool matchWholeWord;
+  final bool useRegex;
+
+  TerminalSearchOptions copyWith(
+      {bool? caseSensitive, bool? matchWholeWord, bool? useRegex}) {
+    return TerminalSearchOptions(
+      caseSensitive: caseSensitive ?? this.caseSensitive,
+      matchWholeWord: matchWholeWord ?? this.matchWholeWord,
+      useRegex: useRegex ?? this.useRegex,
+    );
+  }
+
+  @override
+  bool get stringify => true;
+
+  @override
+  List<Object?> get props => [
+        caseSensitive,
+        matchWholeWord,
+        useRegex,
+      ];
+}
+
 class TerminalSearchTask {
 class TerminalSearchTask {
-  TerminalSearchTask(this._search, this._terminal, this._dirtyTagName);
+  TerminalSearchTask(this._search, this._terminal, this._dirtyTagName,
+      this._terminalSearchOptions);
 
 
   final TerminalSearch _search;
   final TerminalSearch _search;
   final Terminal _terminal;
   final Terminal _terminal;
@@ -77,6 +111,7 @@ class TerminalSearchTask {
   bool _isPatternDirty = true;
   bool _isPatternDirty = true;
   RegExp? _searchRegexp = null;
   RegExp? _searchRegexp = null;
   final String _dirtyTagName;
   final String _dirtyTagName;
+  TerminalSearchOptions _terminalSearchOptions;
 
 
   bool? _hasBeenUsingAltBuffer;
   bool? _hasBeenUsingAltBuffer;
   TerminalSearchResult? _lastSearchResult = null;
   TerminalSearchResult? _lastSearchResult = null;
@@ -125,6 +160,16 @@ class TerminalSearchTask {
     }
     }
   }
   }
 
 
+  TerminalSearchOptions get options => _terminalSearchOptions;
+  void set options(TerminalSearchOptions newOptions) {
+    if (_terminalSearchOptions == newOptions) {
+      return;
+    }
+    _terminalSearchOptions = newOptions;
+    _isPatternDirty = true;
+    _searchRegexp = null;
+  }
+
   TerminalSearchResult get searchResult {
   TerminalSearchResult get searchResult {
     if (_pattern == null) {
     if (_pattern == null) {
       return TerminalSearchResult.empty();
       return TerminalSearchResult.empty();
@@ -135,21 +180,43 @@ class TerminalSearchTask {
 
 
     final terminalWidth = _terminal.terminalWidth;
     final terminalWidth = _terminal.terminalWidth;
 
 
-    //TODO: make caseSensitive an option
     if (_searchRegexp == null) {
     if (_searchRegexp == null) {
-      _searchRegexp = RegExp(_pattern!, caseSensitive: false, multiLine: false);
+      var pattern = _pattern!;
+      if (!_terminalSearchOptions.useRegex) {
+        pattern = RegExp.escape(_pattern!);
+      }
+      final regex = '(?<hit>$pattern)';
+      _searchRegexp = RegExp(regex,
+          caseSensitive: _terminalSearchOptions.caseSensitive,
+          multiLine: false);
     }
     }
 
 
     final hits = List<TerminalSearchHit>.empty(growable: true);
     final hits = List<TerminalSearchHit>.empty(growable: true);
 
 
     for (final match
     for (final match
         in _searchRegexp!.allMatches(_search.terminalSearchString)) {
         in _searchRegexp!.allMatches(_search.terminalSearchString)) {
-      final startLineIndex = (match.start / terminalWidth).floor();
-      final endLineIndex = (match.end / terminalWidth).floor();
+      final start = match.start;
+      final end = match.end;
+      final startLineIndex = (start / terminalWidth).floor();
+      final endLineIndex = (end / terminalWidth).floor();
 
 
       // subtract the lines that got added in order to get the index inside the line
       // subtract the lines that got added in order to get the index inside the line
-      final startIndex = match.start - startLineIndex * terminalWidth;
-      final endIndex = match.end - endLineIndex * terminalWidth;
+      final startIndex = start - startLineIndex * terminalWidth;
+      final endIndex = end - endLineIndex * terminalWidth;
+
+      if (_terminalSearchOptions.matchWholeWord) {
+        // we match a whole word when the hit fulfills:
+        // 1) starts at a line beginning or has a word-separator before it
+        final startIsOK =
+            startIndex == 0 || kWordSeparators.contains(match.input[start - 1]);
+        // 2) ends with a line or has a word-separator after it
+        final endIsOK = endIndex == terminalWidth ||
+            kWordSeparators.contains(match.input[end]);
+
+        if (!startIsOK || !endIsOK) {
+          continue;
+        }
+      }
 
 
       hits.add(
       hits.add(
         TerminalSearchHit(
         TerminalSearchHit(
@@ -178,7 +245,8 @@ class TerminalSearch {
   int? _lastTerminalWidth;
   int? _lastTerminalWidth;
 
 
   TerminalSearchTask createSearchTask(String dirtyTagName) {
   TerminalSearchTask createSearchTask(String dirtyTagName) {
-    return TerminalSearchTask(this, _terminal, dirtyTagName);
+    return TerminalSearchTask(
+        this, _terminal, dirtyTagName, TerminalSearchOptions());
   }
   }
 
 
   String get terminalSearchString {
   String get terminalSearchString {

+ 7 - 0
lib/terminal/terminal_ui_interaction.dart

@@ -138,6 +138,13 @@ abstract class TerminalUiInteraction with Observable {
   /// returns the list of search hits
   /// returns the list of search hits
   TerminalSearchResult get userSearchResult;
   TerminalSearchResult get userSearchResult;
 
 
+  /// gets the current user search options
+  TerminalSearchOptions get userSearchOptions;
+
+  /// sets new user search options. This invalidates the cached search hits and
+  /// will re-trigger a new search
+  void set userSearchOptions(TerminalSearchOptions options);
+
   /// the search pattern of a currently active search or [null]
   /// the search pattern of a currently active search or [null]
   String? get userSearchPattern;
   String? get userSearchPattern;
 
 

+ 14 - 0
lib/util/constants.dart

@@ -11,3 +11,17 @@ const kProfileMode = bool.fromEnvironment(
 const kDebugMode = !kReleaseMode && !kProfileMode;
 const kDebugMode = !kReleaseMode && !kProfileMode;
 
 
 const kIsWeb = identical(0, 0.0);
 const kIsWeb = identical(0, 0.0);
+
+final kWordSeparators = [
+  String.fromCharCode(0),
+  ' ',
+  '.',
+  ':',
+  '-',
+  '\'',
+  '"',
+  '*',
+  '+',
+  '/',
+  '\\'
+];