-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
SearchHelper.ts
154 lines (137 loc) · 4.49 KB
/
SearchHelper.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ISearchHelper, ISearchAddonTerminal } from './Interfaces';
interface ISearchResult {
term: string;
col: number;
row: number;
}
/**
* A class that knows how to search the terminal and how to display the results.
*/
export class SearchHelper implements ISearchHelper {
constructor(private _terminal: ISearchAddonTerminal) {
// TODO: Search for multiple instances on 1 line
// TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
// TODO: Highlight other instances in the viewport
// TODO: Support regex, case sensitivity, etc.
}
/**
* Find the next instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @return Whether a result was found.
*/
public findNext(term: string): boolean {
if (!term || term.length === 0) {
return false;
}
let result: ISearchResult;
let startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionEnd) {
// Start from the selection end if there is a selection
startRow = this._terminal._core.selectionManager.selectionEnd[1];
}
// Search from ydisp + 1 to end
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y);
if (result) {
break;
}
}
// Search from the top to the current ydisp
if (!result) {
for (let y = 0; y < startRow; y++) {
result = this._findInLine(term, y);
if (result) {
break;
}
}
}
// Set selection and scroll if a result was found
return this._selectResult(result);
}
/**
* Find the previous instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
* @param term Tne search term.
* @return Whether a result was found.
*/
public findPrevious(term: string): boolean {
if (!term || term.length === 0) {
return false;
}
let result: ISearchResult;
let startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionStart) {
// Start from the selection end if there is a selection
startRow = this._terminal._core.selectionManager.selectionStart[1];
}
// Search from ydisp + 1 to end
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y);
if (result) {
break;
}
}
// Search from the top to the current ydisp
if (!result) {
for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
result = this._findInLine(term, y);
if (result) {
break;
}
}
}
// Set selection and scroll if a result was found
return this._selectResult(result);
}
/**
* Searches a line for a search term.
* @param term Tne search term.
* @param y The line to search.
* @return The search result if it was found.
*/
private _findInLine(term: string, y: number): ISearchResult {
const lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase();
const lowerTerm = term.toLowerCase();
let searchIndex = lowerStringLine.indexOf(lowerTerm);
if (searchIndex >= 0) {
const line = this._terminal._core.buffer.lines.get(y);
for (let i = 0; i < searchIndex; i++) {
const charData = line.get(i);
// Adjust the searchIndex to normalize emoji into single chars
const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
if (char.length > 1) {
searchIndex -= char.length - 1;
}
// Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK)
const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
if (charWidth === 0) {
searchIndex++;
}
}
return {
term,
col: searchIndex,
row: y
};
}
}
/**
* Selects and scrolls to a result.
* @param result The result to select.
* @return Whethera result was selected.
*/
private _selectResult(result: ISearchResult): boolean {
if (!result) {
return false;
}
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);
return true;
}
}