Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement missing API for vscode.typescript-language-features@1.62.3 #11083

Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

- [plugin] Introduce `DebugSession#workspaceFolder` [#11090](https://github.com/eclipse-theia/theia/pull/11090) - Contributed on behalf of STMicroelectronics

<a name="breaking_changes_1.26.0">[Breaking Changes:](#breaking_changes_1.26.0)</a>

- [callhierarchy] `paths.ts` and `glob.ts` moved to `core/src/common`; `language-selector.ts` moved to `editor/src/common`. Any imports will need to be updated.

## v1.25.0 - 4/28/2022

[1.25.0 Milestone](https://github.com/eclipse-theia/theia/milestone/35)
Expand Down
14 changes: 12 additions & 2 deletions examples/api-tests/src/preferences.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,21 @@ describe('Preferences', function () {
}

async function deleteAllValues() {
return setValueTo(undefined);
}

/**
* @param {any} value - A JSON value to write to the workspace preference file.
*/
async function setValueTo(value) {
const reference = await modelService.createModelReference(uri);
if (reference.object.dirty) {
await reference.object.revert();
}
/** @type {import ('@theia/preferences/lib/browser/folder-preference-provider').FolderPreferenceProvider} */
const provider = Array.from(folderPreferences['providers'].values()).find(candidate => candidate.getConfigUri().isEqual(uri));
assert.isDefined(provider);
await provider['doSetPreference']('', [], undefined);
await provider['doSetPreference']('', [], value);
reference.dispose();
}

Expand All @@ -92,7 +99,10 @@ describe('Preferences', function () {
if (!fileExistsBeforehand) {
await fileService.delete(uri, { fromUserGesture: false }).catch(() => { });
} else {
await fileService.write(uri, contentBeforehand);
let content = '';
try { content = JSON.parse(contentBeforehand); } catch { }
// Use the preference service because its promise is guaranteed to resolve after the file change is complete.
await setValueTo(content);
}
});

Expand Down
53 changes: 48 additions & 5 deletions examples/api-tests/src/typescript.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('TypeScript', function () {
const contextKeyService = container.get(ContextKeyService);
const commands = container.get(CommandRegistry);
const openerService = container.get(OpenerService);
/** @type {KeybindingRegistry} */
const keybindings = container.get(KeybindingRegistry);
/** @type {import('@theia/core/lib/browser/preferences/preference-service').PreferenceService} */
const preferences = container.get(PreferenceService);
Expand Down Expand Up @@ -108,20 +109,21 @@ describe('TypeScript', function () {
/**
* @param {() => Promise<unknown> | unknown} condition
* @param {number | undefined} [timeout]
* @param {string | undefined} [message]
* @returns {Promise<void>}
*/
function waitForAnimation(condition, timeout) {
const success = new Promise(async (resolve, dispose) => {
toTearDown.push({ dispose });
function waitForAnimation(condition, timeout, message) {
const success = new Promise(async (resolve, reject) => {
toTearDown.push({ dispose: () => reject(message ?? 'Test terminated before resolution.') });
do {
await animationFrame();
} while (!condition());
resolve();
});
if (timeout !== undefined) {
const timedOut = new Promise((_, fail) => {
const toClear = setTimeout(() => fail(new Error('Wait for animation timed out.')), timeout);
toTearDown.push({ dispose: () => (fail(new Error('Wait for animation timed out.')), clearTimeout(toClear)) });
const toClear = setTimeout(() => fail(new Error(message ?? 'Wait for animation timed out.')), timeout);
toTearDown.push({ dispose: () => (fail(new Error(message ?? 'Wait for animation timed out.')), clearTimeout(toClear)) });
});
return Promise.race([success, timedOut]);
}
Expand Down Expand Up @@ -277,6 +279,7 @@ describe('TypeScript', function () {
const from = 'an editor' + (preview ? ' preview' : '');
it('within ' + from, async function () {
const editor = await openEditor(demoFileUri, preview);
editor.getControl().revealLine(24);
// const demoInstance = new Demo|Class('demo');
editor.getControl().setPosition({ lineNumber: 24, column: 30 });
assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DemoClass');
Expand All @@ -299,6 +302,7 @@ describe('TypeScript', function () {
await editorManager.open(definitionFileUri, { mode: 'open' });

const editor = await openEditor(demoFileUri, preview);
editor.getControl().revealLine(32);
// const bar: Defined|Interface = { coolField: [] };
editor.getControl().setPosition({ lineNumber: 32, column: 19 });
assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
Expand All @@ -319,6 +323,7 @@ describe('TypeScript', function () {

it(`from ${from} to an editor preview`, async function () {
const editor = await openEditor(demoFileUri);
editor.getControl().revealLine(32);
// const bar: Defined|Interface = { coolField: [] };
editor.getControl().setPosition({ lineNumber: 32, column: 19 });
assert.equal(editor.getControl().getModel().getWordAtPosition(editor.getControl().getPosition()).word, 'DefinedInterface');
Expand Down Expand Up @@ -730,4 +735,42 @@ SPAN {
});
}

it('Can execute code actions', async function () {
const editor = await openEditor(demoFileUri);
/** @type {import('@theia/monaco-editor-core/src/vs/editor/contrib/codeAction/browser/codeActionCommands').QuickFixController} */
const quickFixController = editor.getControl().getContribution('editor.contrib.quickFixController');
const isActionAvailable = () => {
const lightbulbVisibility = quickFixController['_ui'].rawValue?.['_lightBulbWidget'].rawValue?.['_domNode'].style.visibility;
return lightbulbVisibility !== undefined && lightbulbVisibility !== 'hidden';
}
assert.isFalse(isActionAvailable());
// import { DefinedInterface } from "./demo-definitions-file";
assert.strictEqual(editor.getControl().getModel().getLineContent(30), 'import { DefinedInterface } from "./demo-definitions-file";');
editor.getControl().revealLine(30);
editor.getControl().setSelection(new Selection(30, 1, 30, 60));
await waitForAnimation(() => isActionAvailable(), 5000, 'No code action available. (1)');
assert.isTrue(isActionAvailable());

await commands.executeCommand('editor.action.quickFix');
await waitForAnimation(() => Boolean(document.querySelector('.p-Widget.p-Menu')), 5000, 'No context menu appeared. (1)');
await animationFrame();

keybindings.dispatchKeyDown('ArrowDown');
keybindings.dispatchKeyDown('Enter');

await waitForAnimation(() => editor.getControl().getModel().getLineContent(30) === 'import * as demoDefinitionsFile from "./demo-definitions-file";', 5000, 'The namespace import did not take effect.');

editor.getControl().setSelection(new Selection(30, 1, 30, 64));
await waitForAnimation(() => isActionAvailable(), 5000, 'No code action available. (2)');

// Change it back: https://github.com/eclipse-theia/theia/issues/11059
await commands.executeCommand('editor.action.quickFix');
await waitForAnimation(() => Boolean(document.querySelector('.p-Widget.p-Menu')), 5000, 'No context menu appeared. (2)');
await animationFrame();

keybindings.dispatchKeyDown('ArrowDown');
keybindings.dispatchKeyDown('Enter');

await waitForAnimation(() => editor.getControl().getModel().getLineContent(30) === 'import { DefinedInterface } from "./demo-definitions-file";', 5000, 'The named import did not take effect.');
});
});
2 changes: 1 addition & 1 deletion examples/api-tests/src/undo-redo-selectAll.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('Undo, Redo and Select All', function () {
let originalValue = undefined;
before(async () => {
originalValue = preferenceService.inspect('files.autoSave').globalValue;
await preferenceService.set('files.autoSave', false, PreferenceScope.User);
await preferenceService.set('files.autoSave', 'off', PreferenceScope.User);
shell.leftPanelHandler.collapse();
});

Expand Down
23 changes: 9 additions & 14 deletions examples/playwright/src/theia-notification-indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,26 @@

import { TheiaStatusIndicator } from './theia-status-indicator';

const NOTIFICATION_ICON = 'codicon-bell';
const NOTIFICATION_DOT_ICON = 'codicon-bell-dot';
const NOTIFICATION_ICONS = [NOTIFICATION_ICON, NOTIFICATION_DOT_ICON];

export class TheiaNotificationIndicator extends TheiaStatusIndicator {

protected get title(): string {
return 'Notification';
}

override async isVisible(): Promise<boolean> {
return super.isVisible(NOTIFICATION_ICONS, this.title);
}
id = 'theia-notification-center';

async hasNotifications(): Promise<boolean> {
return super.isVisible(NOTIFICATION_DOT_ICON, this.title);
const container = await this.getElementHandle();
const bellWithDot = await container.$(`.${NOTIFICATION_DOT_ICON}`);
return Boolean(bellWithDot?.isVisible());
}

override async waitForVisible(expectNotifications = false): Promise<void> {
await super.waitForVisibleByIcon(expectNotifications ? NOTIFICATION_DOT_ICON : NOTIFICATION_ICON);
await super.waitForVisible();
if (expectNotifications && !(await this.hasNotifications())) {
throw new Error('No notifications when notifications expected.');
}
}

async toggleOverlay(): Promise<void> {
const hasNotifications = await this.hasNotifications();
const element = await this.getElementHandleByIcon(hasNotifications ? NOTIFICATION_DOT_ICON : NOTIFICATION_ICON, this.title);
const element = await this.getElementHandle();
if (element) {
await element.click();
}
Expand Down
11 changes: 2 additions & 9 deletions examples/playwright/src/theia-problem-indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@
import { ElementHandle } from '@playwright/test';
import { TheiaStatusIndicator } from './theia-status-indicator';

const PROBLEM_ICON = 'codicon-error';

export class TheiaProblemIndicator extends TheiaStatusIndicator {

override async isVisible(): Promise<boolean> {
const handle = await super.getElementHandleByIcon(PROBLEM_ICON);
return !!handle && handle.isVisible();
}
id = 'problem-marker-status';

async numberOfProblems(): Promise<number> {
const spans = await this.getSpans();
Expand All @@ -37,8 +31,7 @@ export class TheiaProblemIndicator extends TheiaStatusIndicator {
}

protected async getSpans(): Promise<ElementHandle[] | undefined> {
const handle = await super.getElementHandleByIcon(PROBLEM_ICON);
const handle = await this.getElementHandle();
return handle?.$$('span');
}

}
59 changes: 15 additions & 44 deletions examples/playwright/src/theia-status-indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,60 +17,31 @@
import { ElementHandle } from '@playwright/test';
import { TheiaPageObject } from './theia-page-object';

export class TheiaStatusIndicator extends TheiaPageObject {
export abstract class TheiaStatusIndicator extends TheiaPageObject {
protected abstract id: string;

protected elementSpanSelector = '#theia-statusBar .element span';
protected statusBarElementSelector = '#theia-statusBar div.element';

protected async getElementHandle(): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this.page.$(this.elementSpanSelector);
protected getSelectorForId(id: string): string {
return `${this.statusBarElementSelector}#status-bar-${id}`;
}

async waitForVisible(): Promise<void> {
await this.page.waitForSelector(this.elementSpanSelector);
async waitForVisible(waitForDetached = false): Promise<void> {
await this.page.waitForSelector(this.getSelectorForId(this.id), waitForDetached ? { state: 'detached' } : {});
}

protected getSelectorByTitle(title: string): string {
return `.element[title="${title}"]`;
}

async getElementHandleByTitle(title: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
// Fetch element via title in case status elements exist without a dedicated Codicon icon
return this.page.$(this.getSelectorByTitle(title));
}

protected getSelectorByIcon(icon: string): string {
return `${this.elementSpanSelector}.codicon.${icon}`;
}

async getElementHandleByIcon(iconClass: string | string[], titleContain = ''): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
const icons = Array.isArray(iconClass) ? iconClass : [iconClass];
for (const icon of icons) {
const span = await this.page.$(this.getSelectorByIcon(icon));
if (span) {
const parent = await span.$('..');
if (titleContain === '') {
return parent;
} else {
const parentTitle = await parent?.getAttribute('title');
if (parentTitle?.includes(titleContain)) { return parent; }
}
}
async getElementHandle(): Promise<ElementHandle<SVGElement | HTMLElement>> {
const element = await this.page.$(this.getSelectorForId(this.id));
if (element) {
return element;
}
throw new Error('Cannot find indicator');
}

async waitForVisibleByTitle(title: string, waitForDetached = false): Promise<void> {
await this.page.waitForSelector(this.getSelectorByTitle(title), waitForDetached ? { state: 'detached' } : {});
}

async waitForVisibleByIcon(icon: string, waitForDetached = false): Promise<void> {
await this.page.waitForSelector(this.getSelectorByIcon(icon), waitForDetached ? { state: 'detached' } : {});
throw new Error('Could not find status bar element with ID ' + this.id);
}

async isVisible(icon: string | string[], titleContain = ''): Promise<boolean> {
async isVisible(): Promise<boolean> {
try {
const element = await this.getElementHandleByIcon(icon, titleContain);
return !!element && element.isVisible();
const element = await this.getElementHandle();
return element.isVisible();
} catch (err) {
return false;
}
Expand Down
6 changes: 1 addition & 5 deletions examples/playwright/src/theia-toggle-bottom-indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@

import { TheiaStatusIndicator } from './theia-status-indicator';

const TOGGLE_BOTTOM_ICON = 'codicon-window';

export class TheiaToggleBottomIndicator extends TheiaStatusIndicator {
override async isVisible(): Promise<boolean> {
return super.isVisible(TOGGLE_BOTTOM_ICON);
}
id = 'bottom-panel-toggle';
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
"vscode.theme-red": "https://open-vsx.org/api/vscode/theme-red/1.62.3/file/vscode.theme-red-1.62.3.vsix",
"vscode.theme-solarized-dark": "https://open-vsx.org/api/vscode/theme-solarized-dark/1.62.3/file/vscode.theme-solarized-dark-1.62.3.vsix",
"vscode.theme-tomorrow-night-blue": "https://open-vsx.org/api/vscode/theme-tomorrow-night-blue/1.62.3/file/vscode.theme-tomorrow-night-blue-1.62.3.vsix",
"vscode.typescript": "https://open-vsx.org/api/vscode/typescript/1.62.3/file/vscode.typescript-1.62.3.vsix",
"vscode.typescript-language-features": "https://open-vsx.org/api/vscode/typescript-language-features/1.62.3/file/vscode.typescript-language-features-1.62.3.vsix",
"vscode-builtin-extensions-pack": "https://open-vsx.org/api/eclipse-theia/builtin-extension-pack/1.50.1/file/eclipse-theia.builtin-extension-pack-1.50.1.vsix",
"vscode-editorconfig": "https://open-vsx.org/api/EditorConfig/EditorConfig/0.14.4/file/EditorConfig.EditorConfig-0.14.4.vsix",
"vscode-eslint": "https://open-vsx.org/api/dbaeumer/vscode-eslint/2.1.1/file/dbaeumer.vscode-eslint-2.1.1.vsix",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { CancellationToken } from '@theia/core';
import URI from '@theia/core/lib/common/uri';
import { ContributionProvider, Disposable, Emitter, Event } from '@theia/core/lib/common';
import { CallHierarchyItem, CallHierarchyIncomingCall, CallHierarchyOutgoingCall } from './callhierarchy';
import { LanguageSelector, score } from '../common/language-selector';
import { LanguageSelector, score } from '@theia/editor/lib/common/language-selector';

export const CallHierarchyService = Symbol('CallHierarchyService');

Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"@phosphor/signaling": "1",
"@phosphor/virtualdom": "1",
"@phosphor/widgets": "1",
"@primer/octicons-react": "^9.0.0",
"@theia/application-package": "1.25.0",
"@types/body-parser": "^1.16.4",
"@types/cookie": "^0.3.3",
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1608,7 +1608,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
{
id: 'statusBarItem.warningBackground', defaults: {
dark: Color.darken('warningBackground', 0.4),
light: Color.darken('warningBackground', 0.4),
light: Color.darken('warningBackground', 0.4),
hc: undefined
}, description: 'Status bar warning items background color. Warning items stand out from other status bar entries to indicate warning conditions. The status bar is shown in the bottom of the window.'
},
Expand Down Expand Up @@ -2227,6 +2227,15 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
hc: Color.lighten('activityBar.foreground', 0.1),
}, description: 'Foreground color of active toolbar item',
},
{
id: 'editorHoverWidgetInternalBorder',
defaults: {
dark: Color.transparent('editorHoverWidget.border', 0.5),
light: Color.transparent('editorHoverWidget.border', 0.5),
hc: Color.transparent('editorHoverWidget.border', 0.5),
},
description: 'The border between subelements of a hover widget'
}
);
}
}
Loading