diff --git a/.travis.yml b/.travis.yml index 3883b53806037..f5f6552332632 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ cache: - examples/browser/node_modules - examples/electron/node_modules - node_modules - - packages/bunyan/node_modules - packages/callhierarchy/node_modules - packages/console/node_modules - packages/core/node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index e278fd841bc86..d01598d78e02a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## v0.14.0 + +- [bunyan] removed [`@theia/bunyan`](https://github.com/eclipse-theia/theia/tree/b92a5673de1e9d1bdc85e6200486b92394200579/packages/bunyan) extension [#6651](https://github.com/eclipse-theia/theia/pull/6651) + +Breaking changes: + +- [core] new browser windows spawned through opener-service have noopener set, preventing them from accessing window.opener and giving them their own event loop. openNewWindow will no longer return a Window as a result. +- [terminal] renamed `TerminalCopyOnSelectionHander` to `TerminalCopyOnSelectionHandler` [#6692](https://github.com/eclipse-theia/theia/pull/6692) +- [debug] renamed command `COPY_VARAIBLE_AS_EXPRESSION` to `COPY_VARIABLE_AS_EXPRESSION` [#6698](https://github.com/eclipse-theia/theia/pull/6698) +- [debug] renamed command `COPY_VARAIBLE_VALUE` to `COPY_VARIABLE_VALUE` [#6698](https://github.com/eclipse-theia/theia/pull/6698) +- [debug] renamed getter method `multiSesssion` to `multiSession` [#6698](https://github.com/eclipse-theia/theia/pull/6698) +- [task] changed the data structure of `ProvidedTaskConfigurations.tasksMap` [#6718](https://github.com/eclipse-theia/theia/pull/6718) +- [task] added `taskDefinitionRegistry` and `taskSourceResolver` to the constructor of `TaskRunQuickOpenItem` and `ConfigureBuildOrTestTaskQuickOpenItem` [#6718](https://github.com/eclipse-theia/theia/pull/6718) + ## v0.13.0 - [console] added filtering support based on severity [#6486](https://github.com/eclipse-theia/theia/pull/6486) @@ -69,7 +83,7 @@ Breaking changes: allowing them to break out and manipulate shared data as cookies, local storage or even start service workers for the main window as well as for each other. Now each webview will be deployed on own origin by default. - Webview origin pattern can be configured with `THEIA_WEBVIEW_EXTERNAL_ENDPOINT` env variable. The default value is `{{uuid}}.webview.{{hostname}}`. - Here `{{uuid}}` and `{{hostname}}` are placeholders which get replaced at runtime with proper webview uuid + Here `{{uuid}}` and `{{hostname}}` are placeholders which get replaced at runtime with proper webview uuid and [hostname](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hostname) correspondingly. - To switch to un-secure mode as before configure `THEIA_WEBVIEW_EXTERNAL_ENDPOINT` with `{{hostname}}` as a value. You can also drop `{{uuid}}.` prefix, in this case, webviews still will be able to access each other but not the main window. diff --git a/dev-packages/application-manager/package.json b/dev-packages/application-manager/package.json index 79e571b406eb3..37fc6854b3dbe 100644 --- a/dev-packages/application-manager/package.json +++ b/dev-packages/application-manager/package.json @@ -36,7 +36,6 @@ "@theia/compression-webpack-plugin": "^3.0.0", "@types/fs-extra": "^4.0.2", "babel-loader": "^8.0.6", - "bunyan": "^1.8.10", "circular-dependency-plugin": "^5.0.0", "copy-webpack-plugin": "^4.5.0", "css-loader": "^0.28.1", diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index 8e53f7d664cfb..816d8bd5aad38 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -69,7 +69,7 @@ require('reflect-metadata'); const { Container } = require('inversify'); const { FrontendApplication } = require('@theia/core/lib/browser'); const { frontendApplicationModule } = require('@theia/core/lib/browser/frontend-application-module'); -const { messagingFrontendModule } = require('@theia/core/lib/browser/messaging/messaging-frontend-module'); +const { messagingFrontendModule } = require('@theia/core/lib/${this.pck.isBrowser() ? 'browser/messaging/messaging-frontend-module' : 'electron-browser/messaging/electron-messaging-frontend-module'}'); const { loggerFrontendModule } = require('@theia/core/lib/browser/logger-frontend-module'); const { ThemeService } = require('@theia/core/lib/browser/theming'); const { FrontendApplicationConfigProvider } = require('@theia/core/lib/browser/frontend-application-config-provider'); @@ -141,6 +141,10 @@ const Storage = require('electron-store'); const electronStore = new Storage(); app.on('ready', () => { + // Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit") + // See: https://github.com/electron-userland/electron-builder/issues/2468 + app.setName(applicationName); + const { screen } = electron; // Remove the default electron menus, waiting for the application to set its own. diff --git a/doc/Developing.md b/doc/Developing.md index 3a25806db5e8a..c0f9dcb12fe2c 100644 --- a/doc/Developing.md +++ b/doc/Developing.md @@ -232,7 +232,7 @@ Let assume you have to work for instance in the `@theia/navigator` extension. Bu - Start the backend by using `yarn run start`. - In a browser: Open http://localhost:3000/ and use the dev tools for debugging. - - Open the debug view and run the `Launch Broowser Frontend` configuration. + - Open the debug view and run the `Launch Browser Frontend` configuration. ### Debug the browser example's frontend and backend at the same time diff --git a/packages/bunyan/README.md b/packages/bunyan/README.md deleted file mode 100644 index 0dc9db7d8249d..0000000000000 --- a/packages/bunyan/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Theia bunyan Logger Extension - -This extension provides the [`bunyan`](https://www.npmjs.com/package/bunyan)-based logger implementation for Theia. - -See [here](https://www.theia-ide.org/doc/index.html) for a detailed documentation. - -## License -- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/) -- [δΈ€ (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp) diff --git a/packages/bunyan/compile.tsconfig.json b/packages/bunyan/compile.tsconfig.json deleted file mode 100644 index 4c867a02d2696..0000000000000 --- a/packages/bunyan/compile.tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../configs/base.tsconfig", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib", - "baseUrl": "." - }, - "include": [ - "src" - ] -} \ No newline at end of file diff --git a/packages/bunyan/package.json b/packages/bunyan/package.json deleted file mode 100644 index 886918ae42881..0000000000000 --- a/packages/bunyan/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@theia/bunyan", - "version": "0.13.0", - "description": "Theia - bunyan Logger Extension", - "dependencies": { - "@theia/core": "^0.13.0", - "@types/bunyan": "^1.8.0", - "bunyan": "^1.8.10" - }, - "publishConfig": { - "access": "public" - }, - "theiaExtensions": [ - { - "backend": "lib/node/bunyan-backend-module" - } - ], - "keywords": [ - "theia-extension" - ], - "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", - "repository": { - "type": "git", - "url": "https://github.com/eclipse-theia/theia.git" - }, - "bugs": { - "url": "https://github.com/eclipse-theia/theia/issues" - }, - "homepage": "https://github.com/eclipse-theia/theia", - "files": [ - "lib", - "src" - ], - "scripts": { - "prepare": "yarn run clean && yarn run build", - "clean": "theiaext clean", - "build": "theiaext build", - "watch": "theiaext watch", - "test": "theiaext test" - }, - "devDependencies": { - "@theia/ext-scripts": "^0.13.0" - }, - "nyc": { - "extends": "../../configs/nyc.json" - } -} diff --git a/packages/bunyan/src/node/bunyan-backend-module.ts b/packages/bunyan/src/node/bunyan-backend-module.ts deleted file mode 100644 index 4566dc83637e4..0000000000000 --- a/packages/bunyan/src/node/bunyan-backend-module.ts +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************** - * Copyright (C) 2018 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { interfaces, ContainerModule } from 'inversify'; -import { ILoggerServer } from '@theia/core/lib/common/logger-protocol'; -import { BunyanLoggerServer } from './bunyan-logger-server'; - -export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { - rebind(ILoggerServer).to(BunyanLoggerServer).inSingletonScope(); -}); diff --git a/packages/bunyan/src/node/bunyan-logger-server.ts b/packages/bunyan/src/node/bunyan-logger-server.ts deleted file mode 100644 index ead92aea2fc22..0000000000000 --- a/packages/bunyan/src/node/bunyan-logger-server.ts +++ /dev/null @@ -1,221 +0,0 @@ -/******************************************************************************** - * Copyright (C) 2018 Ericsson and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import * as bunyan from 'bunyan'; -import { inject, injectable, postConstruct } from 'inversify'; -import { LoggerWatcher } from '@theia/core/lib/common/logger-watcher'; -import { LogLevelCliContribution } from '@theia/core/lib/node/logger-cli-contribution'; -import { ILoggerServer, ILoggerClient, ILogLevelChangedEvent, LogLevel, rootLoggerName } from '@theia/core/lib/common/logger-protocol'; - -@injectable() -export class BunyanLoggerServer implements ILoggerServer { - - /* Root logger and all child logger array. */ - private readonly loggers = new Map(); - - /* Logger client to send notifications to. */ - private client: ILoggerClient | undefined = undefined; - - @inject(LoggerWatcher) - protected watcher: LoggerWatcher; - - @inject(LogLevelCliContribution) - protected cli: LogLevelCliContribution; - - @postConstruct() - protected init(): void { - /* Create the root logger by default. */ - const opts = this.makeLoggerOptions(rootLoggerName); - const rootOpts = Object.assign(opts, { name: 'Theia' }); - const logger = bunyan.createLogger(rootOpts); - this.loggers.set(rootLoggerName, logger); - - this.cli.onLogConfigChanged(() => this.updateLogLevels()); - } - - protected updateLogLevels(): void { - for (const loggerName of this.loggers.keys()) { - const newLevel = this.cli.logLevelFor(loggerName); - this.setLogLevel(loggerName, newLevel); - } - } - - protected makeLoggerOptions(name: string): { - logger: string; - level: number; - } { - return { - logger: name, - level: this.toBunyanLevel(this.cli.logLevelFor(name)), - }; - } - - dispose(): void { - // no-op - } - - /* Create a logger child of the root logger. See the bunyan child - * documentation. */ - child(name: string): Promise { - if (name.length === 0) { - return Promise.reject(new Error("Can't create a logger with an empty name.")); - } - - if (this.loggers.has(name)) { - /* Logger already exists. */ - return Promise.resolve(); - } - - const rootLogger = this.loggers.get(rootLoggerName); - if (rootLogger === undefined) { - throw new Error('No root logger.'); - } - - const opts = this.makeLoggerOptions(name); - const logger = rootLogger.child(opts); - this.loggers.set(name, logger); - - return Promise.resolve(); - } - - /* Set the client to receive notifications on. */ - setClient(client: ILoggerClient | undefined): void { - this.client = client; - } - - /** - * Set the log level for a logger. logLevel should be one of Theia's - * LogLevel. - */ - setLogLevel(name: string, newLogLevel: number): Promise { - const logger = this.loggers.get(name); - if (logger === undefined) { - throw new Error(`No logger named ${name}.`); - } - - // Does the log level really change? - const newBunyanLogLevel = this.toBunyanLevel(newLogLevel); - if (newBunyanLogLevel === logger.level()) { - return Promise.resolve(); - } - - logger.level(newBunyanLogLevel); - - const changedEvent: ILogLevelChangedEvent = { - loggerName: name, - newLogLevel: newLogLevel, - }; - - /* Notify the frontend. */ - if (this.client !== undefined) { - this.client.onLogLevelChanged(changedEvent); - } - - /* Notify the backend. */ - this.watcher.fireLogLevelChanged(changedEvent); - - return Promise.resolve(); - } - - /* Get the log level for a logger. */ - getLogLevel(name: string): Promise { - const logger = this.loggers.get(name); - if (logger === undefined) { - throw new Error(`No logger named ${name}.`); - } - - return Promise.resolve( - this.toTheiaLevel(logger.level()) - ); - } - - /* Log a message to a logger. */ - // tslint:disable-next-line:no-any - log(name: string, logLevel: number, message: any, params: any[]): Promise { - const logger = this.loggers.get(name); - if (logger === undefined) { - throw new Error(`No logger named ${name}.`); - } - - switch (logLevel) { - case LogLevel.TRACE: - logger.trace(message, params); - break; - case LogLevel.DEBUG: - logger.debug(message, params); - break; - case LogLevel.INFO: - logger.info(message, params); - break; - case LogLevel.WARN: - logger.warn(message, params); - break; - case LogLevel.ERROR: - logger.error(message, params); - break; - case LogLevel.FATAL: - logger.fatal(message, params); - break; - default: - logger.info(message, params); - break; - } - return Promise.resolve(); - } - - /* Convert Theia's log levels to bunyan's. */ - protected toBunyanLevel(logLevel: number): number { - switch (logLevel) { - case LogLevel.FATAL: - return bunyan.FATAL; - case LogLevel.ERROR: - return bunyan.ERROR; - case LogLevel.WARN: - return bunyan.WARN; - case LogLevel.INFO: - return bunyan.INFO; - case LogLevel.DEBUG: - return bunyan.DEBUG; - case LogLevel.TRACE: - return bunyan.TRACE; - default: - return bunyan.INFO; - } - } - - /** - * Convert Bunyan's log levels to Theia's. - */ - protected toTheiaLevel(bunyanLogLevel: number | string): number { - switch (Number(bunyanLogLevel)) { - case bunyan.FATAL: - return LogLevel.FATAL; - case bunyan.ERROR: - return LogLevel.ERROR; - case bunyan.WARN: - return LogLevel.WARN; - case bunyan.INFO: - return LogLevel.INFO; - case bunyan.DEBUG: - return LogLevel.DEBUG; - case bunyan.TRACE: - return LogLevel.TRACE; - default: - return LogLevel.INFO; - } - } - -} diff --git a/packages/core/package.json b/packages/core/package.json index 8e9c52f296d5c..ebb8c667fb206 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -10,7 +10,6 @@ "@primer/octicons-react": "^9.0.0", "@theia/application-package": "^0.13.0", "@types/body-parser": "^1.16.4", - "@types/bunyan": "^1.8.0", "@types/express": "^4.16.0", "@types/fs-extra": "^4.0.2", "@types/lodash.debounce": "4.0.3", diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index ea2a975fc0268..f9780737fa620 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -612,7 +612,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi }, { command: CommonCommands.CLOSE_ALL_TABS.id, - keybinding: 'alt+shift+w' + keybinding: this.isElectron() ? 'ctrlCmd+k ctrlCmd+w' : 'alt+shift+w' }, // Panels { diff --git a/packages/core/src/browser/dialogs.ts b/packages/core/src/browser/dialogs.ts index 686808cd87544..4822837ca6091 100644 --- a/packages/core/src/browser/dialogs.ts +++ b/packages/core/src/browser/dialogs.ts @@ -243,7 +243,7 @@ export abstract class AbstractDialog extends BaseWidget { close(): void { if (this.resolve) { if (this.activeElement) { - this.activeElement.focus(); + this.activeElement.focus({ preventScroll: true }); } this.resolve(undefined); } diff --git a/packages/core/src/browser/frontend-application.ts b/packages/core/src/browser/frontend-application.ts index 480f5657d149f..d8db377935205 100644 --- a/packages/core/src/browser/frontend-application.ts +++ b/packages/core/src/browser/frontend-application.ts @@ -157,17 +157,42 @@ export class FrontendApplication { return startupElements.length === 0 ? undefined : startupElements[0] as HTMLElement; } + /* vvv HOTFIX begin vvv + * + * This is a hotfix against issues eclipse/theia#6459 and gitpod-io/gitpod#875 . + * It should be reverted after Theia was updated to the newer Monaco. + */ + protected inComposition = false; + /** + * Register composition related event listeners. + */ + protected registerComositionEventListeners(): void { + window.document.addEventListener('compositionstart', event => { + this.inComposition = true; + }); + window.document.addEventListener('compositionend', event => { + this.inComposition = false; + }); + } + /* ^^^ HOTFIX end ^^^ */ + /** * Register global event listeners. */ protected registerEventListeners(): void { + this.registerComositionEventListeners(); /* Hotfix. See above. */ + window.addEventListener('beforeunload', () => { this.stateService.state = 'closing_window'; this.layoutRestorer.storeLayout(this); this.stopContributions(); }); window.addEventListener('resize', () => this.shell.update()); - document.addEventListener('keydown', event => this.keybindings.run(event), true); + document.addEventListener('keydown', event => { + if (this.inComposition !== true) { + this.keybindings.run(event); + } + }, true); document.addEventListener('touchmove', event => { event.preventDefault(); }, { passive: false }); // Prevent forward/back navigation by scrolling in OS X if (isOSX) { diff --git a/packages/core/src/browser/http-open-handler.ts b/packages/core/src/browser/http-open-handler.ts index 875c64b047aeb..d21ec019e308c 100644 --- a/packages/core/src/browser/http-open-handler.ts +++ b/packages/core/src/browser/http-open-handler.ts @@ -35,7 +35,7 @@ export class HttpOpenHandler implements OpenHandler { return (uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? 500 : 0; } - async open(uri: URI): Promise { + async open(uri: URI): Promise { const resolvedUri = await this.externalUriService.resolve(uri); return this.windowService.openNewWindow(resolvedUri.toString(true), { external: true }); } diff --git a/packages/core/src/browser/tree/test/tree-test-container.ts b/packages/core/src/browser/tree/test/tree-test-container.ts new file mode 100644 index 0000000000000..bcb44d56027bd --- /dev/null +++ b/packages/core/src/browser/tree/test/tree-test-container.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { TreeImpl, Tree } from '../tree'; +import { TreeModel, TreeModelImpl } from '../tree-model'; +import { Container } from 'inversify'; +import { TreeSelectionServiceImpl } from '../tree-selection-impl'; +import { TreeSelectionService } from '../tree-selection'; +import { TreeExpansionServiceImpl, TreeExpansionService } from '../tree-expansion'; +import { TreeNavigationService } from '../tree-navigation'; +import { TreeSearch } from '../tree-search'; +import { FuzzySearch } from '../fuzzy-search'; +import { MockLogger } from '../../../common/test/mock-logger'; +import { ILogger } from '../../../common'; + +export function createTreeTestContainer(): Container { + const container = new Container({ defaultScope: 'Singleton' }); + container.bind(TreeImpl).toSelf(); + container.bind(Tree).toService(TreeImpl); + container.bind(TreeSelectionServiceImpl).toSelf(); + container.bind(TreeSelectionService).toService(TreeSelectionServiceImpl); + container.bind(TreeExpansionServiceImpl).toSelf(); + container.bind(TreeExpansionService).toService(TreeExpansionServiceImpl); + container.bind(TreeNavigationService).toSelf(); + container.bind(TreeModelImpl).toSelf(); + container.bind(TreeModel).toService(TreeModelImpl); + container.bind(TreeSearch).toSelf(); + container.bind(FuzzySearch).toSelf(); + container.bind(MockLogger).toSelf(); + container.bind(ILogger).to(MockLogger); + return container; +} diff --git a/packages/core/src/browser/tree/tree-consistency.spec.ts b/packages/core/src/browser/tree/tree-consistency.spec.ts new file mode 100644 index 0000000000000..c77e3ce4215da --- /dev/null +++ b/packages/core/src/browser/tree/tree-consistency.spec.ts @@ -0,0 +1,101 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import * as assert from 'assert'; +import { injectable } from 'inversify'; +import { createTreeTestContainer } from './test/tree-test-container'; +import { TreeImpl, CompositeTreeNode, TreeNode } from './tree'; +import { TreeModel } from './tree-model'; +import { ExpandableTreeNode } from './tree-expansion'; + +@injectable() +class ConsistencyTestTree extends TreeImpl { + + public resolveCounter = 0; + + protected async resolveChildren(parent: CompositeTreeNode): Promise { + if (parent.id === 'expandable') { + const step: () => Promise = async () => { + // a predicate to emulate bad timing, i.e. + // children of a node gets resolved when a root is changed + if (this.root && this.root !== parent.parent) { + this.resolveCounter++; + return []; + } else { + await new Promise(resolve => setTimeout(resolve, 10)); + return step(); + } + }; + return step(); + } + return super.resolveChildren(parent); + } + +} + +/** + * Return roots having the same id, but not object identity. + */ +function createConsistencyTestRoot(rootName: string): CompositeTreeNode { + const children: TreeNode[] = []; + const root: CompositeTreeNode = { + id: 'root', + name: rootName, + parent: undefined, + children + }; + const parent: ExpandableTreeNode = { + id: 'expandable', + name: 'expandable', + parent: root, + expanded: true, + children: [] + }; + children.push(parent); + return root; +} + +describe('Tree Consistency', () => { + + it('setting different tree roots should finish', async () => { + const container = createTreeTestContainer(); + container.bind(ConsistencyTestTree).toSelf(); + container.rebind(TreeImpl).toService(ConsistencyTestTree); + const tree = container.get(ConsistencyTestTree); + + const model = container.get(TreeModel); + + model.root = createConsistencyTestRoot('Foo'); + await new Promise(resolve => setTimeout(resolve, 50)); + + model.root = createConsistencyTestRoot('Bar'); + await new Promise(resolve => setTimeout(resolve, 50)); + + let resolveCounter = tree.resolveCounter; + assert.deepStrictEqual(tree.resolveCounter, 1); + for (let i = 0; i < 10; i++) { + await new Promise(resolve => setTimeout(resolve, 50)); + if (resolveCounter === tree.resolveCounter) { + assert.deepStrictEqual(tree.resolveCounter, 1); + assert.deepStrictEqual(model.root!.name, 'Bar'); + return; + } + resolveCounter = tree.resolveCounter; + } + assert.ok(false, 'Resolving does not stop, attempts: ' + tree.resolveCounter); + }); + +}); diff --git a/packages/core/src/browser/tree/tree-expansion.spec.ts b/packages/core/src/browser/tree/tree-expansion.spec.ts index 103e1c6939a33..fb48441aa2bac 100644 --- a/packages/core/src/browser/tree/tree-expansion.spec.ts +++ b/packages/core/src/browser/tree/tree-expansion.spec.ts @@ -16,143 +16,123 @@ import { expect } from 'chai'; import { MockTreeModel } from './test/mock-tree-model'; -import { TreeModelImpl, TreeModel } from './tree-model'; -import { TreeImpl, Tree, TreeNode, CompositeTreeNode } from './tree'; -import { Container } from 'inversify'; -import { TreeSelectionServiceImpl } from './tree-selection-impl'; -import { TreeSelectionService } from './tree-selection'; -import { TreeExpansionServiceImpl, TreeExpansionService, ExpandableTreeNode } from './tree-expansion'; -import { TreeNavigationService } from './tree-navigation'; -import { TreeSearch } from './tree-search'; -import { FuzzySearch } from './fuzzy-search'; -import { MockLogger } from '../../common/test/mock-logger'; -import { ILogger } from '../../common'; +import { TreeModel } from './tree-model'; +import { TreeNode, CompositeTreeNode } from './tree'; +import { ExpandableTreeNode } from './tree-expansion'; +import { createTreeTestContainer } from './test/tree-test-container'; // tslint:disable:no-unused-expression describe('TreeExpansionService', () => { - let model: TreeModel; - beforeEach(() => { - model = createTreeModel(); - model.root = MockTreeModel.HIERARCHICAL_MOCK_ROOT(); - }); - describe('expandNode', () => { - it('won\'t expand an already expanded node', done => { - const node: ExpandableTreeNode = retrieveNode('1'); - model.expandNode(node).then(result => { - expect(result).to.be.false; - done(); - }); + let model: TreeModel; + beforeEach(() => { + model = createTreeModel(); + model.root = MockTreeModel.HIERARCHICAL_MOCK_ROOT(); }); + describe('expandNode', () => { + it("won't expand an already expanded node", done => { + const node: ExpandableTreeNode = retrieveNode('1'); + model.expandNode(node).then(result => { + expect(result).to.be.undefined; + done(); + }); + }); - it('will expand a collapsed node', done => { - const node: ExpandableTreeNode = retrieveNode('1'); - model.collapseNode(node).then(() => { - model.expandNode(node).then(result => { - expect(result).to.be.true; - done(); + it('will expand a collapsed node', done => { + const node: ExpandableTreeNode = retrieveNode('1'); + model.collapseNode(node).then(() => { + model.expandNode(node).then(result => { + expect(result).to.be.eq(result); + done(); + }); + }); }); - }); - }); - it('won\'t expand an undefined node', done => { - model.expandNode(undefined).then(result => { - expect(result).to.be.false; - done(); - }); + it("won't expand an undefined node", done => { + model.expandNode(undefined).then(result => { + expect(result).to.be.undefined; + done(); + }); + }); }); - }); - describe('collapseNode', () => { - it('will collapse an expanded node', done => { - const node: ExpandableTreeNode = retrieveNode('1'); - model.collapseNode(node).then(result => { - expect(result).to.be.true; - done(); - }); - }); + describe('collapseNode', () => { + it('will collapse an expanded node', done => { + const node: ExpandableTreeNode = retrieveNode('1'); + model.collapseNode(node).then(result => { + expect(result).to.be.eq(result); + done(); + }); + }); - it('won\'t collapse an already collapsed node', done => { - const node: ExpandableTreeNode = retrieveNode('1'); - model.collapseNode(node).then(() => { - model.collapseNode(node).then(result => { - expect(result).to.be.false; - done(); + it("won't collapse an already collapsed node", done => { + const node: ExpandableTreeNode = retrieveNode('1'); + model.collapseNode(node).then(() => { + model.collapseNode(node).then(result => { + expect(result).to.be.false; + done(); + }); + }); }); - }); - }); - it('cannot collapse a leaf node', done => { - const node: ExpandableTreeNode = retrieveNode('1.1.2'); - model.collapseNode(node).then(result => { - expect(result).to.be.false; - done(); - }); + it('cannot collapse a leaf node', done => { + const node: ExpandableTreeNode = retrieveNode('1.1.2'); + model.collapseNode(node).then(result => { + expect(result).to.be.false; + done(); + }); + }); }); - }); - describe('collapseAll', () => { - it('will collapse all nodes recursively', done => { - model.collapseAll(retrieveNode('1')).then(result => { - expect(result).to.be.true; - done(); - }); - }); + describe('collapseAll', () => { + it('will collapse all nodes recursively', done => { + model.collapseAll(retrieveNode('1')).then(result => { + expect(result).to.be.eq(result); + done(); + }); + }); - it('won\'t collapse nodes recursively if the root node is collapsed already', done => { - model.collapseNode(retrieveNode('1')).then(() => { - model.collapseAll(retrieveNode('1')).then(result => { - expect(result).to.be.true; - done(); + it("won't collapse nodes recursively if the root node is collapsed already", done => { + model.collapseNode(retrieveNode('1')).then(() => { + model.collapseAll(retrieveNode('1')).then(result => { + expect(result).to.be.eq(result); + done(); + }); + }); }); - }); }); - }); - describe('toggleNodeExpansion', () => { - it('changes the expansion state from expanded to collapsed', done => { - const node = retrieveNode('1'); - model.onExpansionChanged((e: Readonly) => { - expect(e).to.be.equal(node); - expect(e.expanded).to.be.false; - }); - model.toggleNodeExpansion(node).then(() => { - done(); - }); - }); + describe('toggleNodeExpansion', () => { + it('changes the expansion state from expanded to collapsed', done => { + const node = retrieveNode('1'); + model.onExpansionChanged((e: Readonly) => { + expect(e).to.be.equal(node); + expect(e.expanded).to.be.false; + }); + model.toggleNodeExpansion(node).then(() => { + done(); + }); + }); - it('changes the expansion state from collapsed to expanded', done => { - const node = retrieveNode('1'); - model.collapseNode(node).then(() => { - }); - model.onExpansionChanged((e: Readonly) => { - expect(e).to.be.equal(node); - expect(e.expanded).to.be.true; - }); - model.toggleNodeExpansion(node).then(() => { - done(); - }); + it('changes the expansion state from collapsed to expanded', done => { + const node = retrieveNode('1'); + model.collapseNode(node).then(() => { + }); + model.onExpansionChanged((e: Readonly) => { + expect(e).to.be.equal(node); + expect(e.expanded).to.be.true; + }); + model.toggleNodeExpansion(node).then(() => { + done(); + }); + }); }); - }); - function createTreeModel(): TreeModel { - const container = new Container({ defaultScope: 'Singleton' }); - container.bind(TreeImpl).toSelf(); - container.bind(Tree).toService(TreeImpl); - container.bind(TreeSelectionServiceImpl).toSelf(); - container.bind(TreeSelectionService).toService(TreeSelectionServiceImpl); - container.bind(TreeExpansionServiceImpl).toSelf(); - container.bind(TreeExpansionService).toService(TreeExpansionServiceImpl); - container.bind(TreeNavigationService).toSelf(); - container.bind(TreeModelImpl).toSelf(); - container.bind(TreeModel).toService(TreeModelImpl); - container.bind(TreeSearch).toSelf(); - container.bind(FuzzySearch).toSelf(); - container.bind(MockLogger).toSelf(); - container.bind(ILogger).to(MockLogger).inSingletonScope(); - return container.get(TreeModel); - } - function retrieveNode(id: string): Readonly { - const readonlyNode: Readonly = model.getNode(id) as T; - return readonlyNode; - } + function createTreeModel(): TreeModel { + const container = createTreeTestContainer(); + return container.get(TreeModel); + } + function retrieveNode(id: string): Readonly { + const readonlyNode: Readonly = model.getNode(id) as T; + return readonlyNode; + } }); diff --git a/packages/core/src/browser/tree/tree-expansion.ts b/packages/core/src/browser/tree/tree-expansion.ts index c1e2de27fcb94..472d109853f04 100644 --- a/packages/core/src/browser/tree/tree-expansion.ts +++ b/packages/core/src/browser/tree/tree-expansion.ts @@ -29,12 +29,12 @@ export interface TreeExpansionService extends Disposable { */ readonly onExpansionChanged: Event>; /** - * If the given node is valid and collapsed then expand it. + * Expand a node for the given node id if it is valid and collapsed. * Expanding a node refreshes all its children. * - * Return true if a node has been expanded; otherwise false. + * Return a valid expanded refreshed node or `undefined` if such does not exist. */ - expandNode(node: Readonly): Promise; + expandNode(node: Readonly): Promise | undefined>; /** * If the given node is valid and expanded then collapse it. * @@ -107,19 +107,22 @@ export class TreeExpansionServiceImpl implements TreeExpansionService { this.onExpansionChangedEmitter.fire(node); } - async expandNode(raw: ExpandableTreeNode): Promise { + async expandNode(raw: ExpandableTreeNode): Promise { const node = this.tree.validateNode(raw); if (ExpandableTreeNode.isCollapsed(node)) { - return await this.doExpandNode(node); + return this.doExpandNode(node); } - return false; + return undefined; } - protected async doExpandNode(node: ExpandableTreeNode): Promise { + protected async doExpandNode(node: ExpandableTreeNode): Promise { node.expanded = true; - await this.tree.refresh(node); - this.fireExpansionChanged(node); - return true; + const refreshed = await this.tree.refresh(node); + if (ExpandableTreeNode.isExpanded(refreshed)) { + this.fireExpansionChanged(refreshed); + return refreshed; + } + return undefined; } async collapseNode(raw: ExpandableTreeNode): Promise { diff --git a/packages/core/src/browser/tree/tree-model.ts b/packages/core/src/browser/tree/tree-model.ts index 0553fdf865ebc..5df2d152ce8cc 100644 --- a/packages/core/src/browser/tree/tree-model.ts +++ b/packages/core/src/browser/tree/tree-model.ts @@ -33,7 +33,7 @@ export interface TreeModel extends Tree, TreeSelectionService, TreeExpansionServ * Expands the given node. If the `node` argument is `undefined`, then expands the currently selected tree node. * If multiple tree nodes are selected, expands the most recently selected tree node. */ - expandNode(node?: Readonly): Promise; + expandNode(node?: Readonly): Promise | undefined>; /** * Collapses the given node. If the `node` argument is `undefined`, then collapses the currently selected tree node. @@ -216,12 +216,11 @@ export class TreeModelImpl implements TreeModel, SelectionProvider): Promise { + async refresh(parent?: Readonly): Promise { if (parent) { - await this.tree.refresh(parent); - } else { - await this.tree.refresh(); + return this.tree.refresh(parent); } + return this.tree.refresh(); } // tslint:disable-next-line:typedef @@ -238,19 +237,19 @@ export class TreeModelImpl implements TreeModel, SelectionProvider): Promise { + async expandNode(raw?: Readonly): Promise { for (const node of raw ? [raw] : this.selectedNodes) { if (ExpandableTreeNode.is(node)) { - return await this.expansionService.expandNode(node); + return this.expansionService.expandNode(node); } } - return false; + return undefined; } async collapseNode(raw?: Readonly): Promise { for (const node of raw ? [raw] : this.selectedNodes) { if (ExpandableTreeNode.is(node)) { - return await this.expansionService.collapseNode(node); + return this.expansionService.collapseNode(node); } } return false; @@ -262,7 +261,7 @@ export class TreeModelImpl implements TreeModel, SelectionProvider { @@ -230,7 +222,7 @@ describe('Tree', () => { function assertTreeNode(expectation: string, node: TreeNode): void { // tslint:disable-next-line:no-any - assert.deepEqual(expectation, JSON.stringify(node, (key: keyof CompositeTreeNode, value: any) => { + assert.deepStrictEqual(expectation, JSON.stringify(node, (key: keyof CompositeTreeNode, value: any) => { if (key === 'parent' || key === 'previousSibling' || key === 'nextSibling') { return value && value.id; } @@ -239,20 +231,7 @@ describe('Tree', () => { } function createTreeModel(): TreeModel { - const container = new Container({ defaultScope: 'Singleton' }); - container.bind(TreeImpl).toSelf(); - container.bind(Tree).toService(TreeImpl); - container.bind(TreeSelectionServiceImpl).toSelf(); - container.bind(TreeSelectionService).toService(TreeSelectionServiceImpl); - container.bind(TreeExpansionServiceImpl).toSelf(); - container.bind(TreeExpansionService).toService(TreeExpansionServiceImpl); - container.bind(TreeNavigationService).toSelf(); - container.bind(TreeModelImpl).toSelf(); - container.bind(TreeModel).toService(TreeModelImpl); - container.bind(TreeSearch).toSelf(); - container.bind(FuzzySearch).toSelf(); - container.bind(MockLogger).toSelf(); - container.bind(ILogger).to(MockLogger).inSingletonScope(); + const container = createTreeTestContainer(); return container.get(TreeModel); } function retrieveNode(id: string): Readonly { diff --git a/packages/core/src/browser/tree/tree.ts b/packages/core/src/browser/tree/tree.ts index 0c7045d667d20..51e5dec302005 100644 --- a/packages/core/src/browser/tree/tree.ts +++ b/packages/core/src/browser/tree/tree.ts @@ -43,12 +43,16 @@ export interface Tree extends Disposable { validateNode(node: TreeNode | undefined): TreeNode | undefined; /** * Refresh children of the root node. + * + * Return a valid refreshed composite root or `undefined` if such does not exist. */ - refresh(): Promise; + refresh(): Promise | undefined>; /** - * Refresh children of the given node if it is valid. + * Refresh children of a node for the give node id if it is valid. + * + * Return a valid refreshed composite node or `undefined` if such does not exist. */ - refresh(parent: Readonly): Promise; + refresh(parent: Readonly): Promise | undefined>; /** * Emit when the children of the given node are refreshed. */ @@ -256,26 +260,33 @@ export class TreeImpl implements Tree { return this.getNode(id); } - async refresh(raw?: CompositeTreeNode): Promise { + async refresh(raw?: CompositeTreeNode): Promise { const parent = !raw ? this._root : this.validateNode(raw); + let result: CompositeTreeNode | undefined; if (CompositeTreeNode.is(parent)) { + result = parent; const children = await this.resolveChildren(parent); - await this.setChildren(parent, children); + result = await this.setChildren(parent, children); } - // FIXME: it should not be here - // if the idea was to support refreshing of all kind of nodes, then API should be adapted this.fireChanged(); + return result; } protected resolveChildren(parent: CompositeTreeNode): Promise { return Promise.resolve(Array.from(parent.children)); } - protected async setChildren(parent: CompositeTreeNode, children: TreeNode[]): Promise { + protected async setChildren(parent: CompositeTreeNode, children: TreeNode[]): Promise { + const root = this.getRootNode(parent); + if (this.nodes[root.id] && this.nodes[root.id] !== root) { + console.error(`Child node '${parent.id}' does not belong to this '${root.id}' tree.`); + return undefined; + } this.removeNode(parent); parent.children = children; this.addNode(parent); await this.fireNodeRefreshed(parent); + return parent; } protected removeNode(node: TreeNode | undefined): void { @@ -297,12 +308,6 @@ export class TreeImpl implements Tree { protected addNode(node: TreeNode | undefined): void { if (node) { - const root = this.getRootNode(node); - if (this.nodes[root.id] && this.nodes[root.id] !== root) { - console.debug('Child node does not belong to this tree. Resetting root.'); - this.root = root; - return; - } this.nodes[node.id] = node; } if (CompositeTreeNode.is(node)) { diff --git a/packages/core/src/browser/window/default-window-service.ts b/packages/core/src/browser/window/default-window-service.ts index c03ed64e1fdbc..a2948fb7a351d 100644 --- a/packages/core/src/browser/window/default-window-service.ts +++ b/packages/core/src/browser/window/default-window-service.ts @@ -41,12 +41,9 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC }); } - openNewWindow(url: string): Window | undefined { - const newWindow = window.open(url); - if (newWindow === null) { - throw new Error('Cannot open a new window for URL: ' + url); - } - return newWindow; + openNewWindow(url: string): undefined { + window.open(url, undefined, 'noopener'); + return undefined; } canUnload(): boolean { diff --git a/packages/core/src/browser/window/window-service.ts b/packages/core/src/browser/window/window-service.ts index b0bcbd8bb48e0..1300b5af249c0 100644 --- a/packages/core/src/browser/window/window-service.ts +++ b/packages/core/src/browser/window/window-service.ts @@ -29,7 +29,7 @@ export interface WindowService { * In a browser, opening a new Theia tab or open a link is the same thing. * But in Electron, we want to open links in a browser, not in Electron. */ - openNewWindow(url: string, options?: NewWindowOptions): Window | undefined; + openNewWindow(url: string, options?: NewWindowOptions): undefined; /** * Called when the `window` is about to `unload` its resources. diff --git a/packages/core/src/common/command.ts b/packages/core/src/common/command.ts index 3ad6eb3fc2188..f6482490029d5 100644 --- a/packages/core/src/common/command.ts +++ b/packages/core/src/common/command.ts @@ -281,7 +281,7 @@ export class CommandRegistry implements CommandService { return result; } const argsMessage = args && args.length > 0 ? ` (args: ${JSON.stringify(args)})` : ''; - throw new Error(`The command '${commandId}' cannot be executed. There are no active handlers available for the command.${argsMessage}`); + throw Object.assign(new Error(`The command '${commandId}' cannot be executed. There are no active handlers available for the command.${argsMessage}`), { code: 'NO_ACTIVE_HANDLER' }); } protected async fireWillExecuteCommand(commandId: string): Promise { diff --git a/packages/core/src/common/messaging/web-socket-channel.ts b/packages/core/src/common/messaging/web-socket-channel.ts index cc581db798922..5fa7d3a3c2997 100644 --- a/packages/core/src/common/messaging/web-socket-channel.ts +++ b/packages/core/src/common/messaging/web-socket-channel.ts @@ -79,6 +79,10 @@ export class WebSocketChannel implements IWebSocket { } close(code: number = 1000, reason: string = ''): void { + if (this.closing) { + // Do not try to close the channel if it is already closing. + return; + } this.checkNotDisposed(); this.doSend(JSON.stringify({ kind: 'close', diff --git a/packages/bunyan/src/package.spec.ts b/packages/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts similarity index 53% rename from packages/bunyan/src/package.spec.ts rename to packages/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts index 88c14dc79022e..6f8db65b589db 100644 --- a/packages/bunyan/src/package.spec.ts +++ b/packages/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2018 TypeFox and others. + * Copyright (C) 2019 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,8 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -describe('bunyan package', () => { - - it('support code coverage statistics', () => true); +import { ContainerModule } from 'inversify'; +import { FrontendApplicationContribution } from '../../browser/frontend-application'; +import { WebSocketConnectionProvider } from '../../browser/messaging/ws-connection-provider'; +import { ElectronWebSocketConnectionProvider } from './electron-ws-connection-provider'; +export const messagingFrontendModule = new ContainerModule(bind => { + bind(ElectronWebSocketConnectionProvider).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(ElectronWebSocketConnectionProvider); + bind(WebSocketConnectionProvider).toService(ElectronWebSocketConnectionProvider); }); diff --git a/packages/core/src/electron-browser/messaging/electron-ws-connection-provider.ts b/packages/core/src/electron-browser/messaging/electron-ws-connection-provider.ts new file mode 100644 index 0000000000000..2bba1570874c3 --- /dev/null +++ b/packages/core/src/electron-browser/messaging/electron-ws-connection-provider.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { WebSocketChannel } from '../../common/messaging/web-socket-channel'; +import { WebSocketConnectionProvider, WebSocketOptions } from '../../browser/messaging/ws-connection-provider'; +import { FrontendApplicationContribution } from '../../browser/frontend-application'; + +@injectable() +export class ElectronWebSocketConnectionProvider extends WebSocketConnectionProvider implements FrontendApplicationContribution { + + /** + * Do not try to reconnect when the frontend application is stopping. The browser is navigating away from this page. + */ + protected stopping = false; + + onStop(): void { + this.stopping = true; + // Close the websocket connection `onStop`. Otherwise, the channels will be closed with 30 sec (`MessagingContribution#checkAliveTimeout`) delay. + // https://github.com/eclipse-theia/theia/issues/6499 + for (const channel of [...this.channels.values()]) { + // `1001` indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page. + // But we cannot use `1001`: https://github.com/TypeFox/vscode-ws-jsonrpc/issues/15 + channel.close(1000, 'The frontend is "going away"...'); + } + } + + openChannel(path: string, handler: (channel: WebSocketChannel) => void, options?: WebSocketOptions): void { + if (!this.stopping) { + super.openChannel(path, handler, options); + } + } + +} diff --git a/packages/core/src/node/messaging/messaging-contribution.ts b/packages/core/src/node/messaging/messaging-contribution.ts index 1ab7cbe683f0d..b85167fd3c8c4 100644 --- a/packages/core/src/node/messaging/messaging-contribution.ts +++ b/packages/core/src/node/messaging/messaging-contribution.ts @@ -127,8 +127,12 @@ export class MessagingContribution implements BackendApplicationContribution, Me const channel = this.createChannel(id, socket); if (channelHandlers.route(path, channel)) { channel.ready(); + console.debug(`Opening channel for service path '${path}'. [ID: ${id}]`); channels.set(id, channel); - channel.onClose(() => channels.delete(id)); + channel.onClose(() => { + console.debug(`Closing channel on service path '${path}'. [ID: ${id}]`); + channels.delete(id); + }); } else { console.error('Cannot find a service for the path: ' + path); } diff --git a/packages/debug/src/browser/debug-frontend-application-contribution.ts b/packages/debug/src/browser/debug-frontend-application-contribution.ts index 3b50e8ef6a2bb..91e58b29bb0c2 100644 --- a/packages/debug/src/browser/debug-frontend-application-contribution.ts +++ b/packages/debug/src/browser/debug-frontend-application-contribution.ts @@ -205,12 +205,12 @@ export namespace DebugCommands { category: DEBUG_CATEGORY, label: 'Set Value', }; - export const COPY_VAIRABLE_VALUE: Command = { + export const COPY_VARIABLE_VALUE: Command = { id: 'debug.variable.copyValue', category: DEBUG_CATEGORY, label: 'Copy Value', }; - export const COPY_VAIRABLE_AS_EXPRESSION: Command = { + export const COPY_VARIABLE_AS_EXPRESSION: Command = { id: 'debug.variable.copyAsExpression', category: DEBUG_CATEGORY, label: 'Copy As Expression', @@ -477,8 +477,8 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi registerMenuActions(DebugVariablesWidget.CONTEXT_MENU, DebugCommands.SET_VARIABLE_VALUE, - DebugCommands.COPY_VAIRABLE_VALUE, - DebugCommands.COPY_VAIRABLE_AS_EXPRESSION + DebugCommands.COPY_VARIABLE_VALUE, + DebugCommands.COPY_VARIABLE_AS_EXPRESSION ); registerMenuActions(DebugBreakpointsWidget.EDIT_MENU, @@ -737,12 +737,12 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi isEnabled: () => !!this.selectedVariable && this.selectedVariable.supportSetVariable, isVisible: () => !!this.selectedVariable && this.selectedVariable.supportSetVariable }); - registry.registerCommand(DebugCommands.COPY_VAIRABLE_VALUE, { + registry.registerCommand(DebugCommands.COPY_VARIABLE_VALUE, { execute: () => this.selectedVariable && this.selectedVariable.copyValue(), isEnabled: () => !!this.selectedVariable && this.selectedVariable.supportCopyValue, isVisible: () => !!this.selectedVariable && this.selectedVariable.supportCopyValue }); - registry.registerCommand(DebugCommands.COPY_VAIRABLE_AS_EXPRESSION, { + registry.registerCommand(DebugCommands.COPY_VARIABLE_AS_EXPRESSION, { execute: () => this.selectedVariable && this.selectedVariable.copyAsExpression(), isEnabled: () => !!this.selectedVariable && this.selectedVariable.supportCopyAsExpression, isVisible: () => !!this.selectedVariable && this.selectedVariable.supportCopyAsExpression diff --git a/packages/debug/src/browser/preferences/launch-preferences.spec.ts b/packages/debug/src/browser/preferences/launch-preferences.spec.ts index 5b884904bdb50..86e82821f52e9 100644 --- a/packages/debug/src/browser/preferences/launch-preferences.spec.ts +++ b/packages/debug/src/browser/preferences/launch-preferences.spec.ts @@ -489,7 +489,7 @@ describe('Launch Preferences', () => { assert.deepStrictEqual(JSON.parse(JSON.stringify(config)), expectation); }); - testIt('get from undefind', () => { + testIt('get from undefined', () => { const config = preferences.get('launch', undefined, undefined); assert.deepStrictEqual(JSON.parse(JSON.stringify(config)), expectation); }); diff --git a/packages/debug/src/browser/view/debug-threads-source.tsx b/packages/debug/src/browser/view/debug-threads-source.tsx index 90dff69801be6..6a227ccbad413 100644 --- a/packages/debug/src/browser/view/debug-threads-source.tsx +++ b/packages/debug/src/browser/view/debug-threads-source.tsx @@ -36,7 +36,7 @@ export class DebugThreadsSource extends TreeSource { this.toDispose.push(this.model.onDidChange(() => this.fireDidChange())); } - get multiSesssion(): boolean { + get multiSession(): boolean { return this.model.sessionCount > 1; } diff --git a/packages/debug/src/browser/view/debug-threads-widget.ts b/packages/debug/src/browser/view/debug-threads-widget.ts index 20b6342836520..39db57685b357 100644 --- a/packages/debug/src/browser/view/debug-threads-widget.ts +++ b/packages/debug/src/browser/view/debug-threads-widget.ts @@ -114,7 +114,7 @@ export class DebugThreadsWidget extends SourceTreeWidget { } protected getDefaultNodeStyle(node: TreeNode, props: NodeProps): React.CSSProperties | undefined { - if (this.threads.multiSesssion) { + if (this.threads.multiSession) { return super.getDefaultNodeStyle(node, props); } return undefined; diff --git a/packages/debug/src/common/debug-model.ts b/packages/debug/src/common/debug-model.ts index b631eab74dca5..42773917ef96f 100644 --- a/packages/debug/src/common/debug-model.ts +++ b/packages/debug/src/common/debug-model.ts @@ -29,7 +29,7 @@ import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-sch import { Disposable } from '@theia/core/lib/common/disposable'; import { MaybePromise } from '@theia/core/lib/common/types'; -// FXIME: break down this file to debug adapter and debug adapter contribution (see Theia file naming conventions) +// FIXME: break down this file to debug adapter and debug adapter contribution (see Theia file naming conventions) /** * DebugAdapterSession symbol for DI. diff --git a/packages/languages/package.json b/packages/languages/package.json index 124b4eda18ac7..e1c76a723a4d3 100644 --- a/packages/languages/package.json +++ b/packages/languages/package.json @@ -3,6 +3,7 @@ "version": "0.13.0", "description": "Theia - Languages Extension", "dependencies": { + "@theia/application-package": "^0.13.0", "@theia/core": "^0.13.0", "@theia/output": "^0.13.0", "@theia/process": "^0.13.0", diff --git a/packages/languages/src/browser/workspace-symbols.ts b/packages/languages/src/browser/workspace-symbols.ts index aa0adcd5d3433..ffcced29e75ee 100644 --- a/packages/languages/src/browser/workspace-symbols.ts +++ b/packages/languages/src/browser/workspace-symbols.ts @@ -15,6 +15,7 @@ ********************************************************************************/ import { injectable, inject } from 'inversify'; +import { environment } from '@theia/application-package/lib/environment'; import { PrefixQuickOpenService, QuickOpenModel, QuickOpenItem, OpenerService, QuickOpenMode, KeybindingContribution, KeybindingRegistry, QuickOpenHandler, QuickOpenOptions, QuickOpenContribution, QuickOpenHandlerRegistry @@ -67,10 +68,14 @@ export class WorkspaceSymbolCommand implements QuickOpenModel, CommandContributi commands.registerCommand(this.command, this); } + private isElectron(): boolean { + return environment.electron.is(); + } + registerKeybindings(keybindings: KeybindingRegistry): void { keybindings.registerKeybinding({ command: this.command.id, - keybinding: 'ctrlcmd+o', + keybinding: this.isElectron() ? 'ctrlcmd+t' : 'ctrlcmd+o', }); } diff --git a/packages/messages/src/browser/notifications-manager.ts b/packages/messages/src/browser/notifications-manager.ts index 59eda20b514d8..421c8c5c5f2c3 100644 --- a/packages/messages/src/browser/notifications-manager.ts +++ b/packages/messages/src/browser/notifications-manager.ts @@ -202,7 +202,8 @@ export class NotificationManager extends MessageClient { } } protected getTimeout(plainMessage: PlainMessage): number { - if (plainMessage.actions && !plainMessage.actions.length) { + if (plainMessage.actions && plainMessage.actions.length > 0) { + // Ignore the timeout if at least one action is set, and we wait for user interaction. return 0; } return plainMessage.options && plainMessage.options.timeout || this.preferences['notification.timeout']; diff --git a/packages/monaco/src/browser/monaco-editor.ts b/packages/monaco/src/browser/monaco-editor.ts index 91a5934286d5c..11fd880a200b0 100644 --- a/packages/monaco/src/browser/monaco-editor.ts +++ b/packages/monaco/src/browser/monaco-editor.ts @@ -322,6 +322,11 @@ export class MonacoEditor extends MonacoEditorServices implements TextEditor { this.toDispose.dispose(); } + // tslint:disable-next-line:no-any + trigger(source: string, handlerId: string, payload: any): void { + this.editor.trigger(source, handlerId, payload); + } + getControl(): IStandaloneCodeEditor { return this.editor; } diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index c71fe10fa5f73..4e5f2b07213c6 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -47,6 +47,7 @@ declare module monaco.editor { export interface IStandaloneCodeEditor extends CommonCodeEditor { setDecorations(decorationTypeKey: string, ranges: IDecorationOptions[]): void; setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void; + trigger(source: string, handlerId: string, payload: any): void } // https://github.com/TypeFox/vscode/blob/monaco/0.18.0/src/vs/editor/browser/widget/codeEditorWidget.ts#L107 diff --git a/packages/navigator/src/browser/navigator-model.spec.ts b/packages/navigator/src/browser/navigator-model.spec.ts index 62d3d6f2d8e74..1c39443ccf215 100644 --- a/packages/navigator/src/browser/navigator-model.spec.ts +++ b/packages/navigator/src/browser/navigator-model.spec.ts @@ -195,13 +195,6 @@ describe('FileNavigatorModel', () => { toRestore.length = 0; }); - it('should update the root(s) on receiving a WorkspaceChanged event from the WorkspaceService', done => { - sinon.stub(navigatorModel, 'updateRoot').callsFake(() => { - done(); // This test would time out if updateRoot() is not called - }); - mockWorkspaceServiceEmitter.fire([]); - }).timeout(2000); - describe('updateRoot() function', () => { it('should assign "this.root" a WorkspaceNode with WorkspaceRootNodes (one for each root folder in the workspace) as its children', async () => { sinon.stub(mockWorkspaceService, 'roots').value([folderA, folderB]); @@ -215,7 +208,7 @@ describe('FileNavigatorModel', () => { }) ); - await navigatorModel.updateRoot(); + await navigatorModel['updateRoot'](); const thisRoot = navigatorModel['root'] as WorkspaceNode; expect(thisRoot).not.to.be.undefined; expect(thisRoot.children.length).to.eq(2); @@ -226,7 +219,7 @@ describe('FileNavigatorModel', () => { it('should assign "this.root" undefined if there is no workspace open', async () => { sinon.stub(mockWorkspaceService, 'opened').value(false); - await navigatorModel.updateRoot(); + await navigatorModel['updateRoot'](); const thisRoot = navigatorModel['root'] as WorkspaceNode; expect(thisRoot).to.be.undefined; }); diff --git a/packages/navigator/src/browser/navigator-model.ts b/packages/navigator/src/browser/navigator-model.ts index df8b43db01175..d1e0f5b0ecb15 100644 --- a/packages/navigator/src/browser/navigator-model.ts +++ b/packages/navigator/src/browser/navigator-model.ts @@ -17,7 +17,7 @@ import { injectable, inject, postConstruct } from 'inversify'; import URI from '@theia/core/lib/common/uri'; import { FileNode, FileTreeModel } from '@theia/filesystem/lib/browser'; -import { OpenerService, open, TreeNode, ExpandableTreeNode } from '@theia/core/lib/browser'; +import { OpenerService, open, TreeNode, ExpandableTreeNode, CompositeTreeNode, SelectableTreeNode } from '@theia/core/lib/browser'; import { FileNavigatorTree, WorkspaceRootNode, WorkspaceNode } from './navigator-tree'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @@ -32,17 +32,32 @@ export class FileNavigatorModel extends FileTreeModel { @postConstruct() protected init(): void { - this.toDispose.push( - this.workspaceService.onWorkspaceChanged(event => { - this.updateRoot(); - }) - ); - this.toDispose.push( - this.workspaceService.onWorkspaceLocationChanged(() => { - this.updateRoot(); - }) - ); super.init(); + this.initializeRoot(); + } + + protected async initializeRoot(): Promise { + await Promise.all([ + this.applicationState.reachedState('initialized_layout'), + this.workspaceService.roots + ]); + await this.updateRoot(); + if (this.toDispose.disposed) { + return; + } + this.toDispose.push(this.workspaceService.onWorkspaceChanged(() => this.updateRoot())); + this.toDispose.push(this.workspaceService.onWorkspaceLocationChanged(() => this.updateRoot())); + if (this.selectedNodes.length) { + return; + } + const root = this.root; + if (CompositeTreeNode.is(root) && root.children.length === 1) { + const child = root.children[0]; + if (SelectableTreeNode.is(child) && !child.selected && ExpandableTreeNode.is(child)) { + this.selectNode(child); + this.expandNode(child); + } + } } previewNode(node: TreeNode): void { @@ -72,8 +87,7 @@ export class FileNavigatorModel extends FileTreeModel { } } - async updateRoot(): Promise { - await this.applicationState.reachedState('initialized_layout'); + protected async updateRoot(): Promise { this.root = await this.createRoot(); } @@ -130,14 +144,14 @@ export class FileNavigatorModel extends FileTreeModel { if (!uri.path.isAbsolute) { return undefined; } - let node = await this.getNodeClosestToRootByUri(uri); + let node = this.getNodeClosestToRootByUri(uri); // success stop condition // we have to reach workspace root because expanded node could be inside collapsed one if (WorkspaceRootNode.is(node)) { if (ExpandableTreeNode.is(node)) { if (!node.expanded) { - await this.expandNode(node); + node = await this.expandNode(node); } return node; } @@ -155,10 +169,10 @@ export class FileNavigatorModel extends FileTreeModel { if (await this.revealFile(uri.parent)) { if (node === undefined) { // get node if it wasn't mounted into navigator tree before expansion - node = await this.getNodeClosestToRootByUri(uri); + node = this.getNodeClosestToRootByUri(uri); } if (ExpandableTreeNode.is(node) && !node.expanded) { - await this.expandNode(node); + node = await this.expandNode(node); } return node; } diff --git a/packages/navigator/src/browser/navigator-widget.tsx b/packages/navigator/src/browser/navigator-widget.tsx index 686494017f0a6..9c04da1145523 100644 --- a/packages/navigator/src/browser/navigator-widget.tsx +++ b/packages/navigator/src/browser/navigator-widget.tsx @@ -21,8 +21,7 @@ import { CommandService, SelectionService } from '@theia/core/lib/common'; import { CommonCommands, CorePreferences, LabelProvider, ViewContainerTitleOptions, Key } from '@theia/core/lib/browser'; import { ContextMenuRenderer, ExpandableTreeNode, - TreeProps, TreeModel, TreeNode, - SelectableTreeNode, CompositeTreeNode + TreeProps, TreeModel, TreeNode } from '@theia/core/lib/browser'; import { FileTreeWidget, FileNode, DirNode } from '@theia/filesystem/lib/browser'; import { WorkspaceService, WorkspaceCommands } from '@theia/workspace/lib/browser'; @@ -68,7 +67,6 @@ export class FileNavigatorWidget extends FileTreeWidget { super(props, model, contextMenuRenderer); this.id = FILE_NAVIGATOR_ID; this.addClass(CLASS); - this.initialize(); } @postConstruct() @@ -91,21 +89,6 @@ export class FileNavigatorWidget extends FileTreeWidget { ]); } - protected async initialize(): Promise { - await this.model.updateRoot(); - if (this.model.selectedNodes.length) { - return; - } - const root = this.model.root; - if (CompositeTreeNode.is(root) && root.children.length === 1) { - const child = root.children[0]; - if (SelectableTreeNode.is(child) && !child.selected && ExpandableTreeNode.is(child)) { - this.model.selectNode(child); - this.model.expandNode(child); - } - } - } - protected doUpdateRows(): void { super.doUpdateRows(); this.title.label = LABEL; diff --git a/packages/plugin-ext-vscode/package.json b/packages/plugin-ext-vscode/package.json index 2677905d03010..17f5f8f96bf98 100644 --- a/packages/plugin-ext-vscode/package.json +++ b/packages/plugin-ext-vscode/package.json @@ -5,6 +5,7 @@ "dependencies": { "@theia/core": "^0.13.0", "@theia/editor": "^0.13.0", + "@theia/monaco": "^0.13.0", "@theia/plugin": "^0.13.0", "@theia/plugin-ext": "^0.13.0", "@theia/workspace": "^0.13.0", diff --git a/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts b/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts index 4acedb5bff7a8..b4295876bf244 100644 --- a/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts +++ b/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts @@ -36,6 +36,7 @@ import { fromViewColumn, toDocumentSymbol } from '@theia/plugin-ext/lib/plugin/t import { ViewColumn } from '@theia/plugin-ext/lib/plugin/types-impl'; import { WorkspaceCommands } from '@theia/workspace/lib/browser'; import { DiffService } from '@theia/workspace/lib/browser/diff-service'; +import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; import { inject, injectable } from 'inversify'; import URI from 'vscode-uri'; @@ -162,6 +163,14 @@ export class PluginVscodeCommandsContribution implements CommandContribution { commands.registerCommand({ id: 'workbench.action.quickOpen' }, { execute: () => this.quickOpen.open('') }); + commands.registerCommand({ id: 'default:type' }, { + execute: args => { + const editor = MonacoEditor.getCurrent(this.editorManager); + if (editor) { + editor.trigger('keyboard', 'type', args); + } + } + }); commands.registerCommand({ id: 'workbench.action.files.save', }, { execute: (uri?: monaco.Uri) => { if (uri) { diff --git a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts index ad47e1eabb6bc..a71ffafe33a65 100644 --- a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts +++ b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts @@ -52,15 +52,20 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca * Maps extension dependencies to deployable extension dependencies. */ getDependencies(plugin: PluginPackage): Map | undefined { - if (!plugin.extensionDependencies || !plugin.extensionDependencies.length) { - return undefined; - } + // Store the list of dependencies. const dependencies = new Map(); - for (const dependency of plugin.extensionDependencies) { - const dependencyId = dependency.toLowerCase(); - dependencies.set(dependencyId, this.VSCODE_PREFIX + dependencyId); + // Iterate through the list of dependencies from `extensionDependencies` and `extensionPack`. + for (const dependency of [plugin.extensionDependencies, plugin.extensionPack]) { + if (dependency !== undefined) { + // Iterate over the list of dependencies present, and add them to the collection. + dependency.forEach((dep: string) => { + const dependencyId = dep.toLowerCase(); + dependencies.set(dependencyId, this.VSCODE_PREFIX + dependencyId); + }); + } } - return dependencies; + // Return the map of dependencies if present, else `undefined`. + return dependencies.size > 0 ? dependencies : undefined ; } getLifecycle(plugin: PluginPackage): PluginLifecycle { diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index 73a3b65c1a6b4..c2acff87e9300 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -53,6 +53,7 @@ export interface PluginPackage { packagePath: string; activationEvents?: string[]; extensionDependencies?: string[]; + extensionPack?: string[]; } export namespace PluginPackage { export function toPluginUrl(pck: PluginPackage, relativePath: string): string { diff --git a/packages/plugin-ext/src/main/browser/command-registry-main.ts b/packages/plugin-ext/src/main/browser/command-registry-main.ts index 84c82f5a945d8..916f959c717d2 100644 --- a/packages/plugin-ext/src/main/browser/command-registry-main.ts +++ b/packages/plugin-ext/src/main/browser/command-registry-main.ts @@ -72,8 +72,20 @@ export class CommandRegistryMainImpl implements CommandRegistryMain, Disposable } // tslint:disable-next-line:no-any - $executeCommand(id: string, ...args: any[]): PromiseLike { - return this.delegate.executeCommand(id, ...args); + async $executeCommand(id: string, ...args: any[]): Promise { + if (!this.delegate.getCommand(id)) { + throw new Error(`Command with id '${id}' is not registered.`); + } + try { + return await this.delegate.executeCommand(id, ...args); + } catch (e) { + // Command handler may be not active at the moment so the error must be caught. See https://github.com/eclipse-theia/theia/pull/6687#discussion_r354810079 + if ('code' in e && e['code'] === 'NO_ACTIVE_HANDLER') { + return; + } else { + throw e; + } + } } $getKeyBinding(commandId: string): PromiseLike { diff --git a/packages/plugin-ext/src/main/browser/webview/webview.ts b/packages/plugin-ext/src/main/browser/webview/webview.ts index d0bb30e16c264..35a3e4cdaffcc 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview.ts @@ -235,7 +235,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget { const element = document.createElement('iframe'); element.className = 'webview'; - element.sandbox.add('allow-scripts', 'allow-same-origin'); + element.sandbox.add('allow-scripts', 'allow-forms', 'allow-same-origin'); element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.identifier.id}`); element.style.border = 'none'; element.style.width = '100%'; diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts index 8faa063529446..f8c4f51f9324a 100644 --- a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts +++ b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts @@ -18,7 +18,7 @@ import '../../src/browser/styles/index.css'; import { ContainerModule, interfaces } from 'inversify'; import { SearchInWorkspaceService, SearchInWorkspaceClientImpl } from './search-in-workspace-service'; -import { SearchInWorkspaceServer } from '../common/search-in-workspace-interface'; +import { SearchInWorkspaceServer, SIW_WS_PATH } from '../common/search-in-workspace-interface'; import { WebSocketConnectionProvider, WidgetFactory, createTreeContainer, TreeWidget, bindViewContribution, FrontendApplicationContribution } from '@theia/core/lib/browser'; import { ResourceResolver } from '@theia/core'; import { SearchInWorkspaceWidget } from './search-in-workspace-widget'; @@ -51,7 +51,7 @@ export default new ContainerModule(bind => { // The object to call methods on the backend. bind(SearchInWorkspaceServer).toDynamicValue(ctx => { const client = ctx.container.get(SearchInWorkspaceClientImpl); - return WebSocketConnectionProvider.createProxy(ctx.container, '/search-in-workspace', client); + return WebSocketConnectionProvider.createProxy(ctx.container, SIW_WS_PATH, client); }).inSingletonScope(); bind(InMemoryTextResourceResolver).toSelf().inSingletonScope(); diff --git a/packages/search-in-workspace/src/common/search-in-workspace-interface.ts b/packages/search-in-workspace/src/common/search-in-workspace-interface.ts index 0a0307f6e806a..2a07a130d19b3 100644 --- a/packages/search-in-workspace/src/common/search-in-workspace-interface.ts +++ b/packages/search-in-workspace/src/common/search-in-workspace-interface.ts @@ -116,6 +116,7 @@ export interface SearchInWorkspaceClient { onDone(searchId: number, error?: string): void; } +export const SIW_WS_PATH = '/services/search-in-workspace'; export const SearchInWorkspaceServer = Symbol('SearchInWorkspaceServer'); export interface SearchInWorkspaceServer extends JsonRpcServer { /** diff --git a/packages/search-in-workspace/src/node/search-in-workspace-backend-module.ts b/packages/search-in-workspace/src/node/search-in-workspace-backend-module.ts index 5bea019d7fed5..1898ad34feb88 100644 --- a/packages/search-in-workspace/src/node/search-in-workspace-backend-module.ts +++ b/packages/search-in-workspace/src/node/search-in-workspace-backend-module.ts @@ -16,20 +16,18 @@ import { ContainerModule } from 'inversify'; import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core/lib/common'; -import { SearchInWorkspaceServer, SearchInWorkspaceClient } from '../common/search-in-workspace-interface'; +import { SearchInWorkspaceServer, SearchInWorkspaceClient, SIW_WS_PATH } from '../common/search-in-workspace-interface'; import { RipgrepSearchInWorkspaceServer, RgPath } from './ripgrep-search-in-workspace-server'; import { rgPath } from 'vscode-ripgrep'; export default new ContainerModule(bind => { bind(SearchInWorkspaceServer).to(RipgrepSearchInWorkspaceServer); bind(ConnectionHandler).toDynamicValue(ctx => - new JsonRpcConnectionHandler - ('/search-in-workspace', client => { - const server = ctx.container.get(SearchInWorkspaceServer); - server.setClient(client); - client.onDidCloseConnection(() => server.dispose()); - return server; - }) - ); + new JsonRpcConnectionHandler(SIW_WS_PATH, client => { + const server = ctx.container.get(SearchInWorkspaceServer); + server.setClient(client); + client.onDidCloseConnection(() => server.dispose()); + return server; + })); bind(RgPath).toConstantValue(rgPath); }); diff --git a/packages/task/src/browser/provided-task-configurations.spec.ts b/packages/task/src/browser/provided-task-configurations.spec.ts index 151155bd3e0ca..c42eac3fbe435 100644 --- a/packages/task/src/browser/provided-task-configurations.spec.ts +++ b/packages/task/src/browser/provided-task-configurations.spec.ts @@ -38,7 +38,7 @@ describe('provided-task-configurations', () => { } }); - const task = await container.get(ProvidedTaskConfigurations).getTask('test', 'task from test'); + const task = await container.get(ProvidedTaskConfigurations).getTask('test', 'task from test', 'test'); assert.isOk(task); assert.equal(task!.type, 'test'); assert.equal(task!.label, 'task from test'); diff --git a/packages/task/src/browser/provided-task-configurations.ts b/packages/task/src/browser/provided-task-configurations.ts index cf40340081dc3..496d10220bd42 100644 --- a/packages/task/src/browser/provided-task-configurations.ts +++ b/packages/task/src/browser/provided-task-configurations.ts @@ -25,9 +25,10 @@ export class ProvidedTaskConfigurations { /** * Map of source (name of extension, or path of root folder that the task config comes from) and `task config map`. - * For the inner map (i.e., `task config map`), the key is task label and value TaskConfiguration + * For the second level of inner map, the key is task label. + * For the third level of inner map, the key is the task scope and value TaskConfiguration. */ - protected tasksMap = new Map>(); + protected tasksMap = new Map>>(); @inject(TaskProviderRegistry) protected readonly taskProviderRegistry: TaskProviderRegistry; @@ -45,13 +46,13 @@ export class ProvidedTaskConfigurations { } /** returns the task configuration for a given source and label or undefined if none */ - async getTask(source: string, taskLabel: string): Promise { - const task = this.getCachedTask(source, taskLabel); + async getTask(source: string, taskLabel: string, scope?: string): Promise { + const task = this.getCachedTask(source, taskLabel, scope); if (task) { return task; } else { await this.getTasks(); - return this.getCachedTask(source, taskLabel); + return this.getCachedTask(source, taskLabel, scope); } } @@ -100,10 +101,13 @@ export class ProvidedTaskConfigurations { return matchedTask; } - protected getCachedTask(source: string, taskLabel: string): TaskConfiguration | undefined { + protected getCachedTask(source: string, taskLabel: string, scope?: string): TaskConfiguration | undefined { const labelConfigMap = this.tasksMap.get(source); if (labelConfigMap) { - return labelConfigMap.get(taskLabel); + const scopeConfigMap = labelConfigMap.get(taskLabel); + if (scopeConfigMap) { + return scopeConfigMap.get(scope); + } } } @@ -111,12 +115,22 @@ export class ProvidedTaskConfigurations { for (const task of tasks) { const label = task.label; const source = task._source; + const scope = task._scope; if (this.tasksMap.has(source)) { - this.tasksMap.get(source)!.set(label, task); + const labelConfigMap = this.tasksMap.get(source)!; + if (labelConfigMap.has(label)) { + labelConfigMap.get(label)!.set(scope, task); + } else { + const newScopeConfigMap = new Map(); + newScopeConfigMap.set(scope, task); + labelConfigMap.set(label, newScopeConfigMap); + } } else { - const labelTaskMap = new Map(); - labelTaskMap.set(label, task); - this.tasksMap.set(source, labelTaskMap); + const newLabelConfigMap = new Map>(); + const newScopeConfigMap = new Map(); + newScopeConfigMap.set(scope, task); + newLabelConfigMap.set(label, newScopeConfigMap); + this.tasksMap.set(source, newLabelConfigMap); } } } diff --git a/packages/task/src/browser/quick-open-task.ts b/packages/task/src/browser/quick-open-task.ts index 8a4949d4e49e4..6d42684fdf6db 100644 --- a/packages/task/src/browser/quick-open-task.ts +++ b/packages/task/src/browser/quick-open-task.ts @@ -27,6 +27,7 @@ import { FileSystem } from '@theia/filesystem/lib/common'; import { QuickOpenModel, QuickOpenItem, QuickOpenActionProvider, QuickOpenMode, QuickOpenGroupItem, QuickOpenGroupItemOptions } from '@theia/core/lib/common/quick-open-model'; import { PreferenceService } from '@theia/core/lib/browser'; import { TaskNameResolver } from './task-name-resolver'; +import { TaskSourceResolver } from './task-source-resolver'; import { TaskConfigurationManager } from './task-configuration-manager'; @injectable() @@ -57,6 +58,9 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { @inject(TaskNameResolver) protected readonly taskNameResolver: TaskNameResolver; + @inject(TaskSourceResolver) + protected readonly taskSourceResolver: TaskSourceResolver; + @inject(FileSystem) protected readonly fileSystem: FileSystem; @@ -80,8 +84,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { const item = new TaskRunQuickOpenItem(task, this.taskService, isMulti, { groupLabel: index === 0 ? 'recently used tasks' : undefined, showBorder: false - }, this.taskNameResolver); - item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; + }, this.taskDefinitionRegistry, this.taskNameResolver, this.taskSourceResolver); return item; }), ...filteredConfiguredTasks.map((task, index) => { @@ -92,8 +95,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { ? false : index === 0 ? true : false ) - }, this.taskNameResolver); - item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; + }, this.taskDefinitionRegistry, this.taskNameResolver, this.taskSourceResolver); return item; }), ...filteredProvidedTasks.map((task, index) => { @@ -104,8 +106,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { ? false : index === 0 ? true : false ) - }, this.taskNameResolver); - item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; + }, this.taskDefinitionRegistry, this.taskNameResolver, this.taskSourceResolver); return item; }) ); @@ -281,10 +282,12 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { if (defaultBuildOrTestTasks.length === 1) { // run the default build / test task const defaultBuildOrTestTask = defaultBuildOrTestTasks[0]; const taskToRun = (defaultBuildOrTestTask as TaskRunQuickOpenItem).getTask(); + const scope = this.taskSourceResolver.resolve(taskToRun); + if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(taskToRun)) { - this.taskService.run(taskToRun.source, taskToRun.label); + this.taskService.run(taskToRun.source, taskToRun.label, scope); } else { - this.taskService.run(taskToRun._source, taskToRun.label); + this.taskService.run(taskToRun._source, taskToRun.label, scope); } return; } @@ -311,9 +314,10 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { item.options, this.taskNameResolver, shouldRunBuildTask, - this.taskConfigurationManager + this.taskConfigurationManager, + this.taskDefinitionRegistry, + this.taskSourceResolver ); - newItem['taskDefinitionRegistry'] = this.taskDefinitionRegistry; return newItem; }); this.quickOpenService.open(this, { @@ -408,14 +412,14 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { export class TaskRunQuickOpenItem extends QuickOpenGroupItem { - protected taskDefinitionRegistry: TaskDefinitionRegistry; - constructor( protected readonly task: TaskConfiguration, protected taskService: TaskService, protected isMulti: boolean, public readonly options: QuickOpenGroupItemOptions, + protected readonly taskDefinitionRegistry: TaskDefinitionRegistry, protected readonly taskNameResolver: TaskNameResolver, + protected readonly taskSourceResolver: TaskSourceResolver ) { super(options); } @@ -452,10 +456,11 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem { return false; } + const scope = this.taskSourceResolver.resolve(this.task); if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(this.task)) { - this.taskService.run(this.task.source || this.task._source, this.task.label); + this.taskService.run(this.task.source || this.task._source, this.task.label, scope); } else { - this.taskService.run(this.task._source, this.task.label); + this.taskService.run(this.task._source, this.task.label, scope); } return true; } @@ -469,9 +474,11 @@ export class ConfigureBuildOrTestTaskQuickOpenItem extends TaskRunQuickOpenItem public readonly options: QuickOpenGroupItemOptions, protected readonly taskNameResolver: TaskNameResolver, protected readonly isBuildTask: boolean, - protected taskConfigurationManager: TaskConfigurationManager + protected taskConfigurationManager: TaskConfigurationManager, + protected readonly taskDefinitionRegistry: TaskDefinitionRegistry, + protected readonly taskSourceResolver: TaskSourceResolver ) { - super(task, taskService, isMulti, options, taskNameResolver); + super(task, taskService, isMulti, options, taskDefinitionRegistry, taskNameResolver, taskSourceResolver); } run(mode: QuickOpenMode): boolean { diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index cef3294c3c3f9..b0ff585d92253 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -21,6 +21,7 @@ import { TaskDefinitionRegistry } from './task-definition-registry'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; import { TaskConfigurationManager } from './task-configuration-manager'; import { TaskSchemaUpdater } from './task-schema-updater'; +import { TaskSourceResolver } from './task-source-resolver'; import { Disposable, DisposableCollection, ResourceProvider } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; import { FileChange, FileChangeType } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; @@ -86,6 +87,9 @@ export class TaskConfigurations implements Disposable { @inject(TaskSchemaUpdater) protected readonly taskSchemaUpdater: TaskSchemaUpdater; + @inject(TaskSourceResolver) + protected readonly taskSourceResolver: TaskSourceResolver; + constructor() { this.toDispose.push(Disposable.create(() => { this.tasksMap.clear(); @@ -292,7 +296,7 @@ export class TaskConfigurations implements Disposable { return; } - const sourceFolderUri: string | undefined = this.getSourceFolderUriFromTask(task); + const sourceFolderUri: string | undefined = this.taskSourceResolver.resolve(task); if (!sourceFolderUri) { console.error('Global task cannot be customized'); return; @@ -414,7 +418,7 @@ export class TaskConfigurations implements Disposable { */ // tslint:disable-next-line:no-any async updateTaskConfig(task: TaskConfiguration, update: { [name: string]: any }): Promise { - const sourceFolderUri: string | undefined = this.getSourceFolderUriFromTask(task); + const sourceFolderUri: string | undefined = this.taskSourceResolver.resolve(task); if (!sourceFolderUri) { console.error('Global task cannot be customized'); return; @@ -464,15 +468,4 @@ export class TaskConfigurations implements Disposable { type: task.taskType || task.type }); } - - private getSourceFolderUriFromTask(task: TaskConfiguration): string | undefined { - const isDetectedTask = this.isDetectedTask(task); - let sourceFolderUri: string | undefined; - if (isDetectedTask) { - sourceFolderUri = task._scope; - } else { - sourceFolderUri = task._source; - } - return sourceFolderUri; - } } diff --git a/packages/task/src/browser/task-frontend-contribution.ts b/packages/task/src/browser/task-frontend-contribution.ts index ff53f78064fcb..2106c0af76a0d 100644 --- a/packages/task/src/browser/task-frontend-contribution.ts +++ b/packages/task/src/browser/task-frontend-contribution.ts @@ -217,9 +217,9 @@ export class TaskFrontendContribution implements CommandContribution, MenuContri isEnabled: () => true, // tslint:disable-next-line:no-any execute: (...args: any[]) => { - const [source, label] = args; + const [source, label, scope] = args; if (source && label) { - return this.taskService.run(source, label); + return this.taskService.run(source, label, scope); } return this.quickOpenTask.open(); } diff --git a/packages/task/src/browser/task-frontend-module.ts b/packages/task/src/browser/task-frontend-module.ts index 96f71519e1285..cc28be6be3965 100644 --- a/packages/task/src/browser/task-frontend-module.ts +++ b/packages/task/src/browser/task-frontend-module.ts @@ -38,6 +38,7 @@ import { bindTaskPreferences } from './task-preferences'; import '../../src/browser/style/index.css'; import './tasks-monaco-contribution'; import { TaskNameResolver } from './task-name-resolver'; +import { TaskSourceResolver } from './task-source-resolver'; import { TaskTemplateSelector } from './task-templates'; export default new ContainerModule(bind => { @@ -74,6 +75,7 @@ export default new ContainerModule(bind => { bindContributionProvider(bind, TaskContribution); bind(TaskSchemaUpdater).toSelf().inSingletonScope(); bind(TaskNameResolver).toSelf().inSingletonScope(); + bind(TaskSourceResolver).toSelf().inSingletonScope(); bind(TaskTemplateSelector).toSelf().inSingletonScope(); bindProcessTaskModule(bind); diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index f40189232de51..809ca34f43139 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -20,6 +20,7 @@ import { ILogger, CommandService } from '@theia/core/lib/common'; import { MessageService } from '@theia/core/lib/common/message-service'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { QuickPickItem, QuickPickService } from '@theia/core/lib/common/quick-pick-service'; +import { LabelProvider } from '@theia/core/lib/browser/label-provider'; import URI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager'; @@ -48,6 +49,7 @@ import { TaskConfigurationClient, TaskConfigurations } from './task-configuratio import { TaskProviderRegistry, TaskResolverRegistry } from './task-contribution'; import { TaskDefinitionRegistry } from './task-definition-registry'; import { TaskNameResolver } from './task-name-resolver'; +import { TaskSourceResolver } from './task-source-resolver'; import { ProblemMatcherRegistry } from './task-problem-matcher-registry'; import { TaskSchemaUpdater } from './task-schema-updater'; import { TaskConfigurationManager } from './task-configuration-manager'; @@ -131,6 +133,9 @@ export class TaskService implements TaskConfigurationClient { @inject(TaskNameResolver) protected readonly taskNameResolver: TaskNameResolver; + @inject(TaskSourceResolver) + protected readonly taskSourceResolver: TaskSourceResolver; + @inject(TaskSchemaUpdater) protected readonly taskSchemaUpdater: TaskSchemaUpdater; @@ -140,6 +145,8 @@ export class TaskService implements TaskConfigurationClient { @inject(CommandService) protected readonly commands: CommandService; + @inject(LabelProvider) + protected readonly labelProvider: LabelProvider; /** * @deprecated To be removed in 0.5.0 */ @@ -161,12 +168,9 @@ export class TaskService implements TaskConfigurationClient { return; } this.runningTasks.set(event.taskId, { exitCode: new Deferred(), terminateSignal: new Deferred() }); - const task = event.config; - let taskIdentifier = event.taskId.toString(); - if (task) { - taskIdentifier = this.taskNameResolver.resolve(task); - } - this.messageService.info(`Task ${taskIdentifier} has been started`); + const taskConfig = event.config; + const taskIdentifier = taskConfig ? this.getTaskIdentifier(taskConfig) : event.taskId.toString(); + this.messageService.info(`Task '${taskIdentifier}' has been started.`); }); this.taskWatcher.onOutputProcessed((event: TaskOutputProcessedEvent) => { @@ -210,27 +214,29 @@ export class TaskService implements TaskConfigurationClient { this.runningTasks.get(event.taskId)!.terminateSignal.resolve(event.signal); setTimeout(() => this.runningTasks.delete(event.taskId), 60 * 1000); - const taskConfiguration = event.config; - let taskIdentifier = event.taskId.toString(); - if (taskConfiguration) { - taskIdentifier = this.taskNameResolver.resolve(taskConfiguration); - } - + const taskConfig = event.config; + const taskIdentifier = taskConfig ? this.getTaskIdentifier(taskConfig) : event.taskId.toString(); if (event.code !== undefined) { - const message = `Task ${taskIdentifier} has exited with code ${event.code}.`; + const message = `Task '${taskIdentifier}' has exited with code ${event.code}.`; if (event.code === 0) { this.messageService.info(message); } else { this.messageService.error(message); } } else if (event.signal !== undefined) { - this.messageService.info(`Task ${taskIdentifier} was terminated by signal ${event.signal}.`); + this.messageService.info(`Task '${taskIdentifier}' was terminated by signal ${event.signal}.`); } else { console.error('Invalid TaskExitedEvent received, neither code nor signal is set.'); } }); } + private getTaskIdentifier(taskConfig: TaskConfiguration): string { + const taskName = this.taskNameResolver.resolve(taskConfig); + const sourceStrUri = this.taskSourceResolver.resolve(taskConfig); + return `${taskName} (${this.labelProvider.getName(new URI(sourceStrUri))})`; + } + /** Returns an array of the task configurations configured in tasks.json and provided by the extensions. */ async getTasks(): Promise { const configuredTasks = await this.getConfiguredTasks(); @@ -312,8 +318,8 @@ export class TaskService implements TaskConfigurationClient { * Returns a task configuration provided by an extension by task source and label. * If there are no task configuration, returns undefined. */ - async getProvidedTask(source: string, label: string): Promise { - return this.providedTaskConfigurations.getTask(source, label); + async getProvidedTask(source: string, label: string, scope?: string): Promise { + return this.providedTaskConfigurations.getTask(source, label, scope); } /** Returns an array of running tasks 'TaskInfo' objects */ @@ -364,8 +370,8 @@ export class TaskService implements TaskConfigurationClient { * Runs a task, by the source and label of the task configuration. * It looks for configured and detected tasks. */ - async run(source: string, taskLabel: string): Promise { - let task = await this.getProvidedTask(source, taskLabel); + async run(source: string, taskLabel: string, scope?: string): Promise { + let task = await this.getProvidedTask(source, taskLabel, scope); if (!task) { // if a detected task cannot be found, search from tasks.json task = this.taskConfigurations.getTask(source, taskLabel); if (!task) { @@ -409,6 +415,52 @@ export class TaskService implements TaskConfigurationClient { } async runTask(task: TaskConfiguration, option?: RunTaskOption): Promise { + const runningTasksInfo: TaskInfo[] = await this.getRunningTasks(); + + // check if the task is active + const matchedRunningTaskInfo = runningTasksInfo.find(taskInfo => { + const taskConfig = taskInfo.config; + return this.taskDefinitionRegistry.compareTasks(taskConfig, task); + }); + if (matchedRunningTaskInfo) { // the task is active + const taskName = this.taskNameResolver.resolve(task); + const terminalId = matchedRunningTaskInfo.terminalId; + if (terminalId) { + const terminal = this.terminalService.getById(this.getTerminalWidgetId(terminalId)); + if (terminal) { + this.shell.activateWidget(terminal.id); // make the terminal visible and assign focus + } + } + const selectedAction = await this.messageService.info(`The task '${taskName}' is already active`, 'Terminate Task', 'Restart Task'); + if (selectedAction === 'Terminate Task') { + await this.terminateTask(matchedRunningTaskInfo); + } else if (selectedAction === 'Restart Task') { + return this.restartTask(matchedRunningTaskInfo, option); + } + } else { // run task as the task is not active + return this.doRunTask(task, option); + } + } + + /** + * Terminates a task that is actively running. + * @param activeTaskInfo the TaskInfo of the task that is actively running + */ + protected async terminateTask(activeTaskInfo: TaskInfo): Promise { + const taskId = activeTaskInfo.taskId; + return this.kill(taskId); + } + + /** + * Terminates a task that is actively running, and restarts it. + * @param activeTaskInfo the TaskInfo of the task that is actively running + */ + protected async restartTask(activeTaskInfo: TaskInfo, option?: RunTaskOption): Promise { + await this.terminateTask(activeTaskInfo); + return this.doRunTask(activeTaskInfo.config, option); + } + + protected async doRunTask(task: TaskConfiguration, option?: RunTaskOption): Promise { if (option && option.customization) { const taskDefinition = this.taskDefinitionRegistry.getDefinition(task); if (taskDefinition) { // use the customization object to override the task config @@ -648,7 +700,7 @@ export class TaskService implements TaskConfigurationClient { TERMINAL_WIDGET_FACTORY_ID, { created: new Date().toString(), - id: 'terminal-' + processId, + id: this.getTerminalWidgetId(processId), title: taskInfo ? `Task: ${taskInfo.config.label}` : `Task: #${taskId}`, @@ -660,6 +712,10 @@ export class TaskService implements TaskConfigurationClient { widget.start(processId); } + private getTerminalWidgetId(terminalId: number): string { + return `${TERMINAL_WIDGET_FACTORY_ID}-${terminalId}`; + } + async configure(task: TaskConfiguration): Promise { await this.taskConfigurations.configure(task); } diff --git a/packages/task/src/browser/task-source-resolver.ts b/packages/task/src/browser/task-source-resolver.ts new file mode 100644 index 0000000000000..90822ad6fd9ba --- /dev/null +++ b/packages/task/src/browser/task-source-resolver.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (C) 2019 Ericsson and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { TaskConfiguration, ContributedTaskConfiguration } from '../common'; +import { TaskDefinitionRegistry } from './task-definition-registry'; + +@injectable() +export class TaskSourceResolver { + @inject(TaskDefinitionRegistry) + protected taskDefinitionRegistry: TaskDefinitionRegistry; + + /** + * Returns task source to display. + */ + resolve(task: TaskConfiguration): string | undefined { + const isDetectedTask = this.isDetectedTask(task); + let sourceFolderUri: string | undefined; + if (isDetectedTask) { + sourceFolderUri = task._scope; + } else { + sourceFolderUri = task._source; + } + return sourceFolderUri; + } + + private isDetectedTask(task: TaskConfiguration): task is ContributedTaskConfiguration { + return !!this.taskDefinitionRegistry.getDefinition(task); + } +} diff --git a/packages/terminal/src/browser/terminal-copy-on-selection-handler.ts b/packages/terminal/src/browser/terminal-copy-on-selection-handler.ts index 87c755bc2a44c..d2ac51bc47ad4 100644 --- a/packages/terminal/src/browser/terminal-copy-on-selection-handler.ts +++ b/packages/terminal/src/browser/terminal-copy-on-selection-handler.ts @@ -18,7 +18,7 @@ import { injectable, postConstruct } from 'inversify'; import { isFirefox } from '@theia/core/lib/browser'; @injectable() -export class TerminalCopyOnSelectionHander { +export class TerminalCopyOnSelectionHandler { private textToCopy: string; private interceptCopy: boolean; @@ -36,18 +36,18 @@ export class TerminalCopyOnSelectionHander { } private async clipBoardCopyIsGranted(): Promise { - // Unfortunately Firefox doesn't support permission check `clipboard-write`, so let try to copy anyway, - if (isFirefox) { + // Unfortunately Firefox doesn't support permission check `clipboard-write`, so let try to copy anyway, + if (isFirefox) { return true; } try { // tslint:disable-next-line:no-any const permissions = (navigator as any).permissions; - const { state } = await permissions.query({name: 'clipboard-write'}); + const { state } = await permissions.query({ name: 'clipboard-write' }); if (state === 'granted') { return true; } - } catch (e) {} + } catch (e) { } return false; } diff --git a/packages/terminal/src/browser/terminal-frontend-module.ts b/packages/terminal/src/browser/terminal-frontend-module.ts index f0a9a232056cb..0ddb8dff5bffe 100644 --- a/packages/terminal/src/browser/terminal-frontend-module.ts +++ b/packages/terminal/src/browser/terminal-frontend-module.ts @@ -37,7 +37,7 @@ import { TerminalQuickOpenService, TerminalQuickOpenContribution } from './termi import '../../src/browser/terminal.css'; import 'xterm/lib/xterm.css'; -import { TerminalCopyOnSelectionHander } from './terminal-copy-on-selection-handler'; +import { TerminalCopyOnSelectionHandler } from './terminal-copy-on-selection-handler'; export default new ContainerModule(bind => { bindTerminalPreferences(bind); @@ -68,7 +68,7 @@ export default new ContainerModule(bind => { })); bind(TerminalQuickOpenService).toSelf().inSingletonScope(); - bind(TerminalCopyOnSelectionHander).toSelf().inSingletonScope(); + bind(TerminalCopyOnSelectionHandler).toSelf().inSingletonScope(); bind(TerminalQuickOpenContribution).toSelf().inSingletonScope(); for (const identifier of [CommandContribution, QuickOpenContribution]) { diff --git a/packages/terminal/src/browser/terminal-linkmatcher-files.ts b/packages/terminal/src/browser/terminal-linkmatcher-files.ts index ccface4612391..4383e6b36213a 100644 --- a/packages/terminal/src/browser/terminal-linkmatcher-files.ts +++ b/packages/terminal/src/browser/terminal-linkmatcher-files.ts @@ -131,7 +131,7 @@ export class TerminalLinkmatcherFiles extends AbstractCmdClickTerminalContributi } } -// below reg exps are taken from +// The following regular expressions are taken from: // https://github.com/microsoft/vscode/blob/fbbc1aa80332189aa0d3006cb2159b79a9eba480/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts /*--------------------------------------------------------------------------------------------- @@ -142,7 +142,7 @@ export class TerminalLinkmatcherFiles extends AbstractCmdClickTerminalContributi const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; // '":; are allowed in paths but they are often separators so ignore them -// Also disallow \\ to prevent a catastropic backtracking case #24798 +// Also disallow \\ to prevent a catastrophic backtracking case #24798 const excludedPathCharactersClause = '[^\\0\\s!$`&*()\\[\\]+\'":;\\\\]'; /** A regex that matches paths in the form /foo, ~/foo, ./foo, ../foo, foo/bar */ const unixLocalLinkClause = '((' + pathPrefix + '|(' + excludedPathCharactersClause + ')+)?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + ')+)+)'; @@ -154,7 +154,7 @@ const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()\\[\\]+\'":;] /** A regex that matches paths in the form c:\foo, ~\foo, .\foo, ..\foo, foo\bar */ const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathCharactersClause + ')+)?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)'; -/** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160, replacing space with nonBreakningSpace or space ASCII code - 32. */ +/** As xterm reads from DOM, space in that case is non-breaking char ASCII code - 160, replacing space with nonBreakingSpace or space ASCII code - 32. */ const lineAndColumnClause = [ // "(file path)", line 45 [see #40468] '((\\S*)", line ((\\d+)( column (\\d+))?))', diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 854fa7a3cc2d9..f04d0ac74ba4b 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -33,7 +33,7 @@ import { TerminalPreferences, TerminalRendererType, isTerminalRendererType, DEFA import { TerminalContribution } from './terminal-contribution'; import URI from '@theia/core/lib/common/uri'; import { TerminalService } from './base/terminal-service'; -import { TerminalCopyOnSelectionHander } from './terminal-copy-on-selection-handler'; +import { TerminalCopyOnSelectionHandler } from './terminal-copy-on-selection-handler'; export const TERMINAL_WIDGET_FACTORY_ID = 'terminal'; @@ -76,7 +76,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget @inject(TerminalPreferences) protected readonly preferences: TerminalPreferences; @inject(ContributionProvider) @named(TerminalContribution) protected readonly terminalContributionProvider: ContributionProvider; @inject(TerminalService) protected readonly terminalService: TerminalService; - @inject(TerminalCopyOnSelectionHander) protected readonly copyOnSelectionHandler: TerminalCopyOnSelectionHander; + @inject(TerminalCopyOnSelectionHandler) protected readonly copyOnSelectionHandler: TerminalCopyOnSelectionHandler; protected readonly onDidOpenEmitter = new Emitter(); readonly onDidOpen: Event = this.onDidOpenEmitter.event; diff --git a/packages/terminal/src/node/terminal-server.spec.ts b/packages/terminal/src/node/terminal-server.spec.ts index adc8da043f4c8..7182ed23f0efa 100644 --- a/packages/terminal/src/node/terminal-server.spec.ts +++ b/packages/terminal/src/node/terminal-server.spec.ts @@ -24,7 +24,7 @@ import { ITerminalServer } from '../common/terminal-protocol'; const expect = chai.expect; -describe('TermninalServer', function (): void { +describe('TerminalServer', function (): void { this.timeout(5000); let terminalServer: ITerminalServer; diff --git a/packages/workspace/src/browser/workspace-frontend-contribution.ts b/packages/workspace/src/browser/workspace-frontend-contribution.ts index d2bdf91a1d1b7..80b6bd29a755a 100644 --- a/packages/workspace/src/browser/workspace-frontend-contribution.ts +++ b/packages/workspace/src/browser/workspace-frontend-contribution.ts @@ -147,7 +147,7 @@ export class WorkspaceFrontendContribution implements CommandContribution, Keybi }); keybindings.registerKeybinding({ command: isOSX || !this.isElectron() ? WorkspaceCommands.OPEN.id : WorkspaceCommands.OPEN_FILE.id, - keybinding: 'ctrlcmd+alt+o', + keybinding: this.isElectron() ? 'ctrlcmd+o' : 'ctrlcmd+alt+o', }); if (!isOSX && this.isElectron()) { keybindings.registerKeybinding({ diff --git a/tsconfig.json b/tsconfig.json index 42d04f1a9c071..e10ed4853e031 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -97,9 +97,6 @@ "@theia/editorconfig/lib/*": [ "packages/editorconfig/src/*" ], - "@theia/bunyan/lib/*": [ - "packages/bunyan/src/*" - ], "@theia/console/lib/*": [ "packages/console/src/*" ], diff --git a/yarn.lock b/yarn.lock index 7a06232bbff35..65f1bd2c7e8de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -874,13 +874,6 @@ "@types/connect" "*" "@types/node" "*" -"@types/bunyan@^1.8.0": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582" - integrity sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ== - dependencies: - "@types/node" "*" - "@types/caseless@*": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" @@ -2890,16 +2883,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bunyan@^1.8.10: - version "1.8.12" - resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797" - integrity sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c= - optionalDependencies: - dtrace-provider "~0.8" - moment "^2.10.6" - mv "~2" - safe-json-stringify "~1" - byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -4596,13 +4579,6 @@ drivelist@^6.4.3: nan "^2.10.0" prebuild-install "^4.0.0" -dtrace-provider@~0.8: - version "0.8.8" - resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" - integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== - dependencies: - nan "^2.14.0" - dugite-extra@0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/dugite-extra/-/dugite-extra-0.1.12.tgz#fc2f5e2fb288e688aaec7a770a24a21293831175" @@ -8458,7 +8434,7 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@^2.10.6, moment@^2.21.0, moment@^2.6.0: +moment@^2.21.0, moment@^2.6.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -8546,7 +8522,7 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -mv@^2.1.1, mv@~2: +mv@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI= @@ -10852,11 +10828,6 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== -safe-json-stringify@~1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" - integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== - safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"