Skip to content

Commit 344f5c0

Browse files
committed
Detect and remove unusual line terminators (fixes microsoft#96142)
1 parent a2a5971 commit 344f5c0

10 files changed

Lines changed: 270 additions & 96 deletions

File tree

src/vs/base/common/strings.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,14 @@ export function isBasicASCII(str: string): boolean {
728728
return IS_BASIC_ASCII.test(str);
729729
}
730730

731+
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029\u0085]/; // LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL)
732+
/**
733+
* Returns true if `str` contains unusual line terminators, like LS, PS or NEL
734+
*/
735+
export function containsUnusualLineTerminators(str: string): boolean {
736+
return UNUSUAL_LINE_TERMINATORS.test(str);
737+
}
738+
731739
export function containsFullWidthCharacter(str: string): boolean {
732740
for (let i = 0, len = str.length; i < len; i++) {
733741
if (isFullWidthCharacter(str.charCodeAt(i))) {

src/vs/editor/common/config/editorOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ export interface IEditorOptions {
9393
* Defaults to true.
9494
*/
9595
renderFinalNewline?: boolean;
96+
/**
97+
* Remove unusual line terminators like LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL).
98+
* Defaults to true.
99+
*/
100+
removeUnusualLineTerminators?: boolean;
96101
/**
97102
* Should the corresponding line be selected when clicking on the line number?
98103
* Defaults to true.
@@ -3547,6 +3552,7 @@ export const enum EditorOption {
35473552
quickSuggestions,
35483553
quickSuggestionsDelay,
35493554
readOnly,
3555+
removeUnusualLineTerminators,
35503556
renameOnType,
35513557
renderControlCharacters,
35523558
renderIndentGuides,
@@ -3965,6 +3971,10 @@ export const EditorOptions = {
39653971
readOnly: register(new EditorBooleanOption(
39663972
EditorOption.readOnly, 'readOnly', false,
39673973
)),
3974+
removeUnusualLineTerminators: register(new EditorBooleanOption(
3975+
EditorOption.removeUnusualLineTerminators, 'removeUnusualLineTerminators', true,
3976+
{ description: nls.localize('removeUnusualLineTerminators', "Remove unusual line terminators that might cause problems.") }
3977+
)),
39683978
renameOnType: register(new EditorBooleanOption(
39693979
EditorOption.renameOnType, 'renameOnType', false,
39703980
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") }

src/vs/editor/common/model.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,18 @@ export interface ITextModel {
551551
*/
552552
mightContainRTL(): boolean;
553553

554+
/**
555+
* If true, the text model might contain LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL).
556+
* If false, the text model definitely does not contain these.
557+
* @internal
558+
*/
559+
mightContainUnusualLineTerminators(): boolean;
560+
561+
/**
562+
* @internal
563+
*/
564+
removeUnusualLineTerminators(selections?: Selection[]): void;
565+
554566
/**
555567
* If true, the text model might contain non basic ASCII.
556568
* If false, the text model **contains only** basic ASCII.
@@ -1281,6 +1293,8 @@ export interface IReadonlyTextBuffer {
12811293
onDidChangeContent: Event<void>;
12821294
equals(other: ITextBuffer): boolean;
12831295
mightContainRTL(): boolean;
1296+
mightContainUnusualLineTerminators(): boolean;
1297+
resetMightContainUnusualLineTerminators(): void;
12841298
mightContainNonBasicASCII(): boolean;
12851299
getBOM(): string;
12861300
getEOL(): string;

src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,17 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
3636
private readonly _pieceTree: PieceTreeBase;
3737
private readonly _BOM: string;
3838
private _mightContainRTL: boolean;
39+
private _mightContainUnusualLineTerminators: boolean;
3940
private _mightContainNonBasicASCII: boolean;
4041

4142
private readonly _onDidChangeContent: Emitter<void> = new Emitter<void>();
4243
public readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
4344

44-
constructor(chunks: StringBuffer[], BOM: string, eol: '\r\n' | '\n', containsRTL: boolean, isBasicASCII: boolean, eolNormalized: boolean) {
45+
constructor(chunks: StringBuffer[], BOM: string, eol: '\r\n' | '\n', containsRTL: boolean, containsUnusualLineTerminators: boolean, isBasicASCII: boolean, eolNormalized: boolean) {
4546
this._BOM = BOM;
4647
this._mightContainNonBasicASCII = !isBasicASCII;
4748
this._mightContainRTL = containsRTL;
49+
this._mightContainUnusualLineTerminators = containsUnusualLineTerminators;
4850
this._pieceTree = new PieceTreeBase(chunks, eol, eolNormalized);
4951
}
5052
dispose(): void {
@@ -67,6 +69,12 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
6769
public mightContainRTL(): boolean {
6870
return this._mightContainRTL;
6971
}
72+
public mightContainUnusualLineTerminators(): boolean {
73+
return this._mightContainUnusualLineTerminators;
74+
}
75+
public resetMightContainUnusualLineTerminators(): void {
76+
this._mightContainUnusualLineTerminators = false;
77+
}
7078
public mightContainNonBasicASCII(): boolean {
7179
return this._mightContainNonBasicASCII;
7280
}
@@ -216,6 +224,7 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
216224

217225
public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult {
218226
let mightContainRTL = this._mightContainRTL;
227+
let mightContainUnusualLineTerminators = this._mightContainUnusualLineTerminators;
219228
let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
220229
let canReduceOperations = true;
221230

@@ -226,12 +235,20 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
226235
canReduceOperations = false;
227236
}
228237
let validatedRange = op.range;
229-
if (!mightContainRTL && op.text) {
230-
// check if the new inserted text contains RTL
231-
mightContainRTL = strings.containsRTL(op.text);
232-
}
233-
if (!mightContainNonBasicASCII && op.text) {
234-
mightContainNonBasicASCII = !strings.isBasicASCII(op.text);
238+
if (op.text) {
239+
let textMightContainNonBasicASCII = true;
240+
if (!mightContainNonBasicASCII) {
241+
textMightContainNonBasicASCII = !strings.isBasicASCII(op.text);
242+
mightContainNonBasicASCII = textMightContainNonBasicASCII;
243+
}
244+
if (!mightContainRTL && textMightContainNonBasicASCII) {
245+
// check if the new inserted text contains RTL
246+
mightContainRTL = strings.containsRTL(op.text);
247+
}
248+
if (!mightContainUnusualLineTerminators && textMightContainNonBasicASCII) {
249+
// check if the new inserted text contains unusual line terminators
250+
mightContainUnusualLineTerminators = strings.containsUnusualLineTerminators(op.text);
251+
}
235252
}
236253

237254
let validText = '';
@@ -340,6 +357,7 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
340357

341358

342359
this._mightContainRTL = mightContainRTL;
360+
this._mightContainUnusualLineTerminators = mightContainUnusualLineTerminators;
343361
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
344362

345363
const contentChanges = this._doApplyEdits(operations);

src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory {
1818
private readonly _lf: number,
1919
private readonly _crlf: number,
2020
private readonly _containsRTL: boolean,
21+
private readonly _containsUnusualLineTerminators: boolean,
2122
private readonly _isBasicASCII: boolean,
2223
private readonly _normalizeEOL: boolean
2324
) { }
@@ -53,7 +54,7 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory {
5354
}
5455
}
5556

56-
return new PieceTreeTextBuffer(chunks, this._bom, eol, this._containsRTL, this._isBasicASCII, this._normalizeEOL);
57+
return new PieceTreeTextBuffer(chunks, this._bom, eol, this._containsRTL, this._containsUnusualLineTerminators, this._isBasicASCII, this._normalizeEOL);
5758
}
5859

5960
public getFirstLineText(lengthLimit: number): string {
@@ -73,6 +74,7 @@ export class PieceTreeTextBufferBuilder implements ITextBufferBuilder {
7374
private lf: number;
7475
private crlf: number;
7576
private containsRTL: boolean;
77+
private containsUnusualLineTerminators: boolean;
7678
private isBasicASCII: boolean;
7779

7880
constructor() {
@@ -87,6 +89,7 @@ export class PieceTreeTextBufferBuilder implements ITextBufferBuilder {
8789
this.lf = 0;
8890
this.crlf = 0;
8991
this.containsRTL = false;
92+
this.containsUnusualLineTerminators = false;
9093
this.isBasicASCII = true;
9194
}
9295

@@ -140,9 +143,13 @@ export class PieceTreeTextBufferBuilder implements ITextBufferBuilder {
140143
this.isBasicASCII = lineStarts.isBasicASCII;
141144
}
142145
if (!this.isBasicASCII && !this.containsRTL) {
143-
// No need to check if is basic ASCII
146+
// No need to check if it is basic ASCII
144147
this.containsRTL = strings.containsRTL(chunk);
145148
}
149+
if (!this.isBasicASCII && !this.containsUnusualLineTerminators) {
150+
// No need to check if it is basic ASCII
151+
this.containsUnusualLineTerminators = strings.containsUnusualLineTerminators(chunk);
152+
}
146153
}
147154

148155
public finish(normalizeEOL: boolean = true): PieceTreeTextBufferFactory {
@@ -154,6 +161,7 @@ export class PieceTreeTextBufferBuilder implements ITextBufferBuilder {
154161
this.lf,
155162
this.crlf,
156163
this.containsRTL,
164+
this.containsUnusualLineTerminators,
157165
this.isBasicASCII,
158166
normalizeEOL
159167
);

src/vs/editor/common/model/textModel.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { Color } from 'vs/base/common/color';
3737
import { EditorTheme } from 'vs/editor/common/view/viewContext';
3838
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
3939
import { TextChange } from 'vs/editor/common/model/textChange';
40+
import { Constants } from 'vs/base/common/uint';
4041

4142
function createTextBufferBuilder() {
4243
return new PieceTreeTextBufferBuilder();
@@ -699,6 +700,17 @@ export class TextModel extends Disposable implements model.ITextModel {
699700
return this._buffer.mightContainRTL();
700701
}
701702

703+
public mightContainUnusualLineTerminators(): boolean {
704+
return this._buffer.mightContainUnusualLineTerminators();
705+
}
706+
707+
public removeUnusualLineTerminators(selections: Selection[] | null = null): void {
708+
const matches = this.findMatches(strings.UNUSUAL_LINE_TERMINATORS.source, false, true, false, null, false, Constants.MAX_SAFE_SMALL_INTEGER);
709+
const eol = this.getEOL();
710+
this._buffer.resetMightContainUnusualLineTerminators();
711+
this.pushEditOperations(selections, matches.map(m => ({ range: m.range, text: eol })), () => null);
712+
}
713+
702714
public mightContainNonBasicASCII(): boolean {
703715
return this._buffer.mightContainNonBasicASCII();
704716
}
@@ -1097,7 +1109,7 @@ export class TextModel extends Disposable implements model.ITextModel {
10971109
return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
10981110
}
10991111

1100-
public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
1112+
public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string | null, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
11011113
this._assertNotDisposed();
11021114

11031115
let searchRange: Range;

src/vs/editor/common/standalone/standaloneEnums.ts

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -240,49 +240,50 @@ export enum EditorOption {
240240
quickSuggestions = 70,
241241
quickSuggestionsDelay = 71,
242242
readOnly = 72,
243-
renameOnType = 73,
244-
renderControlCharacters = 74,
245-
renderIndentGuides = 75,
246-
renderFinalNewline = 76,
247-
renderLineHighlight = 77,
248-
renderLineHighlightOnlyWhenFocus = 78,
249-
renderValidationDecorations = 79,
250-
renderWhitespace = 80,
251-
revealHorizontalRightPadding = 81,
252-
roundedSelection = 82,
253-
rulers = 83,
254-
scrollbar = 84,
255-
scrollBeyondLastColumn = 85,
256-
scrollBeyondLastLine = 86,
257-
scrollPredominantAxis = 87,
258-
selectionClipboard = 88,
259-
selectionHighlight = 89,
260-
selectOnLineNumbers = 90,
261-
showFoldingControls = 91,
262-
showUnused = 92,
263-
snippetSuggestions = 93,
264-
smoothScrolling = 94,
265-
stopRenderingLineAfter = 95,
266-
suggest = 96,
267-
suggestFontSize = 97,
268-
suggestLineHeight = 98,
269-
suggestOnTriggerCharacters = 99,
270-
suggestSelection = 100,
271-
tabCompletion = 101,
272-
useTabStops = 102,
273-
wordSeparators = 103,
274-
wordWrap = 104,
275-
wordWrapBreakAfterCharacters = 105,
276-
wordWrapBreakBeforeCharacters = 106,
277-
wordWrapColumn = 107,
278-
wordWrapMinified = 108,
279-
wrappingIndent = 109,
280-
wrappingStrategy = 110,
281-
editorClassName = 111,
282-
pixelRatio = 112,
283-
tabFocusMode = 113,
284-
layoutInfo = 114,
285-
wrappingInfo = 115
243+
removeUnusualLineTerminators = 73,
244+
renameOnType = 74,
245+
renderControlCharacters = 75,
246+
renderIndentGuides = 76,
247+
renderFinalNewline = 77,
248+
renderLineHighlight = 78,
249+
renderLineHighlightOnlyWhenFocus = 79,
250+
renderValidationDecorations = 80,
251+
renderWhitespace = 81,
252+
revealHorizontalRightPadding = 82,
253+
roundedSelection = 83,
254+
rulers = 84,
255+
scrollbar = 85,
256+
scrollBeyondLastColumn = 86,
257+
scrollBeyondLastLine = 87,
258+
scrollPredominantAxis = 88,
259+
selectionClipboard = 89,
260+
selectionHighlight = 90,
261+
selectOnLineNumbers = 91,
262+
showFoldingControls = 92,
263+
showUnused = 93,
264+
snippetSuggestions = 94,
265+
smoothScrolling = 95,
266+
stopRenderingLineAfter = 96,
267+
suggest = 97,
268+
suggestFontSize = 98,
269+
suggestLineHeight = 99,
270+
suggestOnTriggerCharacters = 100,
271+
suggestSelection = 101,
272+
tabCompletion = 102,
273+
useTabStops = 103,
274+
wordSeparators = 104,
275+
wordWrap = 105,
276+
wordWrapBreakAfterCharacters = 106,
277+
wordWrapBreakBeforeCharacters = 107,
278+
wordWrapColumn = 108,
279+
wordWrapMinified = 109,
280+
wrappingIndent = 110,
281+
wrappingStrategy = 111,
282+
editorClassName = 112,
283+
pixelRatio = 113,
284+
tabFocusMode = 114,
285+
layoutInfo = 115,
286+
wrappingInfo = 116
286287
}
287288

288289
/**

0 commit comments

Comments
 (0)