Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { findLastIdxMonotonous, findLastMonotonous, findFirstMonotonous } from '../../../../base/common/arraysFind.js';
7
import { CharCode } from '../../../../base/common/charCode.js';
8
import { OffsetRange } from '../../core/ranges/offsetRange.js';
9
import { Position } from '../../core/position.js';
10
import { Range } from '../../core/range.js';
11
import { ISequence } from './algorithms/diffAlgorithm.js';
12
import { isSpace } from './utils.js';
13
14
export class LinesSliceCharSequence implements ISequence {
15
private readonly elements: number[] = [];
16
private readonly firstElementOffsetByLineIdx: number[] = [];
17
private readonly lineStartOffsets: number[] = [];
18
private readonly trimmedWsLengthsByLineIdx: number[] = [];
19
20
constructor(public readonly lines: string[], private readonly range: Range, public readonly considerWhitespaceChanges: boolean) {
21
this.firstElementOffsetByLineIdx.push(0);
22
for (let lineNumber = this.range.startLineNumber; lineNumber <= this.range.endLineNumber; lineNumber++) {
23
let line = lines[lineNumber - 1];
24
let lineStartOffset = 0;
25
if (lineNumber === this.range.startLineNumber && this.range.startColumn > 1) {
26
lineStartOffset = this.range.startColumn - 1;
27
line = line.substring(lineStartOffset);
28
}
29
this.lineStartOffsets.push(lineStartOffset);
30
31
let trimmedWsLength = 0;
32
if (!considerWhitespaceChanges) {
33
const trimmedStartLine = line.trimStart();
34
trimmedWsLength = line.length - trimmedStartLine.length;
35
line = trimmedStartLine.trimEnd();
36
}
37
this.trimmedWsLengthsByLineIdx.push(trimmedWsLength);
38
39
const lineLength = lineNumber === this.range.endLineNumber ? Math.min(this.range.endColumn - 1 - lineStartOffset - trimmedWsLength, line.length) : line.length;
40
for (let i = 0; i < lineLength; i++) {
41
this.elements.push(line.charCodeAt(i));
42
}
43
44
if (lineNumber < this.range.endLineNumber) {
45
this.elements.push('\n'.charCodeAt(0));
46
this.firstElementOffsetByLineIdx.push(this.elements.length);
47
}
48
}
49
}
50
51
toString() {
52
return `Slice: "${this.text}"`;
53
}
54
55
get text(): string {
56
return this.getText(new OffsetRange(0, this.length));
57
}
58
59
getText(range: OffsetRange): string {
60
return this.elements.slice(range.start, range.endExclusive).map(e => String.fromCharCode(e)).join('');
61
}
62
63
getElement(offset: number): number {
64
return this.elements[offset];
65
}
66
67
get length(): number {
68
return this.elements.length;
69
}
70
71
public getBoundaryScore(length: number): number {
72
// a b c , d e f
73
// 11 0 0 12 15 6 13 0 0 11
74
75
const prevCategory = getCategory(length > 0 ? this.elements[length - 1] : -1);
76
const nextCategory = getCategory(length < this.elements.length ? this.elements[length] : -1);
77
78
if (prevCategory === CharBoundaryCategory.LineBreakCR && nextCategory === CharBoundaryCategory.LineBreakLF) {
79
// don't break between \r and \n
80
return 0;
81
}
82
if (prevCategory === CharBoundaryCategory.LineBreakLF) {
83
// prefer the linebreak before the change
84
return 150;
85
}
86
87
let score = 0;
88
if (prevCategory !== nextCategory) {
89
score += 10;
90
if (prevCategory === CharBoundaryCategory.WordLower && nextCategory === CharBoundaryCategory.WordUpper) {
91
score += 1;
92
}
93
}
94
95
score += getCategoryBoundaryScore(prevCategory);
96
score += getCategoryBoundaryScore(nextCategory);
97
98
return score;
99
}
100
101
public translateOffset(offset: number, preference: 'left' | 'right' = 'right'): Position {
102
// find smallest i, so that lineBreakOffsets[i] <= offset using binary search
103
const i = findLastIdxMonotonous(this.firstElementOffsetByLineIdx, (value) => value <= offset);
104
const lineOffset = offset - this.firstElementOffsetByLineIdx[i];
105
return new Position(
106
this.range.startLineNumber + i,
107
1 + this.lineStartOffsets[i] + lineOffset + ((lineOffset === 0 && preference === 'left') ? 0 : this.trimmedWsLengthsByLineIdx[i])
108
);
109
}
110
111
public translateRange(range: OffsetRange): Range {
112
const pos1 = this.translateOffset(range.start, 'right');
113
const pos2 = this.translateOffset(range.endExclusive, 'left');
114
if (pos2.isBefore(pos1)) {
115
return Range.fromPositions(pos2, pos2);
116
}
117
return Range.fromPositions(pos1, pos2);
118
}
119
120
/**
121
* Finds the word that contains the character at the given offset
122
*/
123
public findWordContaining(offset: number): OffsetRange | undefined {
124
if (offset < 0 || offset >= this.elements.length) {
125
return undefined;
126
}
127
128
if (!isWordChar(this.elements[offset])) {
129
return undefined;
130
}
131
132
// find start
133
let start = offset;
134
while (start > 0 && isWordChar(this.elements[start - 1])) {
135
start--;
136
}
137
138
// find end
139
let end = offset;
140
while (end < this.elements.length && isWordChar(this.elements[end])) {
141
end++;
142
}
143
144
return new OffsetRange(start, end);
145
}
146
147
/** fooBar has the two sub-words foo and bar */
148
public findSubWordContaining(offset: number): OffsetRange | undefined {
149
if (offset < 0 || offset >= this.elements.length) {
150
return undefined;
151
}
152
153
if (!isWordChar(this.elements[offset])) {
154
return undefined;
155
}
156
157
// find start
158
let start = offset;
159
while (start > 0 && isWordChar(this.elements[start - 1]) && !isUpperCase(this.elements[start])) {
160
start--;
161
}
162
163
// find end
164
let end = offset;
165
while (end < this.elements.length && isWordChar(this.elements[end]) && !isUpperCase(this.elements[end])) {
166
end++;
167
}
168
169
return new OffsetRange(start, end);
170
}
171
172
public countLinesIn(range: OffsetRange): number {
173
return this.translateOffset(range.endExclusive).lineNumber - this.translateOffset(range.start).lineNumber;
174
}
175
176
public isStronglyEqual(offset1: number, offset2: number): boolean {
177
return this.elements[offset1] === this.elements[offset2];
178
}
179
180
public extendToFullLines(range: OffsetRange): OffsetRange {
181
const start = findLastMonotonous(this.firstElementOffsetByLineIdx, x => x <= range.start) ?? 0;
182
const end = findFirstMonotonous(this.firstElementOffsetByLineIdx, x => range.endExclusive <= x) ?? this.elements.length;
183
return new OffsetRange(start, end);
184
}
185
}
186
187
function isWordChar(charCode: number): boolean {
188
return charCode >= CharCode.a && charCode <= CharCode.z
189
|| charCode >= CharCode.A && charCode <= CharCode.Z
190
|| charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9;
191
}
192
193
function isUpperCase(charCode: number): boolean {
194
return charCode >= CharCode.A && charCode <= CharCode.Z;
195
}
196
197
const enum CharBoundaryCategory {
198
WordLower,
199
WordUpper,
200
WordNumber,
201
End,
202
Other,
203
Separator,
204
Space,
205
LineBreakCR,
206
LineBreakLF,
207
}
208
209
const score: Record<CharBoundaryCategory, number> = {
210
[CharBoundaryCategory.WordLower]: 0,
211
[CharBoundaryCategory.WordUpper]: 0,
212
[CharBoundaryCategory.WordNumber]: 0,
213
[CharBoundaryCategory.End]: 10,
214
[CharBoundaryCategory.Other]: 2,
215
[CharBoundaryCategory.Separator]: 30,
216
[CharBoundaryCategory.Space]: 3,
217
[CharBoundaryCategory.LineBreakCR]: 10,
218
[CharBoundaryCategory.LineBreakLF]: 10,
219
};
220
221
function getCategoryBoundaryScore(category: CharBoundaryCategory): number {
222
return score[category];
223
}
224
225
function getCategory(charCode: number): CharBoundaryCategory {
226
if (charCode === CharCode.LineFeed) {
227
return CharBoundaryCategory.LineBreakLF;
228
} else if (charCode === CharCode.CarriageReturn) {
229
return CharBoundaryCategory.LineBreakCR;
230
} else if (isSpace(charCode)) {
231
return CharBoundaryCategory.Space;
232
} else if (charCode >= CharCode.a && charCode <= CharCode.z) {
233
return CharBoundaryCategory.WordLower;
234
} else if (charCode >= CharCode.A && charCode <= CharCode.Z) {
235
return CharBoundaryCategory.WordUpper;
236
} else if (charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9) {
237
return CharBoundaryCategory.WordNumber;
238
} else if (charCode === -1) {
239
return CharBoundaryCategory.End;
240
} else if (charCode === CharCode.Comma || charCode === CharCode.Semicolon) {
241
return CharBoundaryCategory.Separator;
242
} else {
243
return CharBoundaryCategory.Other;
244
}
245
}
246
247
248