Skip to content

Commit 3ff1dce

Browse files
Enable edit context (#237497)
* Revert "Revert Enablement of EditContext on Insiders (#235062)" This reverts commit 45385e1. * adding product import * adding som code * removing console logs * make sure editor selection is observed after keybinding dispatches Co-authored-with: Aiday Mar <[email protected]> * removing empty lines --------- Co-authored-by: João Moreno <[email protected]>
1 parent 9642337 commit 3ff1dce

File tree

13 files changed

+120
-40
lines changed

13 files changed

+120
-40
lines changed

src/typings/editContext.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ interface EditContextEventHandlersEventMap {
5858

5959
type EventHandler<TEvent extends Event = Event> = (event: TEvent) => void;
6060

61-
interface TextUpdateEvent extends Event {
62-
new(type: DOMString, options?: TextUpdateEventInit): TextUpdateEvent;
61+
declare class TextUpdateEvent extends Event {
62+
constructor(type: DOMString, options?: TextUpdateEventInit);
6363

6464
readonly updateRangeStart: number;
6565
readonly updateRangeEnd: number;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { USUAL_WORD_SEPARATORS } from '../core/wordHelper.js';
1616
import * as nls from '../../../nls.js';
1717
import { AccessibilitySupport } from '../../../platform/accessibility/common/accessibility.js';
1818
import { IConfigurationPropertySchema } from '../../../platform/configuration/common/configurationRegistry.js';
19+
import product from '../../../platform/product/common/product.js';
1920

2021
//#region typed options
2122

@@ -5814,7 +5815,7 @@ export const EditorOptions = {
58145815
emptySelectionClipboard: register(new EditorEmptySelectionClipboard()),
58155816
dropIntoEditor: register(new EditorDropIntoEditor()),
58165817
experimentalEditContextEnabled: register(new EditorBooleanOption(
5817-
EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', false,
5818+
EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', product.quality !== 'stable',
58185819
{
58195820
description: nls.localize('experimentalEditContextEnabled', "Sets whether the new experimental edit context should be used instead of the text area."),
58205821
included: platform.isChrome || platform.isEdge || platform.isNative

src/vs/workbench/services/driver/browser/driver.ts

+48-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { getClientArea, getTopLeftOffset } from '../../../../base/browser/dom.js';
6+
import { getClientArea, getTopLeftOffset, isHTMLDivElement, isHTMLTextAreaElement } from '../../../../base/browser/dom.js';
77
import { mainWindow } from '../../../../base/browser/window.js';
88
import { coalesce } from '../../../../base/common/arrays.js';
99
import { language, locale } from '../../../../base/common/platform.js';
@@ -133,18 +133,54 @@ export class BrowserWindowDriver implements IWindowDriver {
133133
if (!element) {
134134
throw new Error(`Editor not found: ${selector}`);
135135
}
136+
if (isHTMLDivElement(element)) {
137+
// Edit context is enabled
138+
const editContext = element.editContext;
139+
if (!editContext) {
140+
throw new Error(`Edit context not found: ${selector}`);
141+
}
142+
const selectionStart = editContext.selectionStart;
143+
const selectionEnd = editContext.selectionEnd;
144+
const event = new TextUpdateEvent('textupdate', {
145+
updateRangeStart: selectionStart,
146+
updateRangeEnd: selectionEnd,
147+
text,
148+
selectionStart: selectionStart + text.length,
149+
selectionEnd: selectionStart + text.length,
150+
compositionStart: 0,
151+
compositionEnd: 0
152+
});
153+
editContext.dispatchEvent(event);
154+
} else if (isHTMLTextAreaElement(element)) {
155+
const start = element.selectionStart;
156+
const newStart = start + text.length;
157+
const value = element.value;
158+
const newValue = value.substr(0, start) + text + value.substr(start);
159+
160+
element.value = newValue;
161+
element.setSelectionRange(newStart, newStart);
162+
163+
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
164+
element.dispatchEvent(event);
165+
}
166+
}
136167

137-
const textarea = element as HTMLTextAreaElement;
138-
const start = textarea.selectionStart;
139-
const newStart = start + text.length;
140-
const value = textarea.value;
141-
const newValue = value.substr(0, start) + text + value.substr(start);
142-
143-
textarea.value = newValue;
144-
textarea.setSelectionRange(newStart, newStart);
145-
146-
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
147-
textarea.dispatchEvent(event);
168+
async getEditorSelection(selector: string): Promise<{ selectionStart: number; selectionEnd: number }> {
169+
const element = mainWindow.document.querySelector(selector);
170+
if (!element) {
171+
throw new Error(`Editor not found: ${selector}`);
172+
}
173+
if (isHTMLDivElement(element)) {
174+
const editContext = element.editContext;
175+
if (!editContext) {
176+
throw new Error(`Edit context not found: ${selector}`);
177+
}
178+
return { selectionStart: editContext.selectionStart, selectionEnd: editContext.selectionEnd };
179+
} else if (isHTMLTextAreaElement(element)) {
180+
return { selectionStart: element.selectionStart, selectionEnd: element.selectionEnd };
181+
} else {
182+
throw new Error(`Unknown type of element: ${selector}`);
183+
}
148184
}
149185

150186
async getTerminalBuffer(selector: string): Promise<string[]> {

src/vs/workbench/services/driver/common/driver.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export interface IWindowDriver {
3838
getElements(selector: string, recursive: boolean): Promise<IElement[]>;
3939
getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }>;
4040
typeInEditor(selector: string, text: string): Promise<void>;
41+
getEditorSelection(selector: string): Promise<{ selectionStart: number; selectionEnd: number }>;
4142
getTerminalBuffer(selector: string): Promise<string[]>;
4243
writeInTerminal(selector: string, text: string): Promise<void>;
4344
getLocaleInfo(): Promise<ILocaleInfo>;

test/automation/src/code.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { launch as launchPlaywrightBrowser } from './playwrightBrowser';
1212
import { PlaywrightDriver } from './playwrightDriver';
1313
import { launch as launchPlaywrightElectron } from './playwrightElectron';
1414
import { teardown } from './processes';
15+
import { Quality } from './application';
1516

1617
export interface LaunchOptions {
1718
codePath?: string;
@@ -28,6 +29,7 @@ export interface LaunchOptions {
2829
readonly tracing?: boolean;
2930
readonly headless?: boolean;
3031
readonly browser?: 'chromium' | 'webkit' | 'firefox';
32+
readonly quality: Quality;
3133
}
3234

3335
interface ICodeInstance {
@@ -77,15 +79,15 @@ export async function launch(options: LaunchOptions): Promise<Code> {
7779
const { serverProcess, driver } = await measureAndLog(() => launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger);
7880
registerInstance(serverProcess, options.logger, 'server');
7981

80-
return new Code(driver, options.logger, serverProcess);
82+
return new Code(driver, options.logger, serverProcess, options.quality);
8183
}
8284

8385
// Electron smoke tests (playwright)
8486
else {
8587
const { electronProcess, driver } = await measureAndLog(() => launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger);
8688
registerInstance(electronProcess, options.logger, 'electron');
8789

88-
return new Code(driver, options.logger, electronProcess);
90+
return new Code(driver, options.logger, electronProcess, options.quality);
8991
}
9092
}
9193

@@ -96,7 +98,8 @@ export class Code {
9698
constructor(
9799
driver: PlaywrightDriver,
98100
readonly logger: Logger,
99-
private readonly mainProcess: cp.ChildProcess
101+
private readonly mainProcess: cp.ChildProcess,
102+
readonly quality: Quality
100103
) {
101104
this.driver = new Proxy(driver, {
102105
get(target, prop) {
@@ -242,6 +245,10 @@ export class Code {
242245
await this.poll(() => this.driver.typeInEditor(selector, text), () => true, `type in editor '${selector}'`);
243246
}
244247

248+
async waitForEditorSelection(selector: string, accept: (selection: { selectionStart: number; selectionEnd: number }) => boolean): Promise<void> {
249+
await this.poll(() => this.driver.getEditorSelection(selector), accept, `get editor selection '${selector}'`);
250+
}
251+
245252
async waitForTerminalBuffer(selector: string, accept: (result: string[]) => boolean): Promise<void> {
246253
await this.poll(() => this.driver.getTerminalBuffer(selector), accept, `get terminal buffer '${selector}'`);
247254
}

test/automation/src/debug.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Code, findElement } from './code';
99
import { Editors } from './editors';
1010
import { Editor } from './editor';
1111
import { IElement } from './driver';
12+
import { Quality } from './application';
1213

1314
const VIEWLET = 'div[id="workbench.view.debug"]';
1415
const DEBUG_VIEW = `${VIEWLET}`;
@@ -31,7 +32,8 @@ const CONSOLE_OUTPUT = `.repl .output.expression .value`;
3132
const CONSOLE_EVALUATION_RESULT = `.repl .evaluation-result.expression .value`;
3233
const CONSOLE_LINK = `.repl .value a.link`;
3334

34-
const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea';
35+
const REPL_FOCUSED_NATIVE_EDIT_CONTEXT = '.repl-input-wrapper .monaco-editor .native-edit-context';
36+
const REPL_FOCUSED_TEXTAREA = '.repl-input-wrapper .monaco-editor textarea';
3537

3638
export interface IStackFrame {
3739
name: string;
@@ -127,8 +129,9 @@ export class Debug extends Viewlet {
127129

128130
async waitForReplCommand(text: string, accept: (result: string) => boolean): Promise<void> {
129131
await this.commands.runCommand('Debug: Focus on Debug Console View');
130-
await this.code.waitForActiveElement(REPL_FOCUSED);
131-
await this.code.waitForSetValue(REPL_FOCUSED, text);
132+
const selector = this.code.quality === Quality.Stable ? REPL_FOCUSED_TEXTAREA : REPL_FOCUSED_NATIVE_EDIT_CONTEXT;
133+
await this.code.waitForActiveElement(selector);
134+
await this.code.waitForSetValue(selector, text);
132135

133136
// Wait for the keys to be picked up by the editor model such that repl evaluates what just got typed
134137
await this.editor.waitForEditorContents('debug:replinput', s => s.indexOf(text) >= 0);

test/automation/src/editor.ts

+15-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { References } from './peek';
77
import { Commands } from './workbench';
88
import { Code } from './code';
9+
import { Quality } from './application';
910

1011
const RENAME_BOX = '.monaco-editor .monaco-editor.rename-box';
1112
const RENAME_INPUT = `${RENAME_BOX} .rename-input`;
@@ -78,10 +79,10 @@ export class Editor {
7879
async waitForEditorFocus(filename: string, lineNumber: number, selectorPrefix = ''): Promise<void> {
7980
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
8081
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
81-
const textarea = `${editor} textarea`;
82+
const editContext = `${editor} ${this._editContextSelector()}`;
8283

8384
await this.code.waitAndClick(line, 1, 1);
84-
await this.code.waitForActiveElement(textarea);
85+
await this.code.waitForActiveElement(editContext);
8586
}
8687

8788
async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise<any> {
@@ -92,14 +93,23 @@ export class Editor {
9293

9394
await this.code.waitForElement(editor);
9495

95-
const textarea = `${editor} textarea`;
96-
await this.code.waitForActiveElement(textarea);
96+
const editContext = `${editor} ${this._editContextSelector()}`;
97+
await this.code.waitForActiveElement(editContext);
9798

98-
await this.code.waitForTypeInEditor(textarea, text);
99+
await this.code.waitForTypeInEditor(editContext, text);
99100

100101
await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix);
101102
}
102103

104+
async waitForEditorSelection(filename: string, accept: (selection: { selectionStart: number; selectionEnd: number }) => boolean): Promise<void> {
105+
const selector = `${EDITOR(filename)} ${this._editContextSelector()}`;
106+
await this.code.waitForEditorSelection(selector, accept);
107+
}
108+
109+
private _editContextSelector() {
110+
return this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context';
111+
}
112+
103113
async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise<any> {
104114
const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' ');
105115
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));

test/automation/src/editors.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { Quality } from './application';
67
import { Code } from './code';
78

89
export class Editors {
@@ -53,7 +54,7 @@ export class Editors {
5354
}
5455

5556
async waitForActiveEditor(fileName: string, retryCount?: number): Promise<any> {
56-
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`;
57+
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`;
5758
return this.code.waitForActiveElement(selector, retryCount);
5859
}
5960

test/automation/src/extensions.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Code } from './code';
88
import { ncp } from 'ncp';
99
import { promisify } from 'util';
1010
import { Commands } from './workbench';
11+
import { Quality } from './application';
1112
import path = require('path');
1213
import fs = require('fs');
1314

@@ -20,7 +21,7 @@ export class Extensions extends Viewlet {
2021

2122
async searchForExtension(id: string): Promise<any> {
2223
await this.commands.runCommand('Extensions: Focus on Extensions View', { exactLabelMatch: true });
23-
await this.code.waitForTypeInEditor('div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor textarea', `@id:${id}`);
24+
await this.code.waitForTypeInEditor(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`, `@id:${id}`);
2425
await this.code.waitForTextContent(`div.part.sidebar div.composite.title h2`, 'Extensions: Marketplace');
2526

2627
let retrials = 1;

test/automation/src/notebook.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { Quality } from './application';
67
import { Code } from './code';
78
import { QuickAccess } from './quickaccess';
89
import { QuickInput } from './quickinput';
@@ -46,10 +47,10 @@ export class Notebook {
4647

4748
await this.code.waitForElement(editor);
4849

49-
const textarea = `${editor} textarea`;
50-
await this.code.waitForActiveElement(textarea);
50+
const editContext = `${editor} ${this.code.quality === Quality.Stable ? 'textarea' : '.native-edit-context'}`;
51+
await this.code.waitForActiveElement(editContext);
5152

52-
await this.code.waitForTypeInEditor(textarea, text);
53+
await this.code.waitForTypeInEditor(editContext, text);
5354

5455
await this._waitForActiveCellEditorContents(c => c.indexOf(text) > -1);
5556
}

test/automation/src/playwrightDriver.ts

+4
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ export class PlaywrightDriver {
288288
return this.page.evaluate(([driver, selector, text]) => driver.typeInEditor(selector, text), [await this.getDriverHandle(), selector, text] as const);
289289
}
290290

291+
async getEditorSelection(selector: string) {
292+
return this.page.evaluate(([driver, selector]) => driver.getEditorSelection(selector), [await this.getDriverHandle(), selector] as const);
293+
}
294+
291295
async getTerminalBuffer(selector: string) {
292296
return this.page.evaluate(([driver, selector]) => driver.getTerminalBuffer(selector), [await this.getDriverHandle(), selector] as const);
293297
}

test/automation/src/scm.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import { Viewlet } from './viewlet';
77
import { IElement } from './driver';
88
import { findElement, findElements, Code } from './code';
9+
import { Quality } from './application';
910

1011
const VIEWLET = 'div[id="workbench.view.scm"]';
11-
const SCM_INPUT = `${VIEWLET} .scm-editor textarea`;
12+
const SCM_INPUT_NATIVE_EDIT_CONTEXT = `${VIEWLET} .scm-editor .native-edit-context`;
13+
const SCM_INPUT_TEXTAREA = `${VIEWLET} .scm-editor textarea`;
1214
const SCM_RESOURCE = `${VIEWLET} .monaco-list-row .resource`;
1315
const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Refresh"]`;
1416
const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Commit"]`;
@@ -44,7 +46,7 @@ export class SCM extends Viewlet {
4446

4547
async openSCMViewlet(): Promise<any> {
4648
await this.code.dispatchKeybinding('ctrl+shift+g');
47-
await this.code.waitForElement(SCM_INPUT);
49+
await this.code.waitForElement(this._editContextSelector());
4850
}
4951

5052
async waitForChange(name: string, type?: string): Promise<void> {
@@ -71,9 +73,13 @@ export class SCM extends Viewlet {
7173
}
7274

7375
async commit(message: string): Promise<void> {
74-
await this.code.waitAndClick(SCM_INPUT);
75-
await this.code.waitForActiveElement(SCM_INPUT);
76-
await this.code.waitForSetValue(SCM_INPUT, message);
76+
await this.code.waitAndClick(this._editContextSelector());
77+
await this.code.waitForActiveElement(this._editContextSelector());
78+
await this.code.waitForSetValue(this._editContextSelector(), message);
7779
await this.code.waitAndClick(COMMIT_COMMAND);
7880
}
81+
82+
private _editContextSelector(): string {
83+
return this.code.quality === Quality.Stable ? SCM_INPUT_TEXTAREA : SCM_INPUT_NATIVE_EDIT_CONTEXT;
84+
}
7985
}

0 commit comments

Comments
 (0)