Skip to content

Commit 4cf12e6

Browse files
mulhamfetnaCopilot
andcommitted
Add opt-in RTL auto-detect for editor lines (#246116)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 40db337 commit 4cf12e6

5 files changed

Lines changed: 93 additions & 31 deletions

File tree

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,11 @@ export interface IEditorOptions {
571571
* Defaults to true.
572572
*/
573573
doubleClickSelectsBlock?: boolean;
574+
/**
575+
* Controls whether lines containing RTL text are rendered with RTL direction automatically.
576+
* Defaults to false.
577+
*/
578+
rtlAutoDetect?: boolean;
574579
/**
575580
* Controls if the editor should allow to move selections via drag and drop.
576581
* Defaults to false.
@@ -5931,6 +5936,7 @@ export const enum EditorOption {
59315936
inertialScroll,
59325937
inlayHints,
59335938
wrapOnEscapedLineFeeds,
5939+
rtlAutoDetect,
59345940
// Leave these at the end (because they have dependencies!)
59355941
effectiveCursorStyle,
59365942
editorClassName,
@@ -6851,6 +6857,12 @@ export const EditorOptions = {
68516857
tabFocusMode: register(new EditorBooleanOption(EditorOption.tabFocusMode, 'tabFocusMode', false,
68526858
{ markdownDescription: nls.localize('tabFocusMode', "Controls whether the editor receives tabs or defers them to the workbench for navigation.") }
68536859
)),
6860+
rtlAutoDetect: register(new EditorBooleanOption(
6861+
EditorOption.rtlAutoDetect, 'rtlAutoDetect', false,
6862+
{
6863+
description: nls.localize('rtlAutoDetect', "Controls whether lines containing RTL text are rendered with RTL direction automatically.")
6864+
}
6865+
)),
68546866
layoutInfo: register(new EditorLayoutInfoComputer()),
68556867
wrappingInfo: register(new EditorWrappingInfoComputer()),
68566868
wrappingIndent: register(new WrappingIndentOption()),

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -335,19 +335,20 @@ export enum EditorOption {
335335
inertialScroll = 158,
336336
inlayHints = 159,
337337
wrapOnEscapedLineFeeds = 160,
338-
effectiveCursorStyle = 161,
339-
editorClassName = 162,
340-
pixelRatio = 163,
341-
tabFocusMode = 164,
342-
layoutInfo = 165,
343-
wrappingInfo = 166,
344-
defaultColorDecorators = 167,
345-
colorDecoratorsActivatedOn = 168,
346-
inlineCompletionsAccessibilityVerbose = 169,
347-
effectiveEditContext = 170,
348-
scrollOnMiddleClick = 171,
349-
effectiveAllowVariableFonts = 172,
350-
doubleClickSelectsBlock = 173
338+
rtlAutoDetect = 161,
339+
effectiveCursorStyle = 162,
340+
editorClassName = 163,
341+
pixelRatio = 164,
342+
tabFocusMode = 165,
343+
layoutInfo = 166,
344+
wrappingInfo = 167,
345+
defaultColorDecorators = 168,
346+
colorDecoratorsActivatedOn = 169,
347+
inlineCompletionsAccessibilityVerbose = 170,
348+
effectiveEditContext = 171,
349+
scrollOnMiddleClick = 172,
350+
effectiveAllowVariableFonts = 173,
351+
doubleClickSelectsBlock = 174
351352
}
352353

353354
/**

src/vs/editor/common/viewModel/viewModelImpl.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ export class ViewModel extends Disposable implements IViewModel {
853853
return this._lines.getInjectedTextAt(viewPosition);
854854
}
855855

856-
private _getTextDirection(lineNumber: number, decorations: ViewModelDecoration[]): TextDirection {
856+
private _getTextDirection(lineNumber: number, lineContent: string, decorations: ViewModelDecoration[]): TextDirection {
857857
let rtlCount = 0;
858858

859859
for (const decoration of decorations) {
@@ -869,12 +869,22 @@ export class ViewModel extends Disposable implements IViewModel {
869869
}
870870
}
871871

872-
return rtlCount > 0 ? TextDirection.RTL : TextDirection.LTR;
872+
if (rtlCount > 0) {
873+
return TextDirection.RTL;
874+
}
875+
if (rtlCount < 0) {
876+
return TextDirection.LTR;
877+
}
878+
if (this._configuration.options.get(EditorOption.rtlAutoDetect) && strings.containsRTL(lineContent)) {
879+
return TextDirection.RTL;
880+
}
881+
882+
return TextDirection.LTR;
873883
}
874884

875885
public getTextDirection(lineNumber: number): TextDirection {
876886
const decorationsCollection = this._decorations.getDecorationsOnLine(lineNumber);
877-
return this._getTextDirection(lineNumber, decorationsCollection.decorations);
887+
return this._getTextDirection(lineNumber, this.getLineContent(lineNumber), decorationsCollection.decorations);
878888
}
879889

880890
public getViewportViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData {
@@ -914,7 +924,7 @@ export class ViewModel extends Disposable implements IViewModel {
914924
inlineDecorations,
915925
tabSize,
916926
lineData.startVisibleColumn,
917-
this._getTextDirection(lineNumber, decorations),
927+
this._getTextDirection(lineNumber, lineData.content, decorations),
918928
hasVariableFonts
919929
);
920930
}

src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import assert from 'assert';
77
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
88
import { Position } from '../../../common/core/position.js';
99
import { Range } from '../../../common/core/range.js';
10-
import { EndOfLineSequence, PositionAffinity } from '../../../common/model.js';
10+
import { EndOfLineSequence, PositionAffinity, TextDirection } from '../../../common/model.js';
1111
import { ViewEventHandler } from '../../../common/viewEventHandler.js';
1212
import { ViewEvent } from '../../../common/viewEvents.js';
1313
import { testViewModel } from './testViewModel.js';
@@ -149,6 +149,38 @@ suite('ViewModel', () => {
149149
});
150150
});
151151

152+
test('rtlAutoDetect renders Arabic text as RTL when enabled', () => {
153+
testViewModel(['مرحبا، Hello world!'], { rtlAutoDetect: true }, (viewModel) => {
154+
assert.strictEqual(viewModel.getTextDirection(1), TextDirection.RTL);
155+
});
156+
});
157+
158+
test('rtlAutoDetect keeps Arabic text as LTR when disabled', () => {
159+
testViewModel(['مرحبا، Hello world!'], { rtlAutoDetect: false }, (viewModel) => {
160+
assert.strictEqual(viewModel.getTextDirection(1), TextDirection.LTR);
161+
});
162+
});
163+
164+
test('rtlAutoDetect still allows forcing LTR direction via decorations', () => {
165+
testViewModel(['مرحبا، Hello world!'], { rtlAutoDetect: true }, (viewModel, model) => {
166+
model.deltaDecorations([], [{
167+
range: new Range(1, 1, 1, 1),
168+
options: {
169+
description: 'text-direction-ltr',
170+
textDirection: TextDirection.LTR,
171+
}
172+
}]);
173+
174+
assert.strictEqual(viewModel.getTextDirection(1), TextDirection.LTR);
175+
});
176+
});
177+
178+
test('rtlAutoDetect renders ASCII-only lines as LTR', () => {
179+
testViewModel(['Hello world!'], { rtlAutoDetect: true }, (viewModel) => {
180+
assert.strictEqual(viewModel.getTextDirection(1), TextDirection.LTR);
181+
});
182+
});
183+
152184
function assertGetPlainTextToCopy(text: string[], ranges: Range[], emptySelectionClipboard: boolean, expected: string | string[]): void {
153185
testViewModel(text, {}, (viewModel, model) => {
154186
const actual = viewModel.getPlainTextToCopy(ranges, emptySelectionClipboard, false);

src/vs/monaco.d.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3726,6 +3726,11 @@ declare namespace monaco.editor {
37263726
* Defaults to true.
37273727
*/
37283728
doubleClickSelectsBlock?: boolean;
3729+
/**
3730+
* Controls whether lines containing RTL text are rendered with RTL direction automatically.
3731+
* Defaults to false.
3732+
*/
3733+
rtlAutoDetect?: boolean;
37293734
/**
37303735
* Controls if the editor should allow to move selections via drag and drop.
37313736
* Defaults to false.
@@ -5245,19 +5250,20 @@ declare namespace monaco.editor {
52455250
inertialScroll = 158,
52465251
inlayHints = 159,
52475252
wrapOnEscapedLineFeeds = 160,
5248-
effectiveCursorStyle = 161,
5249-
editorClassName = 162,
5250-
pixelRatio = 163,
5251-
tabFocusMode = 164,
5252-
layoutInfo = 165,
5253-
wrappingInfo = 166,
5254-
defaultColorDecorators = 167,
5255-
colorDecoratorsActivatedOn = 168,
5256-
inlineCompletionsAccessibilityVerbose = 169,
5257-
effectiveEditContext = 170,
5258-
scrollOnMiddleClick = 171,
5259-
effectiveAllowVariableFonts = 172,
5260-
doubleClickSelectsBlock = 173
5253+
rtlAutoDetect = 161,
5254+
effectiveCursorStyle = 162,
5255+
editorClassName = 163,
5256+
pixelRatio = 164,
5257+
tabFocusMode = 165,
5258+
layoutInfo = 166,
5259+
wrappingInfo = 167,
5260+
defaultColorDecorators = 168,
5261+
colorDecoratorsActivatedOn = 169,
5262+
inlineCompletionsAccessibilityVerbose = 170,
5263+
effectiveEditContext = 171,
5264+
scrollOnMiddleClick = 172,
5265+
effectiveAllowVariableFonts = 173,
5266+
doubleClickSelectsBlock = 174
52615267
}
52625268

52635269
export const EditorOptions: {
@@ -5429,6 +5435,7 @@ declare namespace monaco.editor {
54295435
defaultColorDecorators: IEditorOption<EditorOption.defaultColorDecorators, 'auto' | 'always' | 'never'>;
54305436
pixelRatio: IEditorOption<EditorOption.pixelRatio, number>;
54315437
tabFocusMode: IEditorOption<EditorOption.tabFocusMode, boolean>;
5438+
rtlAutoDetect: IEditorOption<EditorOption.rtlAutoDetect, boolean>;
54325439
layoutInfo: IEditorOption<EditorOption.layoutInfo, EditorLayoutInfo>;
54335440
wrappingInfo: IEditorOption<EditorOption.wrappingInfo, EditorWrappingInfo>;
54345441
wrappingIndent: IEditorOption<EditorOption.wrappingIndent, WrappingIndent>;

0 commit comments

Comments
 (0)