From 77652ef0b662dbc6a8511539db0ed65b9240815e Mon Sep 17 00:00:00 2001 From: vince-fugnitto Date: Wed, 27 Nov 2019 11:20:48 -0500 Subject: [PATCH 01/20] Remove '@theia/bunyan' extension Fixes #6637 Motivation: - the extension is not used by any other extension present in the main repo. - the extension is not part of the `example-browser` or `example-electron` application, therefore it cannot be tested properly. - since it is not part of any other extension, or tested, it is likely to be un-maintained. Signed-off-by: vince-fugnitto --- .travis.yml | 1 - CHANGELOG.md | 6 +- packages/bunyan/README.md | 9 - packages/bunyan/compile.tsconfig.json | 11 - packages/bunyan/package.json | 47 ---- .../bunyan/src/node/bunyan-backend-module.ts | 23 -- .../bunyan/src/node/bunyan-logger-server.ts | 221 ------------------ packages/bunyan/src/package.spec.ts | 21 -- tsconfig.json | 3 - 9 files changed, 5 insertions(+), 337 deletions(-) delete mode 100644 packages/bunyan/README.md delete mode 100644 packages/bunyan/compile.tsconfig.json delete mode 100644 packages/bunyan/package.json delete mode 100644 packages/bunyan/src/node/bunyan-backend-module.ts delete mode 100644 packages/bunyan/src/node/bunyan-logger-server.ts delete mode 100644 packages/bunyan/src/package.spec.ts 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..7757cb4e30e1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 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) + ## v0.13.0 - [console] added filtering support based on severity [#6486](https://github.com/eclipse-theia/theia/pull/6486) @@ -69,7 +73,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/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/bunyan/src/package.spec.ts b/packages/bunyan/src/package.spec.ts deleted file mode 100644 index 88c14dc79022e..0000000000000 --- a/packages/bunyan/src/package.spec.ts +++ /dev/null @@ -1,21 +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 - ********************************************************************************/ - -describe('bunyan package', () => { - - it('support code coverage statistics', () => true); - -}); 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/*" ], From b76f08e46552a630c20510b75ea863ddda39b6a9 Mon Sep 17 00:00:00 2001 From: vince-fugnitto Date: Wed, 27 Nov 2019 11:40:31 -0500 Subject: [PATCH 02/20] Remove 'bunyan' and '@types/bunyan' dependencies - removes the `bunyan` dependency which is unused. - removes the `@types/bunyan` dependency which is unused. Signed-off-by: vince-fugnitto --- dev-packages/application-manager/package.json | 1 - packages/core/package.json | 1 - yarn.lock | 33 ++----------------- 3 files changed, 2 insertions(+), 33 deletions(-) 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/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/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" From 5d2484796f60631d418ed4b436603565b2254ef5 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Mon, 2 Dec 2019 10:24:23 +0000 Subject: [PATCH 03/20] [core] don't scroll when closing a dialog When a dialog gets closed, the previously active element gets focus again. We don't want the default scroll to top behavior. Signed-off-by: Sven Efftinge --- packages/core/src/browser/dialogs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From a551e6d91ce3c1a7fae83e76a0c51834caae237e Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Tue, 3 Dec 2019 14:50:19 +0000 Subject: [PATCH 04/20] Set 'noopener' when opening windows to avoid sharing event loops Prevents the web UI pausing when an app opened with window.open is paused in the browsers debugger. Fixes https://github.com/eclipse-theia/theia/issues/5857. Signed-off-by: Danny Tuppeny --- CHANGELOG.md | 4 ++++ packages/core/src/browser/http-open-handler.ts | 2 +- .../core/src/browser/window/default-window-service.ts | 9 +++------ packages/core/src/browser/window/window-service.ts | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7757cb4e30e1c..3bbe3704b9804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - [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 giveing them their own event loop. openNewWindow will no longer return a Window as a result. + ## v0.13.0 - [console] added filtering support based on severity [#6486](https://github.com/eclipse-theia/theia/pull/6486) 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/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. From 5b45e1240590c1582c8bf6cfb3ee0fa5994e3770 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Tue, 3 Dec 2019 10:56:18 +0000 Subject: [PATCH 05/20] [tree]: fix infinity recursion when tree root is change during ongoing refresh The navigator model root gets initialized too eagerly before the workspace service is ready to provide roots. And then the second time by an event then the workspace service is initialized. It causes 2 parallel refreshes and with bad timing on workspaces with multiple roots can lead to infinity loop and eventually max call stack error. This resolves the issue generically for trees by allowing parallel refreshes without resetting a root. Signed-off-by: Anton Kosyakov --- .../browser/tree/test/tree-test-container.ts | 45 ++++ .../src/browser/tree/tree-consistency.spec.ts | 101 ++++++++ .../src/browser/tree/tree-expansion.spec.ts | 216 ++++++++---------- .../core/src/browser/tree/tree-expansion.ts | 23 +- packages/core/src/browser/tree/tree-model.ts | 19 +- packages/core/src/browser/tree/tree.spec.ts | 31 +-- packages/core/src/browser/tree/tree.ts | 33 +-- .../navigator/src/browser/navigator-model.ts | 8 +- 8 files changed, 294 insertions(+), 182 deletions(-) create mode 100644 packages/core/src/browser/tree/test/tree-test-container.ts create mode 100644 packages/core/src/browser/tree/tree-consistency.spec.ts 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/navigator/src/browser/navigator-model.ts b/packages/navigator/src/browser/navigator-model.ts index df8b43db01175..7ae912eb4c64e 100644 --- a/packages/navigator/src/browser/navigator-model.ts +++ b/packages/navigator/src/browser/navigator-model.ts @@ -130,14 +130,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 +155,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; } From c101c61c58b1b571204b4798e214341ba33d3356 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Tue, 3 Dec 2019 15:39:37 +0000 Subject: [PATCH 06/20] [navigator] set root and listen to changes only when workspace is ready Signed-off-by: Anton Kosyakov --- .../src/browser/navigator-model.spec.ts | 11 +---- .../navigator/src/browser/navigator-model.ts | 40 +++++++++++++------ .../src/browser/navigator-widget.tsx | 19 +-------- 3 files changed, 30 insertions(+), 40 deletions(-) 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 7ae912eb4c64e..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(); } 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; From d5c81105393a811de3097ff06bdf64ab98ef3d80 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 2 Dec 2019 14:57:38 +0100 Subject: [PATCH 07/20] GH-6499: Explicitly close the socket `onStop` in electron Otherwise, the channels will be closed with a `checkAliveTimeout` delay in the electron application, when refreshing the browser window. Closes: #6499 Signed-off-by: Akos Kitta --- .../src/generator/frontend-generator.ts | 2 +- .../common/messaging/web-socket-channel.ts | 4 ++ .../electron-messaging-frontend-module.ts | 26 ++++++++++ .../electron-ws-connection-provider.ts | 47 +++++++++++++++++++ .../node/messaging/messaging-contribution.ts | 6 ++- .../search-in-workspace-frontend-module.ts | 4 +- .../common/search-in-workspace-interface.ts | 1 + .../search-in-workspace-backend-module.ts | 16 +++---- 8 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 packages/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts create mode 100644 packages/core/src/electron-browser/messaging/electron-ws-connection-provider.ts diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index 8e53f7d664cfb..f35488f20d0a0 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'); 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/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts b/packages/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts new file mode 100644 index 0000000000000..6f8db65b589db --- /dev/null +++ b/packages/core/src/electron-browser/messaging/electron-messaging-frontend-module.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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 { 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/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); }); From f2506f1457cb8ecd278b98350ff9a890f5d9c8f7 Mon Sep 17 00:00:00 2001 From: Masaki Muranaka Date: Thu, 5 Dec 2019 17:31:52 +0900 Subject: [PATCH 08/20] Fix typo. Signed-off-by: Masaki Muranaka --- doc/Developing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 8fde6f9feec5ba739a5431d9807f2f734318dc84 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Wed, 4 Dec 2019 17:19:37 +0100 Subject: [PATCH 09/20] [keybinding] Align Electron keybindings for 'Open File' and 'Open Workspace Symbol' with VS Code Signed-off-by: Jan Keromnes --- packages/languages/package.json | 1 + packages/languages/src/browser/workspace-symbols.ts | 7 ++++++- .../src/browser/workspace-frontend-contribution.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) 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/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({ From 9983b6bddf99d30cfc59898f893902f127a111e3 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Thu, 5 Dec 2019 11:00:51 +0000 Subject: [PATCH 10/20] [vscode] add allow-forms Fixes eclipse-theia/theia#6694 Signed-off-by: Sven Efftinge --- packages/plugin-ext/src/main/browser/webview/webview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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%'; From eb74d4a74e51713144a08bb6d118eaaa2a8fcc61 Mon Sep 17 00:00:00 2001 From: vince-fugnitto Date: Wed, 4 Dec 2019 18:47:59 -0500 Subject: [PATCH 11/20] Fix 'terminal' extension typos - fixes `@theia/terminal` extension typos. - renames `TerminalCopyOnSelectionHander` to `TerminalCopyOnSelectionHandler`. Signed-off-by: vince-fugnitto --- CHANGELOG.md | 3 ++- .../src/browser/terminal-copy-on-selection-handler.ts | 10 +++++----- .../terminal/src/browser/terminal-frontend-module.ts | 4 ++-- .../terminal/src/browser/terminal-linkmatcher-files.ts | 6 +++--- packages/terminal/src/browser/terminal-widget-impl.ts | 4 ++-- packages/terminal/src/node/terminal-server.spec.ts | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bbe3704b9804..5c1af39b9cff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ Breaking changes: -- [core] new browser windows spawned through opener-service have noopener set, preventing them from accessing window.opener and giveing them their own event loop. openNewWindow will no longer return a Window as a result. +- [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) ## v0.13.0 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; From b6daa53a74bdb261a6c17f49500e8437f0306bf5 Mon Sep 17 00:00:00 2001 From: Vincent Fugnitto Date: Tue, 3 Dec 2019 08:50:21 -0500 Subject: [PATCH 12/20] Allow installation of VS Code extension packs Fixes #6611 - added `extensionPack` property to `PluginPackage` interface. - when getting dependencies, ensure that `extensionPack` dependencies are also loaded. Extensions packs can now be unloaded and their bundled extensions installed. Signed-off-by: Vincent Fugnitto --- .../src/node/scanner-vscode.ts | 19 ++++++++++++------- .../plugin-ext/src/common/plugin-protocol.ts | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) 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 { From 27745bf2483e10600978d9e7e4821a1a0ab3ac83 Mon Sep 17 00:00:00 2001 From: Masaki Muranaka Date: Mon, 2 Dec 2019 18:13:30 +0900 Subject: [PATCH 13/20] [hotfix] Stop dispatching to keybindings in editing coposition text. Signed-off-by: Masaki Muranaka --- .../core/src/browser/frontend-application.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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) { From d9800b4feb6f716f96a93db3e7553c8249e09ad6 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Fri, 6 Dec 2019 08:54:16 +0100 Subject: [PATCH 14/20] Use VS Code's keychord for the 'Close All' command When the app is running in the electron environment. Signed-off-by: Akos Kitta --- packages/core/src/browser/common-frontend-contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From b87052c095dadcef4991c428c58d8c608f547874 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Fri, 6 Dec 2019 13:14:39 +0100 Subject: [PATCH 15/20] GH-6707: Fixed a timeout issue for notifications Timeout was ignored when no `actions` were set. The corrected logic ignores the `timeout` if any action was set. Closes #6707 Signed-off-by: Akos Kitta --- packages/messages/src/browser/notifications-manager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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']; From 4d3a125c0d35277ced80d4451c10f70ad2dda916 Mon Sep 17 00:00:00 2001 From: Liang Huang Date: Sat, 30 Nov 2019 19:48:57 -0500 Subject: [PATCH 16/20] ask user to terminate or restart a task if it is active With changes in this pull request, Theia offers users the flexibility of terminating or restarting a task, if the user tries to run a task that is actively running. This pull request resolves https://github.com/eclipse-theia/theia/issues/6618#issuecomment-558047443 Signed-off-by: Liang Huang --- .../task/src/browser/task-configurations.ts | 19 ++-- .../task/src/browser/task-frontend-module.ts | 2 + packages/task/src/browser/task-service.ts | 86 +++++++++++++++---- .../task/src/browser/task-source-resolver.ts | 43 ++++++++++ 4 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 packages/task/src/browser/task-source-resolver.ts 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-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..7b6a00b39a1d6 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(); @@ -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); + } +} From 0e64f11eb40c1aaf073c76eebb40f56ab092aaba Mon Sep 17 00:00:00 2001 From: vince-fugnitto Date: Thu, 5 Dec 2019 15:29:01 -0500 Subject: [PATCH 17/20] Fix typos present in 'debug' extension - renamed command `COPY_VAIRABLE_VALUE` to `COPY_VARIABLE_VALUE`. - renamed command `COPY_VAIRABLE_AS_EXPRESSION` to `COPY_VARIABLE_AS_EXPRESSION`. - renamed getter method `multiSesssion` to `multiSession`. - fixed additional typos. Signed-off-by: vince-fugnitto --- CHANGELOG.md | 3 +++ .../debug-frontend-application-contribution.ts | 12 ++++++------ .../browser/preferences/launch-preferences.spec.ts | 2 +- .../debug/src/browser/view/debug-threads-source.tsx | 2 +- .../debug/src/browser/view/debug-threads-widget.ts | 2 +- packages/debug/src/common/debug-model.ts | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1af39b9cff2..60beb86c7ffdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ 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) ## v0.13.0 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. From 0159cd5b6aa51c594498dad3185bcd6c528e22c2 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 9 Dec 2019 20:28:07 +0100 Subject: [PATCH 18/20] Explicitly set the electron app name. To have better menu items on macOS. ("About", "Hide", and "Quit") Signed-off-by: Akos Kitta --- .../application-manager/src/generator/frontend-generator.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index f35488f20d0a0..816d8bd5aad38 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -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. From d5f0fcf9e7fdc6fed0a2f15c6ca43e3c6ce08cfd Mon Sep 17 00:00:00 2001 From: Liang Huang Date: Sat, 7 Dec 2019 23:39:01 -0500 Subject: [PATCH 19/20] search detected tasks by scope This pull request adds the support of detected tasks that have - same label, and - different scopes in a multi-root workspace. This change fixes #6715. Signed-off-by: Liang Huang --- CHANGELOG.md | 2 + .../provided-task-configurations.spec.ts | 2 +- .../browser/provided-task-configurations.ts | 36 +++++++++++------ packages/task/src/browser/quick-open-task.ts | 39 +++++++++++-------- .../src/browser/task-frontend-contribution.ts | 4 +- packages/task/src/browser/task-service.ts | 8 ++-- 6 files changed, 57 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60beb86c7ffdf..d01598d78e02a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Breaking changes: - [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 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-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-service.ts b/packages/task/src/browser/task-service.ts index 7b6a00b39a1d6..809ca34f43139 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -318,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 */ @@ -370,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) { From 928e1e00b47268c7ea8b8f2889c520e5fa3d5f0a Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Mon, 9 Dec 2019 10:16:43 +0200 Subject: [PATCH 20/20] Fixes for Vscode Vim extension Signed-off-by: Igor Vinokur --- packages/core/src/common/command.ts | 2 +- packages/monaco/src/browser/monaco-editor.ts | 5 +++++ packages/monaco/src/typings/monaco/index.d.ts | 1 + packages/plugin-ext-vscode/package.json | 1 + .../plugin-vscode-commands-contribution.ts | 9 +++++++++ .../src/main/browser/command-registry-main.ts | 16 ++++++++++++++-- 6 files changed, 31 insertions(+), 3 deletions(-) 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/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/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 865f3b1c41975..cffc578c1e39f 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 @@ -28,6 +28,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'; @@ -137,6 +138,14 @@ export class PluginVscodeCommandsContribution implements CommandContribution { commands.registerCommand({ id: 'workbench.action.files.openFolder' }, { execute: () => commands.executeCommand(WorkspaceCommands.OPEN_FOLDER.id) }); + 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/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 {