From 8b1cb9643bbb71eaad65d7eebcbce7bc41c068d0 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 16 Jan 2017 10:35:46 +0100 Subject: [PATCH] Emit dir attribute on rendered spans (fixes Microsoft/monaco-editor#280) --- src/vs/editor/browser/standalone/colorizer.ts | 7 +++- .../browser/viewParts/lines/viewLine.ts | 1 + .../editor/browser/widget/codeEditorWidget.ts | 2 +- .../editor/browser/widget/diffEditorWidget.ts | 1 + .../common/viewLayout/viewLineRenderer.ts | 18 ++++++++- .../common/viewLayout/viewLineParts.test.ts | 3 ++ .../viewLayout/viewLineRenderer.test.ts | 39 +++++++++++++++++++ 7 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/standalone/colorizer.ts b/src/vs/editor/browser/standalone/colorizer.ts index cc370e16139..f0c82ae7c1e 100644 --- a/src/vs/editor/browser/standalone/colorizer.ts +++ b/src/vs/editor/browser/standalone/colorizer.ts @@ -94,9 +94,10 @@ export class Colorizer { }); } - public static colorizeLine(line: string, tokens: ViewLineToken[], tabSize: number = 4): string { + public static colorizeLine(line: string, mightContainRTL: boolean, tokens: ViewLineToken[], tabSize: number = 4): string { let renderResult = renderViewLine(new RenderLineInput( line, + mightContainRTL, 0, tokens, [], @@ -113,7 +114,7 @@ export class Colorizer { let content = model.getLineContent(lineNumber); let tokens = model.getLineTokens(lineNumber, false); let inflatedTokens = tokens.inflate(); - return this.colorizeLine(content, inflatedTokens, tabSize); + return this.colorizeLine(content, model.mightContainRTL(), inflatedTokens, tabSize); } } @@ -129,6 +130,7 @@ function _fakeColorize(lines: string[], tabSize: number): string { let renderResult = renderViewLine(new RenderLineInput( line, + false, 0, [new ViewLineToken(line.length, '')], [], @@ -157,6 +159,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: let lineTokens = new LineTokens(colorMap, tokenizeResult.tokens, line); let renderResult = renderViewLine(new RenderLineInput( line, + true/* check for RTL */, 0, lineTokens.inflate(), [], diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 14bebc00d2a..01230e61f79 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -102,6 +102,7 @@ export class ViewLine implements IVisibleLineData { let renderLineInput = new RenderLineInput( model.getLineContent(lineNumber), + model.mightContainRTL(), model.getLineMinColumn(lineNumber) - 1, model.getLineTokens(lineNumber), actualInlineDecorations, diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index e02011125a7..6bd46d9b65e 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -134,7 +134,7 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito let tokens = model.getLineTokens(lineNumber, false); let inflatedTokens = tokens.inflate(); let tabSize = model.getOptions().tabSize; - return Colorizer.colorizeLine(content, inflatedTokens, tabSize); + return Colorizer.colorizeLine(content, model.mightContainRTL(), inflatedTokens, tabSize); } public getView(): editorBrowser.IView { return this._view; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 8a423b577ab..56f3ed899bc 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1889,6 +1889,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { let r = renderViewLine(new RenderLineInput( lineContent, + originalModel.mightContainRTL(), 0, [new ViewLineToken(lineContent.length, '')], actualDecorations, diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 6f27d84803f..19aaf73bc45 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -18,6 +18,7 @@ export const enum RenderWhitespace { export class RenderLineInput { public readonly lineContent: string; + public readonly mightContainRTL: boolean; public readonly fauxIndentLength: number; public readonly lineTokens: ViewLineToken[]; public readonly lineDecorations: Decoration[]; @@ -29,6 +30,7 @@ export class RenderLineInput { constructor( lineContent: string, + mightContainRTL: boolean, fauxIndentLength: number, lineTokens: ViewLineToken[], lineDecorations: Decoration[], @@ -39,6 +41,7 @@ export class RenderLineInput { renderControlCharacters: boolean, ) { this.lineContent = lineContent; + this.mightContainRTL = mightContainRTL; this.fauxIndentLength = fauxIndentLength; this.lineTokens = lineTokens; this.lineDecorations = lineDecorations; @@ -58,6 +61,7 @@ export class RenderLineInput { public equals(other: RenderLineInput): boolean { return ( this.lineContent === other.lineContent + && this.mightContainRTL === other.mightContainRTL && this.fauxIndentLength === other.fauxIndentLength && this.tabSize === other.tabSize && this.spaceWidth === other.spaceWidth @@ -213,6 +217,7 @@ class ResolvedRenderLineInput { public readonly tokens: ViewLineToken[], public readonly lineDecorations: Decoration[], public readonly tabSize: number, + public readonly emitLTRDir: boolean, public readonly spaceWidth: number, public readonly renderWhitespace: RenderWhitespace, public readonly renderControlCharacters: boolean, @@ -243,6 +248,11 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations); } + let emitLTRDir = false; + if (input.mightContainRTL) { + emitLTRDir = strings.containsRTL(lineContent); + } + return new ResolvedRenderLineInput( lineContent, len, @@ -250,6 +260,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput tokens, input.lineDecorations, input.tabSize, + emitLTRDir, input.spaceWidth, input.renderWhitespace, input.renderControlCharacters @@ -424,6 +435,7 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { const isOverflowing = input.isOverflowing; const tokens = input.tokens; const tabSize = input.tabSize; + const emitLTRDir = input.emitLTRDir; const spaceWidth = input.spaceWidth; const renderWhitespace = input.renderWhitespace; const renderControlCharacters = input.renderControlCharacters; @@ -534,7 +546,11 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { charOffsetInPart++; } - out += `${partContent}`; + if (emitLTRDir) { + out += `${partContent}`; + } else { + out += `${partContent}`; + } } } diff --git a/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts b/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts index 8057407ddeb..eb4cd882114 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineParts.test.ts @@ -58,6 +58,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { function testCreateLineParts(lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'all', expected: string): void { let actual = renderViewLine(new RenderLineInput( lineContent, + false, fauxIndentLength, tokens, [], @@ -267,6 +268,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { test('createLineParts can handle unsorted inline decorations', () => { let actual = renderViewLine(new RenderLineInput( 'Hello world', + false, 0, [new ViewLineToken(11, '')], [ @@ -371,6 +373,7 @@ suite('Editor ViewLayout - ViewLineParts', () => { function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { let renderLineOutput = renderViewLine(new RenderLineInput( lineContent, + false, 0, parts, [], diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index fa0c12db006..d238e9c4b50 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -18,6 +18,7 @@ suite('viewLineRenderer.renderLine', () => { function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[][]): void { let _actual = renderViewLine(new RenderLineInput( lineContent, + false, 0, [new ViewLineToken(lineContent.length, '')], [], @@ -62,6 +63,7 @@ suite('viewLineRenderer.renderLine', () => { function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, expectedCharOffsetInPart: number[][]): void { let _actual = renderViewLine(new RenderLineInput( lineContent, + false, 0, parts, [], @@ -95,6 +97,7 @@ suite('viewLineRenderer.renderLine', () => { test('overflow', () => { let _actual = renderViewLine(new RenderLineInput( 'Hello world!', + false, 0, [ createPart(1, '0'), @@ -190,6 +193,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( lineText, + false, 0, lineParts, [], @@ -246,6 +250,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( lineText, + false, 0, lineParts, [], @@ -302,6 +307,7 @@ suite('viewLineRenderer.renderLine', () => { let _actual = renderViewLine(new RenderLineInput( lineText, + false, 0, lineParts, [], @@ -316,6 +322,39 @@ suite('viewLineRenderer.renderLine', () => { assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr); }); + test('issue Microsoft/monaco-editor#280: Improved source code rendering for RTL languages', () => { + let lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; + + let lineParts = [ + createPart(3, 'mtk6'), + createPart(13, 'mtk1'), + createPart(66, 'mtk20'), + createPart(67, 'mtk1'), + ]; + + let expectedOutput = [ + 'var', + ' קודמות = ', + '"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם"', + ';' + ].join(''); + + let _actual = renderViewLine(new RenderLineInput( + lineText, + true, + 0, + lineParts, + [], + 4, + 10, + -1, + 'none', + false + )); + + assert.equal(_actual.output, '' + expectedOutput + ''); + }); + function assertCharacterMapping(actual: CharacterMapping, expected: number[][]): void { let charOffset = 0; for (let partIndex = 0; partIndex < expected.length; partIndex++) {