Skip to content

Commit a71065a

Browse files
committed
Fixes microsoft#34497: Count unicode points for the status bar
1 parent e1a2544 commit a71065a

11 files changed

Lines changed: 155 additions & 19 deletions

File tree

src/vs/editor/browser/widget/codeEditorWidget.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
499499
return CursorColumns.visibleColumnFromColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize) + 1;
500500
}
501501

502+
public getStatusbarColumn(rawPosition: IPosition): number {
503+
if (!this._modelData) {
504+
return rawPosition.column;
505+
}
506+
507+
const position = this._modelData.model.validatePosition(rawPosition);
508+
const tabSize = this._modelData.model.getOptions().tabSize;
509+
510+
return CursorColumns.toStatusbarColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize);
511+
}
512+
502513
public getPosition(): Position | null {
503514
if (!this._modelData) {
504515
return null;

src/vs/editor/browser/widget/diffEditorWidget.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
738738
return this.modifiedEditor.getVisibleColumnFromPosition(position);
739739
}
740740

741+
public getStatusbarColumn(position: IPosition): number {
742+
return this.modifiedEditor.getStatusbarColumn(position);
743+
}
744+
741745
public getPosition(): Position | null {
742746
return this.modifiedEditor.getPosition();
743747
}

src/vs/editor/common/controller/cursorCommon.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,29 @@ export class CursorColumns {
549549
return result;
550550
}
551551

552+
public static toStatusbarColumn(lineContent: string, column: number, tabSize: number): number {
553+
let endOffset = lineContent.length;
554+
if (endOffset > column - 1) {
555+
endOffset = column - 1;
556+
}
557+
558+
let result = 0;
559+
for (let i = 0; i < endOffset; i++) {
560+
let charCode = lineContent.charCodeAt(i);
561+
if (charCode === CharCode.Tab) {
562+
result = this.nextRenderTabStop(result, tabSize);
563+
} else {
564+
if (strings.isHighSurrogate(charCode)) {
565+
result = result + 1;
566+
i = i + 1;
567+
} else {
568+
result = result + 1;
569+
}
570+
}
571+
}
572+
return result + 1;
573+
}
574+
552575
public static visibleColumnFromColumn2(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): number {
553576
return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize);
554577
}

src/vs/editor/common/core/selection.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,6 @@ export class Selection extends Range {
7373
this.positionColumn = positionColumn;
7474
}
7575

76-
/**
77-
* Clone this selection.
78-
*/
79-
public clone(): Selection {
80-
return new Selection(this.selectionStartLineNumber, this.selectionStartColumn, this.positionLineNumber, this.positionColumn);
81-
}
82-
8376
/**
8477
* Transform to a human-readable representation.
8578
*/

src/vs/editor/common/editorCommon.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ export interface IEditor {
315315
*/
316316
getVisibleColumnFromPosition(position: IPosition): number;
317317

318+
/**
319+
* Given a position, returns a column number that takes tab-widths into account.
320+
* @internal
321+
*/
322+
getStatusbarColumn(position: IPosition): number;
323+
318324
/**
319325
* Returns the primary position of the cursor.
320326
*/

src/vs/editor/common/model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,12 @@ export interface ITextModel {
608608
*/
609609
getValueLengthInRange(range: IRange): number;
610610

611+
/**
612+
* Get the character count of text in a certain range.
613+
* @param range The range describing what text length to get.
614+
*/
615+
getCharacterCountInRange(range: IRange): number;
616+
611617
/**
612618
* Splits characters in two buckets. First bucket (A) is of characters that
613619
* sit in lines with length < `LONG_LINE_BOUNDARY`. Second bucket (B) is of
@@ -1203,6 +1209,7 @@ export interface ITextBuffer {
12031209
getValueInRange(range: Range, eol: EndOfLinePreference): string;
12041210
createSnapshot(preserveBOM: boolean): ITextSnapshot;
12051211
getValueLengthInRange(range: Range, eol: EndOfLinePreference): number;
1212+
getCharacterCountInRange(range: Range, eol: EndOfLinePreference): number;
12061213
getLength(): number;
12071214
getLineCount(): number;
12081215
getLinesContent(): string[];

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,37 @@ export class PieceTreeTextBuffer implements ITextBuffer {
106106
return endOffset - startOffset;
107107
}
108108

109+
public getCharacterCountInRange(range: Range, eol: EndOfLinePreference = EndOfLinePreference.TextDefined): number {
110+
if (this._mightContainNonBasicASCII) {
111+
// we must count by iterating
112+
113+
let result = 0;
114+
115+
const fromLineNumber = range.startLineNumber;
116+
const toLineNumber = range.endLineNumber;
117+
for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) {
118+
const lineContent = this.getLineContent(lineNumber);
119+
const fromOffset = (lineNumber === fromLineNumber ? range.startColumn - 1 : 0);
120+
const toOffset = (lineNumber === toLineNumber ? range.endColumn - 1 : lineContent.length);
121+
122+
for (let offset = fromOffset; offset < toOffset; offset++) {
123+
if (strings.isHighSurrogate(lineContent.charCodeAt(offset))) {
124+
result = result + 1;
125+
offset = offset + 1;
126+
} else {
127+
result = result + 1;
128+
}
129+
}
130+
}
131+
132+
result += this._getEndOfLine(eol).length * (toLineNumber - fromLineNumber);
133+
134+
return result;
135+
}
136+
137+
return this.getValueLengthInRange(range, eol);
138+
}
139+
109140
public getLength(): number {
110141
return this._pieceTree.getLength();
111142
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,11 @@ export class TextModel extends Disposable implements model.ITextModel {
726726
return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol);
727727
}
728728

729+
public getCharacterCountInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number {
730+
this._assertNotDisposed();
731+
return this._buffer.getCharacterCountInRange(this.validateRange(rawRange), eol);
732+
}
733+
729734
public getLineCount(): number {
730735
this._assertNotDisposed();
731736
return this._buffer.getLineCount();

src/vs/editor/test/common/controller/cursorMoveHelper.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,60 @@ suite('CursorMove', () => {
173173
testColumnFromVisibleColumn('📚az', 4, 3, 4);
174174
testColumnFromVisibleColumn('📚az', 4, 4, 5);
175175
});
176-
});
176+
177+
test('toStatusbarColumn', () => {
178+
179+
function t(text: string, tabSize: number, column: number, expected: number): void {
180+
assert.equal(CursorColumns.toStatusbarColumn(text, column, tabSize), expected, `<<t('${text}', ${tabSize}, ${column}, ${expected})>>`);
181+
}
182+
183+
t(' spaces', 4, 1, 1);
184+
t(' spaces', 4, 2, 2);
185+
t(' spaces', 4, 3, 3);
186+
t(' spaces', 4, 4, 4);
187+
t(' spaces', 4, 5, 5);
188+
t(' spaces', 4, 6, 6);
189+
t(' spaces', 4, 7, 7);
190+
t(' spaces', 4, 8, 8);
191+
t(' spaces', 4, 9, 9);
192+
t(' spaces', 4, 10, 10);
193+
t(' spaces', 4, 11, 11);
194+
195+
t('\ttab', 4, 1, 1);
196+
t('\ttab', 4, 2, 5);
197+
t('\ttab', 4, 3, 6);
198+
t('\ttab', 4, 4, 7);
199+
t('\ttab', 4, 5, 8);
200+
201+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 1, 1);
202+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 2, 2);
203+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 3, 2);
204+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 4, 3);
205+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 5, 3);
206+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 6, 4);
207+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 7, 4);
208+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 8, 5);
209+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 9, 5);
210+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 10, 6);
211+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 11, 6);
212+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 12, 7);
213+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 13, 7);
214+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 14, 8);
215+
t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 15, 8);
216+
217+
t('🎈🎈🎈🎈', 4, 1, 1);
218+
t('🎈🎈🎈🎈', 4, 2, 2);
219+
t('🎈🎈🎈🎈', 4, 3, 2);
220+
t('🎈🎈🎈🎈', 4, 4, 3);
221+
t('🎈🎈🎈🎈', 4, 5, 3);
222+
t('🎈🎈🎈🎈', 4, 6, 4);
223+
t('🎈🎈🎈🎈', 4, 7, 4);
224+
t('🎈🎈🎈🎈', 4, 8, 5);
225+
t('🎈🎈🎈🎈', 4, 9, 5);
226+
227+
t('何何何何', 4, 1, 1);
228+
t('何何何何', 4, 2, 2);
229+
t('何何何何', 4, 3, 3);
230+
t('何何何何', 4, 4, 4);
231+
});
232+
});

src/vs/monaco.d.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -726,10 +726,6 @@ declare namespace monaco {
726726
*/
727727
readonly positionColumn: number;
728728
constructor(selectionStartLineNumber: number, selectionStartColumn: number, positionLineNumber: number, positionColumn: number);
729-
/**
730-
* Clone this selection.
731-
*/
732-
clone(): Selection;
733729
/**
734730
* Transform to a human-readable representation.
735731
*/
@@ -1555,6 +1551,11 @@ declare namespace monaco.editor {
15551551
* @return The text length.
15561552
*/
15571553
getValueLengthInRange(range: IRange): number;
1554+
/**
1555+
* Get the character count of text in a certain range.
1556+
* @param range The range describing what text length to get.
1557+
*/
1558+
getCharacterCountInRange(range: IRange): number;
15581559
/**
15591560
* Get the number of lines in the model.
15601561
*/

0 commit comments

Comments
 (0)