terminal_search.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import 'package:xterm/buffer/line/line.dart';
  2. import 'package:xterm/terminal/terminal.dart';
  3. class TerminalSearchResult {
  4. final _hitsByLine = Map<int, List<TerminalSearchHit>>();
  5. TerminalSearchResult.fromHits(List<TerminalSearchHit> hits) {
  6. for (final hit in hits) {
  7. if (!_hitsByLine.containsKey(hit.startLineIndex)) {
  8. _hitsByLine[hit.startLineIndex] =
  9. List<TerminalSearchHit>.empty(growable: true);
  10. }
  11. if (!_hitsByLine.containsKey(hit.endLineIndex)) {
  12. _hitsByLine[hit.endLineIndex] =
  13. List<TerminalSearchHit>.empty(growable: true);
  14. }
  15. _hitsByLine[hit.startLineIndex]!.add(hit);
  16. if (hit.startLineIndex != hit.endLineIndex) {
  17. _hitsByLine[hit.endLineIndex]!.add(hit);
  18. }
  19. }
  20. }
  21. TerminalSearchResult.empty();
  22. bool hasEntriesForLine(int line) {
  23. return _hitsByLine.containsKey(line);
  24. }
  25. List<TerminalSearchHit> getEntriesForLine(int line) {
  26. return _hitsByLine[line] ?? List<TerminalSearchHit>.empty(growable: false);
  27. }
  28. bool contains(int line, int col) {
  29. return _hitsByLine[line]?.any((hit) => hit.contains(line, col)) ?? false;
  30. }
  31. }
  32. class TerminalSearchHit {
  33. TerminalSearchHit(
  34. this.startLineIndex, this.startIndex, this.endLineIndex, this.endIndex);
  35. final int startLineIndex;
  36. final int startIndex;
  37. final int endLineIndex;
  38. final int endIndex;
  39. bool contains(int line, int col) {
  40. if (line < startLineIndex || line > endLineIndex) {
  41. return false;
  42. }
  43. if (line == startLineIndex && startLineIndex == endLineIndex) {
  44. return col >= startIndex && col < endIndex;
  45. }
  46. if (line == startLineIndex) {
  47. return col >= startIndex;
  48. }
  49. if (line == endLineIndex) {
  50. return col < endIndex;
  51. }
  52. // here we are sure that the given point is inside a full line match
  53. return true;
  54. }
  55. }
  56. typedef MarkSearchDoneFunc = void Function(BufferLine line);
  57. typedef IsSearchDirtyFunc = bool Function(BufferLine line);
  58. class TerminalSearch {
  59. TerminalSearch(
  60. this._terminal, this._markSearchDoneFunc, this._isSearchDirtyFunc);
  61. final Terminal _terminal;
  62. final MarkSearchDoneFunc _markSearchDoneFunc;
  63. final IsSearchDirtyFunc _isSearchDirtyFunc;
  64. String? _lastSearchPattern = null;
  65. TerminalSearchResult? _lastSearchResult = null;
  66. bool? _hasBeenUsingAltBuffer;
  67. TerminalSearchResult doSearch(String searchPattern) {
  68. final bufferLength = _terminal.buffer.lines.length;
  69. final terminalWidth = _terminal.terminalWidth;
  70. var isSearchDirty = false;
  71. //check if the search is dirty and return if not
  72. if (_lastSearchPattern != null &&
  73. _lastSearchPattern == searchPattern &&
  74. _lastSearchResult != null &&
  75. _hasBeenUsingAltBuffer != null &&
  76. _hasBeenUsingAltBuffer! == _terminal.isUsingAltBuffer()) {
  77. for (var i = 0; i < bufferLength; i++) {
  78. if (_isSearchDirtyFunc(_terminal.buffer.lines[i])) {
  79. isSearchDirty = true;
  80. break;
  81. }
  82. }
  83. } else {
  84. isSearchDirty = true;
  85. }
  86. if (!isSearchDirty) {
  87. return _lastSearchResult!;
  88. }
  89. //TODO: make caseSensitive an option
  90. final searchRegex =
  91. RegExp(searchPattern, caseSensitive: false, multiLine: false);
  92. final result = List<TerminalSearchHit>.empty(growable: true);
  93. final bufferContent = StringBuffer();
  94. for (var i = 0; i < bufferLength; i++) {
  95. final BufferLine line = _terminal.buffer.lines[i];
  96. final searchString = line.toSearchString(terminalWidth);
  97. _markSearchDoneFunc(line);
  98. bufferContent.write(searchString);
  99. if (searchString.length < terminalWidth) {
  100. // fill up so that the row / col can be mapped back later on
  101. bufferContent.writeAll(
  102. List<String>.filled(terminalWidth - searchString.length, ' '));
  103. }
  104. }
  105. for (final match in searchRegex.allMatches(bufferContent.toString())) {
  106. final startLineIndex = (match.start / terminalWidth).floor();
  107. final endLineIndex = (match.end / terminalWidth).floor();
  108. // subtract the lines that got added in order to get the index inside the line
  109. final startIndex = match.start - startLineIndex * terminalWidth;
  110. final endIndex = match.end - endLineIndex * terminalWidth;
  111. result.add(
  112. TerminalSearchHit(
  113. startLineIndex,
  114. startIndex,
  115. endLineIndex,
  116. endIndex,
  117. ),
  118. );
  119. }
  120. _lastSearchPattern = searchPattern;
  121. _lastSearchResult = TerminalSearchResult.fromHits(result);
  122. _hasBeenUsingAltBuffer = _terminal.isUsingAltBuffer();
  123. return _lastSearchResult!;
  124. }
  125. }