Explorar o código

Fixes unicode width handling for search

devmil %!s(int64=4) %!d(string=hai) anos
pai
achega
6e12265401

+ 7 - 4
lib/buffer/line/line.dart

@@ -157,10 +157,13 @@ class BufferLine {
     final length = getTrimmedLength();
     for (int i = 0; i < max(cols, length); i++) {
       var code = cellGetContent(i);
-      if (code == 0) {
-        searchString.writeAll(List<String>.filled(cellGetWidth(i), ' '));
-      } else {
-        searchString.writeCharCode(code);
+      if (code != 0) {
+        final cellString = String.fromCharCode(code);
+        searchString.write(cellString);
+        final widthDiff = cellGetWidth(i) - cellString.length;
+        if (widthDiff > 0) {
+          searchString.write(''.padRight(widthDiff));
+        }
       }
     }
     _searchStringCache = searchString.toString();

+ 18 - 1
lib/terminal/terminal_search.dart

@@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
 import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/terminal/terminal_search_interaction.dart';
 import 'package:xterm/util/constants.dart';
+import 'package:xterm/util/unicode_v11.dart';
 
 /// Represents a search result.
 /// This instance will be replaced as a whole when the search has to be re-triggered
@@ -240,6 +241,22 @@ class TerminalSearchTask {
     _lastSearchResult = null;
   }
 
+  String _createRegexPattern(String inputPattern) {
+    final result = StringBuffer();
+
+    for (final rune in inputPattern.runes) {
+      final runeString = String.fromCharCode(rune);
+      result.write(runeString);
+      final cellWidth = unicodeV11.wcwidth(rune);
+      final widthDiff = cellWidth - runeString.length;
+      if (widthDiff > 0) {
+        result.write(''.padRight(widthDiff));
+      }
+    }
+
+    return result.toString();
+  }
+
   /// returns the current search result or triggers a new search if it has to
   /// the result is a up to date search result either way
   TerminalSearchResult get searchResult {
@@ -257,7 +274,7 @@ class TerminalSearchTask {
       if (!_terminalSearchOptions.useRegex) {
         pattern = RegExp.escape(_pattern!);
       }
-      _searchRegexp = RegExp(pattern,
+      _searchRegexp = RegExp(_createRegexPattern(pattern),
           caseSensitive: _terminalSearchOptions.caseSensitive,
           multiLine: false);
     }

+ 64 - 2
test/terminal/terminal_search_test.dart

@@ -5,9 +5,11 @@ import 'package:mockito/annotations.dart';
 import 'package:mockito/mockito.dart';
 import 'package:xterm/buffer/buffer.dart';
 import 'package:xterm/buffer/line/line.dart';
+import 'package:xterm/terminal/cursor.dart';
 import 'package:xterm/terminal/terminal_search.dart';
 import 'package:xterm/terminal/terminal_search_interaction.dart';
 import 'package:xterm/util/circular_list.dart';
+import 'package:xterm/util/unicode_v11.dart';
 
 import 'terminal_search_test.mocks.dart';
 
@@ -70,7 +72,21 @@ void main() {
 
     test('Emoji search works', () {
       final fixture = _TestFixture();
-      fixture.expectTerminalSearchContent(['🍏🍎🍐🍊🍋🍌🍉🍇🍓🫐🍈🍒🍑']);
+      fixture.expectBufferContentLine([
+        '🍏',
+        '🍎',
+        '🍐',
+        '🍊',
+        '🍋',
+        '🍌',
+        '🍉',
+        '🍇',
+        '🍓',
+        '🫐',
+        '🍈',
+        '🍒',
+        '🍑'
+      ]);
       final task = fixture.uut.createSearchTask('testsearch');
       task.isActive = true;
       task.pattern = '🍋';
@@ -86,7 +102,7 @@ void main() {
 
     test('CJK search works', () {
       final fixture = _TestFixture();
-      fixture.expectTerminalSearchContent(['こんにちは世界']);
+      fixture.expectBufferContentLine(['こ', 'ん', 'に', 'ち', 'は', '世', '界']);
       final task = fixture.uut.createSearchTask('testsearch');
       task.isActive = true;
       task.pattern = 'は';
@@ -285,6 +301,16 @@ class _TestFixture {
     when(terminalSearchInteractionMock.terminalWidth).thenReturn(terminalWidth);
   }
 
+  void expectBufferContentLine(
+    List<String> cellData, {
+    isUsingAltBuffer = false,
+  }) {
+    final buffer = _getBufferFromCellData(cellData);
+    when(terminalSearchInteractionMock.buffer).thenReturn(buffer);
+    when(terminalSearchInteractionMock.isUsingAltBuffer())
+        .thenReturn(isUsingAltBuffer);
+  }
+
   void expectTerminalSearchContent(
     List<String> lines, {
     isUsingAltBuffer = false,
@@ -302,6 +328,16 @@ class _TestFixture {
   final terminalSearchInteractionMock = MockTerminalSearchInteraction();
   late final TerminalSearch uut;
 
+  MockBuffer _getBufferFromCellData(List<String> cellData) {
+    final result = MockBuffer();
+    final circularList = MockTerminalSearchTestCircularList();
+    when(result.lines).thenReturn(circularList);
+    when(circularList[0]).thenReturn(_getBufferLineFromData(cellData));
+    when(circularList.length).thenReturn(1);
+
+    return result;
+  }
+
   MockBuffer _getBuffer(
     List<String> lines, {
     isCached = true,
@@ -324,6 +360,32 @@ class _TestFixture {
     return result;
   }
 
+  BufferLine _getBufferLineFromData(List<String> cellData) {
+    final result = BufferLine(length: _terminalWidth);
+    int currentIndex = 0;
+    for (var data in cellData) {
+      final codePoint = data.runes.first;
+      final width = unicodeV11.wcwidth(codePoint);
+      result.cellInitialize(
+        currentIndex,
+        content: codePoint,
+        width: width,
+        cursor: Cursor(bg: 0, fg: 0, flags: 0),
+      );
+      currentIndex++;
+      for (int i = 1; i < width; i++) {
+        result.cellInitialize(
+          currentIndex,
+          content: 0,
+          width: 0,
+          cursor: Cursor(bg: 0, fg: 0, flags: 0),
+        );
+        currentIndex++;
+      }
+    }
+    return result;
+  }
+
   List<MockBufferLine> _getBufferLinesWithSearchContent(
     List<String> content, {
     isCached = true,