diff --git a/.gitignore b/.gitignore index af8b3ba51c2fc..a5c9cab098ebe 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ package-backup.json .history .Trash-* packages/plugin/typedoc +plugins +gh-pages diff --git a/.gitpod.yml b/.gitpod.yml index d7be3ce3cc9ca..443adad3ff7ab 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,8 +1,8 @@ ports: - port: 3000 tasks: -- init: yarn install - command: cd examples/browser && yarn start --hostname 0.0.0.0 ../.. +- init: nvm install 10 && nvm use 10 && yarn + command: yarn --cwd examples/browser start ../.. github: prebuilds: pullRequestsFromForks: true diff --git a/.travis.yml b/.travis.yml index c6c6385b7140f..66ae57049b646 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ sudo: required language: node_js -node_js: '8' +node_js: '10' git: depth: 1 cache: @@ -118,9 +118,16 @@ jobs: before_deploy: - printf "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}\n" >> ~/.npmrc - yarn + - yarn run docs deploy: - provider: script - script: yarn run publish:next - on: - branch: master - skip_cleanup: true + - provider: script + script: yarn run publish:next + on: + branch: master + skip_cleanup: true + - provider: pages + skip-cleanup: true + github-token: $GITHUB_TOKEN + local-dir: gh-pages + on: + branch: master diff --git a/CHANGELOG.md b/CHANGELOG.md index 5019dea70dbce..7f2276d0499c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,100 @@ # Change Log +## v0.6.0 + +- [filesystem] added the menu item `Upload Files...` to easily upload files into a workspace +- [plugin] added `CodeActionKind` `intersects` Plug-in API +- [task] added support to configure tasks +- [workspace] allow creation of files and folders using recursive paths +- [electron] upgraded version of electron used to version 3. +- [tree] support icons in node tail decorators + +Breaking changes: + +- [node] moved to using Node.js version 10, dropping support for Node.js version 8. +- [dialog] `validate` and `accept` methods are now Promisified [#4764](https://github.com/theia-ide/theia/pull/4764) +- [editor] turn off autoSave by default to align with VS Code [#4777](https://github.com/theia-ide/theia/pull/4777) + - default settings can be overriden in application package.json: + ```json + { + "private": true, + "name": "myapp", + "theia": { + "frontend": { + "config": { + "preferences": { + "editor.autoSave": "on" + } + } + } + } + } + ``` + ## v0.5.0 + +- Added `scope` to task configurations to differentiate 3 things: task type, task source, and where to run tasks +- [core] added implementation for toolbar support for sidepanels and changed sidepanel tabs +- [core] added new keybinding alt+shift+w to close all main area tabs +- [core] added the ability to make sidebar widgets closable +- [core] fixed `ToolbarAwareTabBar` detachment errors +- [core] fixed broken wheel listener +- [core] improved scrollbar styling +- [core] updated tabbar toolbar to use VSCode icons +- [core] updated the UI with numerous improvements including sidepanel icons, better alignment, tabbar and menu size +- [cpp] added new `cpp.clangTidy `and `cpp.clangTidyChecks` preferences to lint cpp program when clangd v9+ is used +- [cpp] fixed properly restarting clangd language server when changing cpp build configurations +- [debug] added new debug preferences to control `view`, `console`, and `location` appearance +- [editorconfig] added support to apply properties to monaco editor when opening/switching editors +- [file-search] improved ordering and consistency of file search results +- [filesystem] added `files.associations` property +- [filesystem] improved the performance when deleting large directories +- [filesystem] upgraded `nsfw` file-watching dependency from `vscode-nsfw` to `Axosoft/nsfw` which fixes memory leaks as well as fixes issues where files are not being properly watched outside the main watched directory +- [git] fixed issue where Theia did not refresh the git view after deleting the only repository +- [git] improved the git diff navigation header to be static +- [java] improved handling of incomplete classpath commands +- [keybindings] improved the keybindings widget search and table header to be static +- [mini-browser] improved error handling of iframe errors +- [navigator] added `Collapse All` toolbar item +- [navigator] updated the navigator to handle multi-root workspaces better +- [plugin-ext] added `workspace.onDidRenameFile ` Plug-in API +- [plugin-ext] added `workspace.onWillRenameFile ` Plug-in API +- [plugin-ext] added `workspace.registerFileSystemProvider` Plug-in API +- [plugin-ext] added `workspace.saveAll` Plug-in API +- [plugin-ext] added `workspace.updateWorkspaceFolders` Plug-in API +- [plugin-ext] added ability to proceed `runInTerminal` requests in sidecar containers +- [plugin-ext] added the ability to get selection context after executing a command +- [plugin-ext] fixed VSCode Plug-in API incompatibilities for the `onDidChangeActiveTextEditor` event +- [plugin-ext] fixed firing the `onWillSaveTextDocument` event +- [plugin-ext] fixed issue of re-deploying already initialized plugins - [plugin] `workspace.openTextDocument` API now respects the contributed `FileSystemProviders` +- [plugin] added support for multiple windows per backend +- [plugin] fixed progress creation +- [plugin] improved the view container to use the native toolbar +- [preferences] fixed content assist when editing `settings.json` +- [preferences] fixed parsing of settings from workspace files +- [preferences] improved overriding of default configurations +- [preview] fixed issue when opening images +- [search-in-workspace] added a new preference `search.lineNumbers` to control whether to show line numbers for search results +- [task] added ability to `Run Selected Text` +- [task] added new command to re-run the last task +- [task] added schema support for `tasks.json` +- [typehierarchy] added the new type hierarchy extension +- [typehierarchy] improved `typehierarchy` to use all levels the language server sends if available +- [workspace] added new `package.json` properties `newFIleName` and `newFileExtension` to specify default file name and extension when creating a new file +- [workspace] improved performance of the file rename action for large directories Breaking changes: + - [editor] computation of resource context keys moved to core [#4531](https://github.com/theia-ide/theia/pull/4531) - [plugin] support multiple windows per a backend [#4509](https://github.com/theia-ide/theia/issues/4509) - Some plugin bindings are scoped per a connection now. Clients, who contribute/rebind these bindings, will need to scope them per a connection as well. - [quick-open] disable separate fuzzy matching by default [#4549](https://github.com/theia-ide/theia/pull/4549) +- [shell] support toolbars in side bars [#4600](https://github.com/theia-ide/theia/pull/4600) + - In side bars a widget title is rendered as an icon. ## v0.4.0 + - [application-manager] added support for pre-load HTML templates - [console] added support for console `when` contexts - [core] added support for os `when` contexts @@ -84,8 +169,8 @@ Breaking changes: - [workspace] fixed displaying the `Open With...` context menu only when more than one open handler is present - [mini-browser] improved handling of iframe errors and time-outs - Breaking changes: + - menus aligned with built-in VS Code menus [#4173](https://github.com/theia-ide/theia/pull/4173) - navigator context menu group changes: - `1_open` and `4_new` replaced by `navigation` group @@ -121,8 +206,8 @@ Breaking changes: - `affects` function is added to `PreferenceChangeEvent` and `PreferenceChange` interface - `navigator.exclude` preference is renamed to `files.exclude` [#4274](https://github.com/theia-ide/theia/pull/4274) - ## v0.3.19 + - [core] added `hostname` alias - [core] added new `editor.formatOnSave` preference, to format documents on manual save - [core] added support for setting end of line character @@ -162,8 +247,8 @@ Breaking changes: - [task] fixed cwd path - [workspace] added multiple-root support for `WorkspaceService.getWorkspaceRootUri()` - ## v0.3.18 + - [core] added a preference to define how to handle application exit - [core] added a way to prevent application exit from extensions - [core] added functionality to prevent application exit if some editors are dirty @@ -201,8 +286,8 @@ Breaking changes: - [workspace] fixed long label computations for multiple-root workspaces - [xterm] updated Xterm to `3.9.1` - ## v0.3.17 + - Added better widget error handling for different use cases (ex: no workspace present, no repository present, ...) - Addressed multiple backend memory leaks - Prefixed quick-open commands for easier categorization and searching @@ -239,8 +324,8 @@ Breaking changes: - [workspace] added the context menu item `Collapse All` for the file navigator - [workspace] included workspace path as part of the URL fragment - ## v0.3.16 + - Reverted [cpp] Add debugging for C/C++ programs. This feature will come back in its own cpp-specific repo - [callhierarchy][typescript] adapt to hierarchical document symbols - [core] added methods to un-register menus, commands and keybindings @@ -252,8 +337,8 @@ Breaking changes: - [terminal] added 'open in terminal' to navigator - [windows] implemented drives selector for the file dialog - ## v0.3.15 + - [cpp] added debugging for C/C++ programs - [debug] added debug toolbar - [debug] resolved variables in configurations @@ -263,8 +348,8 @@ Breaking changes: - [plug-in] added `menus` contribution point - [workspace] added multi-root workspace support with vscode compatibility - ## v0.3.13 + - Re-implemented additional widgets using React - Re-implemented miscellaneous components using React - [cpp] added a status bar button to select an active cpp build configuration @@ -278,12 +363,12 @@ Breaking changes: - [ts] added support for one ls for all JavaScript related languages - [workspace] added support for recently opened workspaces history - ## v0.3.12 + - New Plugin system ! - - See [design](https://github.com/theia-ide/theia/issues/1482) and [documentation](https://github.com/theia-ide/theia/blob/master/packages/plugin/API.md) for more details. + - See [design](https://github.com/theia-ide/theia/issues/1482) and [documentation](https://github.com/theia-ide/theia/blob/master/packages/plugin/API.md) for more details. - Introducing [Task API](https://github.com/theia-ide/theia/pull/2086). - - Note, the format of tasks.json has been changed. For details, see the Task extension's [README.md](https://github.com/theia-ide/theia/blob/master/packages/task/README.md). + - Note, the format of tasks.json has been changed. For details, see the Task extension's [README.md](https://github.com/theia-ide/theia/blob/master/packages/task/README.md). - Added an UI when developing plugins - Migrated widgets to `react` - Theia alerts you when the opening of a new tab is denied by the browser @@ -299,6 +384,7 @@ Breaking changes: - `HTML` files now open in the editor by default ## v0.3.11 + - Added search and replace widget - Added the ability to delete files on OSX with cmd+backspace - Added the ability to set more finely grained logger levels diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000000000..83107159a26e9 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,281 @@ +# Notices for Eclipse Theia + +This content is produced and maintained by the Eclipse Theia project. + +* Project home: https://projects.eclipse.org/projects/ecd.theia + +## Trademarks + +Eclipse Theia is a trademark of the Eclipse Foundation. + +## Copyright + +All content is the property of the respective authors or their employers. For +more information regarding authorship of content, please consult the listed +source code repository logs. + +## Declared Project Licenses + +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 + +## Source Code + +The project maintains the following source code repositories: + +* https://github.com/eclipse/theia-generator-plugin +* https://github.com/eclipse/theia-yeoman-plugin +* https://github.com/eclipse/theia-plugin-packager +* https://github.com/eclipse/theia-cpp-extension +* https://github.com/eclipse/theia-python-extension +* https://github.com/eclipse/theia-java-extension + +## Third-party Content + +This project leverages the following third party content. + +chalk (2.4.1) + +* License: MIT +* Project: https://github.com/chalk/chalk +* Source: https://github.com/chalk/chalk + +code copied from project cortex-debug (0.1.21) + +* License: MIT + +Code copied from project Microsoft/vscode (1.31.0) + +* License: MIT + +Code copied from project Microsoft/vscode (1.32.3) + +* License: MIT +* Project: https://code.visualstudio.com/ +* Source: https://github.com/Microsoft/vscode + +dugite (1.52.0) + +* License: MIT + +electron@2.0.14 (2.0.14) + +* License: MIT AND BSD-2-Clause AND Apache-2.0 AND (AFL-2.1 OR BSD-3-Clause) + AND BSD-3-Clause AND ISC AND X11 AND Public-Domain AND (GPL-2.0 OR MIT) AND + Unlicense AND IJG AND ICU AND UNICODE-TOU AND NTP AND (MIT OR BSD-3-Clause) + AND Libpng AND MPL-2.0 AND LGPL-2.1+ +* Project: https://github.com/electron/electron +* Source: https://github.com/electron/electron/releases/tag/v2.0.14 + +GH-3397: Implemented the HTTP-based authentication for Git in Electron. (n/a) + +* License: MIT + +glob promise (3.4.0) + +* License: ISC +* Project: https://github.com/ahmadnassri/glob-promise +* Source: https://github.com/ahmadnassri/glob-promise + +long.js (3.2.0) + +* License: Apache-2.0 + +micromatch (3.1.10) + +* License: MIT +* Project: https://github.com/micromatch/micromatch +* Source: https://github.com/micromatch/micromatch + +monaco-typescript (2.3.0) + +* License: MIT +* Project: https://github.com/Microsoft/monaco-typescript +* Source: https://github.com/Microsoft/monaco-typescript.git + +native-keymap (1.2.5) + +* License: Pending +* Project: https://github.com/Microsoft/node-native-keymap +* Source: https://github.com/Microsoft/node-native-keymap + +node-oniguruma (n/a) + +* License: BSD-2-Clause AND GPL-2.0 WITH Autoconf-exception-2.0 AND + GPL-2.0-or-later WITH libtool-exception AND X11 AND MIT AND Public-Domain + +node.js dependencies for Theia (n/a) + +* License: MIT AND BSD-3-Clause AND ISC AND Apache-2.0 AND BSD-2-Clause AND + Zlib AND X11 AND (BSD-3-Clause OR AFL-2.1) AND CC-By-4.0 AND CC-by-2.5-SA AND + CC0-1.0 AND (BSD-3-Clause OR MPL-2.0) AND Unlicense AND (MIT OR GPL-3.0) AND + (MIT OR GPL-2.0) AND (Apache-2.0 OR + +ps-list (5.0.1) + +* License: MIT +* Project: https://github.com/sindresorhus/ps-list +* Source: https://github.com/sindresorhus/ps-list + +read-pkg (4.0.1) + +* License: MIT +* Project: https://github.com/sindresorhus/read-pkg +* Source: https://github.com/sindresorhus/read-pkg + +requestretry (3.1.0) + +* License: MIT +* Project: https://github.com/FGRibreau/node-request-retry +* Source: https://github.com/FGRibreau/node-request-retry + +rimraf (2.6.2) + +* License: ISC + +textmate/tcl.tmbundle (n/a) + +* License: LicenseRef-Php_Tmbundle + +theia npm node (n/a) + +* License: BSD-2-Clause OR (MIT OR Apache-2.0) AND (AFL-2.1 OR BSD-3-Clause) + AND Apache-2.0 AND Artistic-2.0 AND BSD-3-Clause AND (BSD-3-Clause OR MIT) + AND MPL-2.0 AND CC0-1.0 AND CC-BY-3.0 AND CC-BY-4.0 AND CC-BY-SA-2.5 AND + GPL-2.0 WITH Autoconf-ex + +theia-cpp-extension npm node (n/a) + +* License: BSD-2-Clause OR (MIT OR Apache-2.0) AND MIT AND BSD-3-Clause AND + Zlib AND (MIT OR GPL-3.0) AND OFL-1.1 AND Apache-2.0 AND CC0-1.0 AND + CC-BY-3.0 AND ISC AND MPL-2.0 AND License-Ref-Public-Domain AND BSL-1.0 AND + (AFL-2.1 OR BSD-3.0) AND Unlicense AND Artist + +tslint (5.10.0) + +* License: Apache-2.0 AND MIT +* Project: http://palantir.github.io/tslint/ +* Source: https://github.com/palantir/tslint + +typefox/monaco-language-client (0.5.0) + +* License: MIT + +typescript-formatter (7.2.2) + +* License: MIT +* Project: https://github.com/vvakame/typescript-formatter +* Source: https://github.com/vvakame/typescript-formatter + +VS Code (1.33.0) + +* License: MIT + +vscode (1.26.0) + +* License: MIT + +vscode (1.31.0) + +* License: MIT + +vscode-debugadapter-node (n/a) + +* License: MIT + +vscode-java (0.36.0) + +* License: EPL-1.0 + +vscode-java-debug (0.15.0) + +* License: MIT + +webdriverio (n/a) + +* License: MIT +* Project: http://webdriver.io/ +* Source: https://github.com/webdriverio/webdriverio.git + +when (3.7.8) + +* License: MIT +* Project: https://github.com/cujojs/when +* Source: https://github.com/cujojs/when + +wjordan/browser-path SHA6719d19077b1454bff8b802f9be79cb1b69ebe7e (n/a) + +* License: MIT + +xterm.js (3.9.1) + +* License: MIT + +yargs (12.0.1) + +* License: MIT +* Project: http://yargs.js.org/ +* Source: https://github.com/yargs/yargs + +yeoman environment (2.3.0) + +* License: BSD-2-Clause AND BSD-3-Clause +* Project: https://github.com/yeoman/environment +* Source: https://github.com/yeoman/environment + +yeoman generator (3.0.0) + +* License: BSD-2-Clause AND BSD-3-Clause +* Project: http://yeoman.io +* Source: https://github.com/yeoman/generator + +yeoman-generator (2.0) + +* License: BSD-2-Clause +* Project: http://yeoman.io/ +* Source: https://github.com/yeoman/generator + +yosay (2.0.2) + +* License: BSD-2-Clause +* Project: https://github.com/yeoman/yosay +* Source: https://github.com/yeoman/yosay + +## Cryptography + +Content may contain encryption software. The country in which you are currently +may have restrictions on the import, possession, and use, and/or re-export to +another country, of encryption software. BEFORE using any encryption software, +please check the country's laws, regulations and policies concerning the import, +possession, or use, and re-export of encryption software, to see if this is +permitted. + + +## Electron + +NOTICE: + +Please note Electron combines Chromium and Node.js into a single runtime. +While Electron, Chromium and Node.js are generally licensed under very +permissive MIT and BSD-3-Clause licenses, both Electron and Chromium distribute +FFmpeg. While FFmpeg is under the LGPL-2.1-or-later license it incorporates +several optional parts and optimizations that are covered by the +GPL-2.0-or-later. We understand both Electron and Chromium do not distribute +versions of FFmpeg with GPL content enabled; however, FFmpeg may be configured +enabled to work with proprietary codecs. It is our understanding these +proprietary codecs may be patented; and as a result, may be subject to +licensing fees. + +We strongly recommend downstream consumers verify the type of FFmpeg support +configured and modify as required. More information on instructions to verify +can be found here +https://electronjs.org/docs/development/upgrading-chromium#verify-ffmpeg-support + + diff --git a/README.md b/README.md index 64959f74dff62..bc69ac688f575 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
@@ -22,7 +22,7 @@
-![Theia](/doc/images/theia-screenshot.png) +![Theia](https://raw.githubusercontent.com/theia-ide/theia/master/doc/images/theia-screenshot.png)
@@ -78,9 +78,9 @@ Read below how to engage with Theia community: - __Debugging__ A visual debugger leveraging the Debug Server Protocol defined by the VS Code team. - + - __Plug-in System__ - + A plug-in system supporting VS Code extensions. - __Workbench Improvements__ @@ -92,7 +92,7 @@ Read below how to engage with Theia community: Integration with testing frameworks, based on a Test Adapter Protocol. - __Robustness and Improved UX__ - + We will further work on simplifying Theia and its UI / UX, improve the performance and keep working on bug reports that are rolling in. - __More__ diff --git a/appveyor.yml b/appveyor.yml index 5fb9f4d55e09d..b5c78a4d28df1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ shallow_clone: true environment: matrix: - - nodejs_version: "8" + - nodejs_version: "10" platform: - x64 diff --git a/configs/typedoc-tsconfig.json b/configs/typedoc-tsconfig.json index 802f19c4b6c91..a520eb8c4ce78 100644 --- a/configs/typedoc-tsconfig.json +++ b/configs/typedoc-tsconfig.json @@ -1,9 +1,6 @@ -// This file could be removed when -// https://github.com/TypeStrong/typedoc/issues/844 -// will be integrated in a new typedocs release (should be 0.14.0) { - "extends": "./base.tsconfig", + "extends": "../tsconfig", "compilerOptions": { - "declarationMap": false + "skipLibCheck": true } } diff --git a/configs/typedoc.json b/configs/typedoc.json index 5f7b62a0f8e64..0513fa5779966 100644 --- a/configs/typedoc.json +++ b/configs/typedoc.json @@ -1,7 +1,17 @@ { "ignoreCompilerErrors": true, - "mode": "file", - "out": "docs/api", + "mode": "modules", + "out": "gh-pages/docs/next", "excludeExternals": true, - "includeDeclarations": true + "includeDeclarations": true, + "readme": "README.md", + "module": "commonjs", + "target": "es6", + "hideGenerator": true, + "external-modulemap": ".*\/packages\/([\\w\\-_]+)\/", + "name": "Theia TypeDoc", + "exclude": [ + "**/+(dev-packages|examples|typings)/**/*.ts", + "**/*spec.ts" + ] } diff --git a/dev-packages/application-manager/package.json b/dev-packages/application-manager/package.json index 395abad6c937e..8e4aeff50a5cc 100644 --- a/dev-packages/application-manager/package.json +++ b/dev-packages/application-manager/package.json @@ -1,6 +1,6 @@ { "name": "@theia/application-manager", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia application manager API.", "publishConfig": { "access": "public" @@ -25,17 +25,16 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "dependencies": { - "@theia/application-package": "^0.4.0", + "@theia/application-package": "^0.5.0", "@types/fs-extra": "^4.0.2", "bunyan": "^1.8.10", "circular-dependency-plugin": "^5.0.0", "copy-webpack-plugin": "^4.5.0", "css-loader": "^0.28.1", - "electron": "^2.0.14", + "electron": "^3.1.7", "electron-rebuild": "^1.5.11", "file-loader": "^1.1.11", "font-awesome-webpack": "0.0.5-beta.2", @@ -52,7 +51,7 @@ "worker-loader": "^1.1.1" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/dev-packages/application-manager/src/generator/frontend-generator.ts b/dev-packages/application-manager/src/generator/frontend-generator.ts index 32dcf4d744e63..72da4f5a2e3a7 100644 --- a/dev-packages/application-manager/src/generator/frontend-generator.ts +++ b/dev-packages/application-manager/src/generator/frontend-generator.ts @@ -164,6 +164,8 @@ if (isMaster) { title: applicationName, width: windowState.width, height: windowState.height, + minWidth: 200, + minHeight: 120, x: windowState.x, y: windowState.y, isMaximized: windowState.isMaximized diff --git a/dev-packages/application-manager/src/rebuild.ts b/dev-packages/application-manager/src/rebuild.ts index 6c15d2c2fe8f8..953d1972e6ed9 100644 --- a/dev-packages/application-manager/src/rebuild.ts +++ b/dev-packages/application-manager/src/rebuild.ts @@ -21,7 +21,7 @@ import cp = require('child_process'); export function rebuild(target: 'electron' | 'browser', modules: string[]) { const nodeModulesPath = path.join(process.cwd(), 'node_modules'); const browserModulesPath = path.join(process.cwd(), '.browser_modules'); - const modulesToProcess = modules || ['@theia/node-pty', 'vscode-nsfw', 'find-git-repositories']; + const modulesToProcess = modules || ['@theia/node-pty', 'nsfw', 'find-git-repositories']; if (target === 'electron' && !fs.existsSync(browserModulesPath)) { const dependencies: { diff --git a/dev-packages/application-package/package.json b/dev-packages/application-package/package.json index d65af61cca1d6..c06a69a215039 100644 --- a/dev-packages/application-package/package.json +++ b/dev-packages/application-package/package.json @@ -1,6 +1,6 @@ { "name": "@theia/application-package", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia application package API.", "publishConfig": { "access": "public" @@ -25,8 +25,7 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "dependencies": { "@types/fs-extra": "^4.0.2", @@ -41,7 +40,7 @@ "write-json-file": "^2.2.0" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/dev-packages/cli/package.json b/dev-packages/cli/package.json index 29b5e84ae5463..f294e01e46fca 100644 --- a/dev-packages/cli/package.json +++ b/dev-packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@theia/cli", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia CLI.", "publishConfig": { "access": "public" @@ -27,10 +27,9 @@ "clean": "rimraf lib", "build": "tsc -p compile.tsconfig.json", "watch": "yarn build -w", - "test": "echo 'skip'", - "docs": "echo 'skip'" + "test": "echo 'skip'" }, "dependencies": { - "@theia/application-manager": "^0.4.0" + "@theia/application-manager": "^0.5.0" } } diff --git a/dev-packages/ext-scripts/package.json b/dev-packages/ext-scripts/package.json index bd79948dde333..3739fe49e4f28 100644 --- a/dev-packages/ext-scripts/package.json +++ b/dev-packages/ext-scripts/package.json @@ -1,7 +1,8 @@ { "private": true, "name": "@theia/ext-scripts", - "version": "0.4.0", + "version": "0.5.0", + "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", "description": "NPM scripts for Theia packages.", "files": [ "theiaext" @@ -16,15 +17,12 @@ "build": "echo 'skip'", "watch": "echo 'skip'", "test": "echo 'skip'", - "docs": "echo 'skip'", - "ext:clean": "theiaext compile:clean && theiaext docs:clean && theiaext test:clean", + "ext:clean": "theiaext compile:clean && theiaext test:clean", "ext:build": "concurrently -n compile,lint -c blue,green \"theiaext compile\" \"theiaext lint\"", "ext:compile": "tsc -p compile.tsconfig.json", "ext:compile:clean": "rimraf lib", "ext:lint": "tslint -c ../../configs/build.tslint.json --project compile.tsconfig.json", "ext:watch": "tsc -w -p compile.tsconfig.json", - "ext:docs": "typedoc --tsconfig ../../configs/typedoc-tsconfig.json --options ../../configs/typedoc.json src", - "ext:docs:clean": "rimraf docs/api", "ext:test": "nyc mocha --opts ../../configs/mocha.opts \"./lib/**/*.*spec.js\"", "ext:test:watch": "mocha -w --opts ../../configs/mocha.opts \"./lib/**/*.*spec.js\"", "ext:test:clean": "rimraf .nyc_output && rimraf coverage" diff --git a/doc/Developing.md b/doc/Developing.md index 1cd18de94b9fe..08449a13c92d4 100644 --- a/doc/Developing.md +++ b/doc/Developing.md @@ -44,10 +44,9 @@ For Windows instructions [click here](#building-on-windows). - [Root privileges errors](#root-privileges-errors) ## Prerequisites - - Node.js `>= 8.x`, `< 9.x`. - - Preferably, **use** version `8.11.4`, it has the [active LTS](https://github.com/nodejs/Release). - - Node.js `9.x` is untested. - - Node.js `10.x` is **not** supported yet due to a known issue in [`nsfw`](https://github.com/theia-ide/theia/issues/2009). + - Node.js `>= 10.2.0`. + - Preferably, **use** version `10.15.3`, it has the [active LTS](https://github.com/nodejs/Release). + - Node.js `11.x` is untested. - [Yarn package manager](https://yarnpkg.com/en/docs/install) v1.7.0 - git (If you would like to use the Git-extension too, you will need to have git version 2.11.0 or higher.) diff --git a/doc/images/theia-screenshot.png b/doc/images/theia-screenshot.png index d215a20cbdbf1..1f2d7cfe2919d 100644 Binary files a/doc/images/theia-screenshot.png and b/doc/images/theia-screenshot.png differ diff --git a/examples/browser/package.json b/examples/browser/package.json index a55d22aed55cf..dc208eeb9305d 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -1,59 +1,63 @@ { "private": true, "name": "@theia/example-browser", - "version": "0.4.0", + "version": "0.5.0", + "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", "theia": { "frontend": { "config": { - "applicationName": "Theia Browser Example" + "applicationName": "Theia Browser Example", + "preferences": { + "files.enableTrash": false + } } } }, "dependencies": { - "@theia/callhierarchy": "^0.4.0", - "@theia/console": "^0.4.0", - "@theia/core": "^0.4.0", - "@theia/cpp": "^0.4.0", - "@theia/debug": "^0.4.0", - "@theia/debug-nodejs": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/editor-preview": "^0.4.0", - "@theia/editorconfig": "^0.4.0", - "@theia/extension-manager": "^0.4.0", - "@theia/file-search": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/getting-started": "^0.4.0", - "@theia/git": "^0.4.0", - "@theia/java": "^0.4.0", - "@theia/java-debug": "^0.4.0", - "@theia/json": "^0.4.0", - "@theia/keymaps": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/markers": "^0.4.0", - "@theia/merge-conflicts": "^0.4.0", - "@theia/messages": "^0.4.0", - "@theia/metrics": "^0.4.0", - "@theia/mini-browser": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/navigator": "^0.4.0", - "@theia/outline-view": "^0.4.0", - "@theia/output": "^0.4.0", - "@theia/plugin-ext": "^0.4.0", - "@theia/plugin-ext-vscode": "^0.4.0", - "@theia/preferences": "^0.4.0", - "@theia/preview": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/python": "^0.4.0", - "@theia/search-in-workspace": "^0.4.0", - "@theia/task": "^0.4.0", - "@theia/terminal": "^0.4.0", - "@theia/textmate-grammars": "^0.4.0", - "@theia/tslint": "^0.4.0", - "@theia/typehierarchy": "^0.4.0", - "@theia/typescript": "^0.4.0", - "@theia/userstorage": "^0.4.0", - "@theia/variable-resolver": "^0.4.0", - "@theia/workspace": "^0.4.0" + "@theia/callhierarchy": "^0.5.0", + "@theia/console": "^0.5.0", + "@theia/core": "^0.5.0", + "@theia/cpp": "^0.5.0", + "@theia/debug": "^0.5.0", + "@theia/debug-nodejs": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/editor-preview": "^0.5.0", + "@theia/editorconfig": "^0.5.0", + "@theia/extension-manager": "^0.5.0", + "@theia/file-search": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/getting-started": "^0.5.0", + "@theia/git": "^0.5.0", + "@theia/java": "^0.5.0", + "@theia/java-debug": "^0.5.0", + "@theia/json": "^0.5.0", + "@theia/keymaps": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/markers": "^0.5.0", + "@theia/merge-conflicts": "^0.5.0", + "@theia/messages": "^0.5.0", + "@theia/metrics": "^0.5.0", + "@theia/mini-browser": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/navigator": "^0.5.0", + "@theia/outline-view": "^0.5.0", + "@theia/output": "^0.5.0", + "@theia/plugin-ext": "^0.5.0", + "@theia/plugin-ext-vscode": "^0.5.0", + "@theia/preferences": "^0.5.0", + "@theia/preview": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/python": "^0.5.0", + "@theia/search-in-workspace": "^0.5.0", + "@theia/task": "^0.5.0", + "@theia/terminal": "^0.5.0", + "@theia/textmate-grammars": "^0.5.0", + "@theia/tslint": "^0.5.0", + "@theia/typehierarchy": "^0.5.0", + "@theia/typescript": "^0.5.0", + "@theia/userstorage": "^0.5.0", + "@theia/variable-resolver": "^0.5.0", + "@theia/workspace": "^0.5.0" }, "scripts": { "prepare": "yarn run clean && yarn build", @@ -71,6 +75,6 @@ "coverage": "yarn coverage:compile && yarn test && yarn coverage:remap && yarn coverage:report:lcov && yarn coverage:report:html" }, "devDependencies": { - "@theia/cli": "^0.4.0" + "@theia/cli": "^0.5.0" } } diff --git a/examples/browser/test/left-panel/left-panel.ts b/examples/browser/test/left-panel/left-panel.ts index 3349c63775dde..1e9b9c9ded1ee 100644 --- a/examples/browser/test/left-panel/left-panel.ts +++ b/examples/browser/test/left-panel/left-panel.ts @@ -31,7 +31,7 @@ export class LeftPanel { } openCloseTab(tabName: string) { - this.driver.element('.p-TabBar.theia-app-left .p-TabBar-content').click(`div=${tabName}`); + this.driver.element('.p-TabBar.theia-app-left .p-TabBar-content').element(`div=${tabName}`).click('..'); // Wait for animations to finish this.driver.pause(300); } diff --git a/examples/browser/test/left-panel/left-panel.ui-spec.ts b/examples/browser/test/left-panel/left-panel.ui-spec.ts index c790794164c55..7bca29439dee6 100644 --- a/examples/browser/test/left-panel/left-panel.ui-spec.ts +++ b/examples/browser/test/left-panel/left-panel.ui-spec.ts @@ -33,20 +33,20 @@ before(() => { }); describe('theia left panel', () => { - it("should show 'Files' and 'Git'", () => { - expect(leftPanel.doesTabExist('Files')).to.be.true; + it("should show 'Explorer' and 'Git'", () => { + expect(leftPanel.doesTabExist('Explorer')).to.be.true; expect(leftPanel.doesTabExist('Git')).to.be.true; }); describe('files tab', () => { it('should open/close the files tab', () => { - leftPanel.openCloseTab('Files'); + leftPanel.openCloseTab('Explorer'); expect(leftPanel.isFileTreeVisible()).to.be.true; - expect(leftPanel.isTabActive('Files')).to.be.true; + expect(leftPanel.isTabActive('Explorer')).to.be.true; - leftPanel.openCloseTab('Files'); + leftPanel.openCloseTab('Explorer'); expect(leftPanel.isFileTreeVisible()).to.be.false; - expect(leftPanel.isTabActive('Files')).to.be.false; + expect(leftPanel.isTabActive('Explorer')).to.be.false; }); }); diff --git a/examples/browser/test/right-panel/right-panel.ts b/examples/browser/test/right-panel/right-panel.ts index 6a207ea8e97ac..7a8b2ed96b118 100644 --- a/examples/browser/test/right-panel/right-panel.ts +++ b/examples/browser/test/right-panel/right-panel.ts @@ -31,7 +31,7 @@ export class RightPanel { } openCloseTab(tabName: string) { - this.driver.element('.p-TabBar.theia-app-right .p-TabBar-content').click(`div=${tabName}`); + this.driver.element('.p-TabBar.theia-app-right .p-TabBar-content').element(`div=${tabName}`).click('..'); // Wait for animations to finish this.driver.pause(300); } diff --git a/examples/browser/test/top-panel/top-panel.ts b/examples/browser/test/top-panel/top-panel.ts index 5cd2bf26af8c8..0d6aa6c09d0f4 100644 --- a/examples/browser/test/top-panel/top-panel.ts +++ b/examples/browser/test/top-panel/top-panel.ts @@ -41,7 +41,7 @@ export class TopPanel { toggleFilesView() { this.clickMenuTab('View'); - this.clickSubMenu('Files'); + this.clickSubMenu('Explorer'); } toggleGitView() { diff --git a/examples/electron/package.json b/examples/electron/package.json index d24d36c8c21bd..d649b30b6a571 100644 --- a/examples/electron/package.json +++ b/examples/electron/package.json @@ -1,7 +1,8 @@ { "private": true, "name": "@theia/example-electron", - "version": "0.4.0", + "version": "0.5.0", + "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", "theia": { "target": "electron", "frontend": { @@ -15,48 +16,48 @@ "`@theia/debug`, `@theia/debug-nodejs` and `@theia/java-debug` are removed due to https://github.com/theia-ide/theia/issues/3716" ], "dependencies": { - "@theia/callhierarchy": "^0.4.0", - "@theia/console": "^0.4.0", - "@theia/core": "^0.4.0", - "@theia/cpp": "^0.4.0", - "@theia/debug": "^0.4.0", - "@theia/debug-nodejs": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/editor-preview": "^0.4.0", - "@theia/editorconfig": "^0.4.0", - "@theia/extension-manager": "^0.4.0", - "@theia/file-search": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/getting-started": "^0.4.0", - "@theia/git": "^0.4.0", - "@theia/java": "^0.4.0", - "@theia/java-debug": "^0.4.0", - "@theia/json": "^0.4.0", - "@theia/keymaps": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/markers": "^0.4.0", - "@theia/merge-conflicts": "^0.4.0", - "@theia/messages": "^0.4.0", - "@theia/metrics": "^0.4.0", - "@theia/mini-browser": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/navigator": "^0.4.0", - "@theia/outline-view": "^0.4.0", - "@theia/output": "^0.4.0", - "@theia/preferences": "^0.4.0", - "@theia/preview": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/python": "^0.4.0", - "@theia/search-in-workspace": "^0.4.0", - "@theia/task": "^0.4.0", - "@theia/terminal": "^0.4.0", - "@theia/textmate-grammars": "^0.4.0", - "@theia/tslint": "^0.4.0", - "@theia/typehierarchy": "^0.4.0", - "@theia/typescript": "^0.4.0", - "@theia/userstorage": "^0.4.0", - "@theia/variable-resolver": "^0.4.0", - "@theia/workspace": "^0.4.0" + "@theia/callhierarchy": "^0.5.0", + "@theia/console": "^0.5.0", + "@theia/core": "^0.5.0", + "@theia/cpp": "^0.5.0", + "@theia/debug": "^0.5.0", + "@theia/debug-nodejs": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/editor-preview": "^0.5.0", + "@theia/editorconfig": "^0.5.0", + "@theia/extension-manager": "^0.5.0", + "@theia/file-search": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/getting-started": "^0.5.0", + "@theia/git": "^0.5.0", + "@theia/java": "^0.5.0", + "@theia/java-debug": "^0.5.0", + "@theia/json": "^0.5.0", + "@theia/keymaps": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/markers": "^0.5.0", + "@theia/merge-conflicts": "^0.5.0", + "@theia/messages": "^0.5.0", + "@theia/metrics": "^0.5.0", + "@theia/mini-browser": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/navigator": "^0.5.0", + "@theia/outline-view": "^0.5.0", + "@theia/output": "^0.5.0", + "@theia/preferences": "^0.5.0", + "@theia/preview": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/python": "^0.5.0", + "@theia/search-in-workspace": "^0.5.0", + "@theia/task": "^0.5.0", + "@theia/terminal": "^0.5.0", + "@theia/textmate-grammars": "^0.5.0", + "@theia/tslint": "^0.5.0", + "@theia/typehierarchy": "^0.5.0", + "@theia/typescript": "^0.5.0", + "@theia/userstorage": "^0.5.0", + "@theia/variable-resolver": "^0.5.0", + "@theia/workspace": "^0.5.0" }, "scripts": { "prepare": "yarn run clean && yarn build", @@ -69,6 +70,6 @@ "test:ui": "wdio wdio.conf.js" }, "devDependencies": { - "@theia/cli": "^0.4.0" + "@theia/cli": "^0.5.0" } } diff --git a/lerna.json b/lerna.json index 608c50a8eccb0..5c8251fdc05b4 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "lerna": "2.2.0", "npmClient": "yarn", "useWorkspaces": true, - "version": "0.4.0", + "version": "0.5.0", "command": { "run": { "stream": true diff --git a/package.json b/package.json index 2b4dcfa67989f..90cb8f75ba8f6 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "version": "0.0.0", "engines": { "yarn": "1.0.x || >=1.2.1", - "node": ">=8.9.3" + "node": ">=10.2.0" }, "resolution": { - "**/@types/node": "8.10.20" + "**/@types/node": "~10.3.6" }, "devDependencies": { "@types/chai": "^4.0.1", "@types/chai-string": "^1.4.0", "@types/jsdom": "^11.0.4", "@types/mocha": "^2.2.41", - "@types/node": "8.10.20", + "@types/node": "~10.3.6", "@types/sinon": "^2.3.5", "@types/temp": "^0.8.29", "@types/webdriverio": "^4.7.0", @@ -35,10 +35,11 @@ "temp": "^0.8.3", "tslint": "^5.12.0", "tslint-language-service": "^0.9.9", - "typedoc": "^0.13.0", + "typedoc": "^0.15.0-0", + "typedoc-plugin-external-module-map": "^1.0.0", "typescript": "^3.1.3", "uuid": "^3.1.0", - "wdio-mocha-framework": "0.5.13", + "wdio-mocha-framework": "0.6.4", "wdio-selenium-standalone-service": "0.0.12", "wdio-spec-reporter": "0.1.5", "webdriverio": "4.14.1" @@ -49,7 +50,7 @@ "prepare:hoisting": "theia check:hoisted -s", "build": "run build", "build:clean": "run prepare", - "docs": "run docs \"@theia/!(example-)*\"", + "docs": "rimraf gh-pages/docs/next && typedoc --tsconfig configs/typedoc-tsconfig.json --options configs/typedoc.json", "test": "yarn test:theia && yarn test:electron && yarn test:browser", "test:theia": "run test \"@theia/!(example-)*\" --stream --concurrency=1", "test:browser": "yarn rebuild:browser && run test \"@theia/example-browser\"", diff --git a/packages/bunyan/package.json b/packages/bunyan/package.json index e56f7aa74fec8..bf75a38647626 100644 --- a/packages/bunyan/package.json +++ b/packages/bunyan/package.json @@ -1,9 +1,9 @@ { "name": "@theia/bunyan", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - bunyan Logger Extension", "dependencies": { - "@theia/core": "^0.4.0", + "@theia/core": "^0.5.0", "@types/bunyan": "^1.8.0", "bunyan": "^1.8.10" }, @@ -36,11 +36,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/bunyan/src/node/bunyan-logger-server.ts b/packages/bunyan/src/node/bunyan-logger-server.ts index 7631fee637a5a..c0eee1a1d2782 100644 --- a/packages/bunyan/src/node/bunyan-logger-server.ts +++ b/packages/bunyan/src/node/bunyan-logger-server.ts @@ -68,7 +68,7 @@ export class BunyanLoggerServer implements ILoggerServer { * documentation. */ child(name: string): Promise { if (name.length === 0) { - return Promise.reject("Can't create a logger with an empty name."); + return Promise.reject(new Error("Can't create a logger with an empty name.")); } if (this.loggers.has(name)) { diff --git a/packages/callhierarchy/package.json b/packages/callhierarchy/package.json index ab55533d66d02..ceed0fe17127f 100644 --- a/packages/callhierarchy/package.json +++ b/packages/callhierarchy/package.json @@ -1,12 +1,12 @@ { "name": "@theia/callhierarchy", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Call Hierarchy Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/monaco": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/monaco": "^0.5.0", "ts-md5": "^1.2.2" }, "publishConfig": { @@ -38,11 +38,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/callhierarchy/src/browser/callhierarchy-tree/callhierarchy-tree-widget.tsx b/packages/callhierarchy/src/browser/callhierarchy-tree/callhierarchy-tree-widget.tsx index 6a732046647d4..a40e992cb2026 100644 --- a/packages/callhierarchy/src/browser/callhierarchy-tree/callhierarchy-tree-widget.tsx +++ b/packages/callhierarchy/src/browser/callhierarchy-tree/callhierarchy-tree-widget.tsx @@ -102,11 +102,13 @@ export class CallHierarchyTreeWidget extends TreeWidget { const container = (containerName) ? containerName + ' — ' + location : location; return
-
- {symbol} -
-
- {container} +
+ + {symbol} + + + {container} +
; } @@ -120,14 +122,16 @@ export class CallHierarchyTreeWidget extends TreeWidget { const container = (containerName) ? containerName + ' — ' + location : location; return
-
- {symbol} -
-
- {(referenceCount > 1) ? `[${referenceCount}]` : ''} -
-
- {container} +
+ + {symbol} + + + {(referenceCount > 1) ? `[${referenceCount}]` : ''} + + + {container} +
; } diff --git a/packages/callhierarchy/src/browser/style/index.css b/packages/callhierarchy/src/browser/style/index.css index a556ba015c15f..d82492f8926db 100644 --- a/packages/callhierarchy/src/browser/style/index.css +++ b/packages/callhierarchy/src/browser/style/index.css @@ -24,11 +24,12 @@ } .theia-CallHierarchyTree .theia-ExpansionToggle { - min-width: 16px; + min-width: 9px; + padding-right: 4px; } .theia-CallHierarchyTree .noCallers { - margin: 5px; + margin: 5px 19px; } .theia-CallHierarchyTree .definitionNode { @@ -44,23 +45,23 @@ } .theia-CallHierarchyTree .definitionNode .symbol { - white-space: nowrap; - overflow: hidden; + padding-right: 4px; } .theia-CallHierarchyTree .definitionNode .referenceCount { - white-space: nowrap; - overflow: hidden; color: var(--theia-ui-font-color3); } .theia-CallHierarchyTree .definitionNode .container { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; color: var(--theia-ui-font-color2); } .call-hierarchy-tab-icon::before { content: "\f0ab" } + +.theia-CallHierarchyTree .definitionNode-content { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/packages/console/package.json b/packages/console/package.json index b62e4eb70fdc3..f85193c014c5e 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,10 +1,10 @@ { "name": "@theia/console", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Console Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/monaco": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/monaco": "^0.5.0", "anser": "^1.4.7" }, "publishConfig": { @@ -36,11 +36,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/core/package.json b/packages/core/package.json index f0a3815d8c3d3..1b0f953c112e4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,12 +1,12 @@ { "name": "@theia/core", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia is a cloud & desktop IDE framework implemented in TypeScript.", "main": "lib/common/index.js", "typings": "lib/common/index.d.ts", "dependencies": { "@phosphor/widgets": "^1.5.0", - "@theia/application-package": "^0.4.0", + "@theia/application-package": "^0.5.0", "@types/body-parser": "^1.16.4", "@types/bunyan": "^1.8.0", "@types/express": "^4.16.0", @@ -20,7 +20,7 @@ "@types/yargs": "^11.1.0", "ajv": "^6.5.3", "body-parser": "^1.17.2", - "electron": "^2.0.14", + "electron": "^3.1.7", "electron-store": "^2.0.0", "es6-promise": "^4.2.4", "express": "^4.16.3", @@ -31,6 +31,7 @@ "inversify": "^4.14.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", + "nsfw": "^1.2.2", "perfect-scrollbar": "^1.3.0", "react": "^16.4.1", "react-dom": "^16.4.1", @@ -39,7 +40,6 @@ "reflect-metadata": "^0.1.10", "route-parser": "^0.0.5", "vscode-languageserver-types": "^3.10.0", - "vscode-nsfw": "^1.0.17", "vscode-uri": "^1.0.1", "vscode-ws-jsonrpc": "^0.0.2-1", "ws": "^5.2.2", @@ -79,11 +79,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index 8ff0b10ceec1f..9c3510ce9bb87 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -30,7 +30,7 @@ import { AboutDialog } from './about-dialog'; import * as browser from './browser'; import URI from '../common/uri'; import { ContextKeyService } from './context-key-service'; -import { OS } from '../common/os'; +import { OS, isOSX } from '../common/os'; import { ResourceContextKey } from './resource-context-key'; import { UriSelection } from '../common/selection'; @@ -216,6 +216,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi this.contextKeyService.createKey('isWindows', OS.type() === OS.Type.Windows); this.initResourceContextKeys(); + this.registerCtrlWHandling(); } protected initResourceContextKeys(): void { @@ -580,12 +581,36 @@ export class CommonFrontendContribution implements FrontendApplicationContributi this.aboutDialog.open(); } + protected shouldPreventClose = false; + + /** + * registers event listener which make sure that + * window doesn't get closed if CMD/CTRL W is pressed. + * Too many users have that in their muscle memory. + * Chrome doesn't let us rebind or prevent default the keybinding, so this + * at least doesn't close the window immediately. + */ + protected registerCtrlWHandling() { + function isCtrlCmd(event: KeyboardEvent) { + return (isOSX && event.metaKey) || (!isOSX && event.ctrlKey); + } + + window.document.addEventListener('keydown', event => { + this.shouldPreventClose = isCtrlCmd(event) || event.code === 'KeyW'; + }); + + window.document.addEventListener('keyup', () => { + this.shouldPreventClose = false; + }); + } + onWillStop() { - if (this.shell.canSaveAll()) { - setTimeout(() => { - this.messageService.info('Some documents should be saved, data will be lost otherwise.'); - }); - return true; + try { + if (this.shouldPreventClose || this.shell.canSaveAll()) { + return true; + } + } finally { + this.shouldPreventClose = false; } } } diff --git a/packages/core/src/browser/dialogs.ts b/packages/core/src/browser/dialogs.ts index a8ad83b270e59..8f25d23995f26 100644 --- a/packages/core/src/browser/dialogs.ts +++ b/packages/core/src/browser/dialogs.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable, inject } from 'inversify'; -import { Disposable } from '../common'; +import { Disposable, MaybePromise, CancellationTokenSource } from '../common'; import { Key } from './keys'; import { Widget, BaseWidget, Message } from './widgets'; @@ -168,7 +168,7 @@ export abstract class AbstractDialog extends BaseWidget { open(): Promise { if (this.resolve) { - return Promise.reject('The dialog is already opened.'); + return Promise.reject(new Error('The dialog is already opened.')); } this.activeElement = window.document.activeElement as HTMLElement; return new Promise((resolve, reject) => { @@ -194,31 +194,45 @@ export abstract class AbstractDialog extends BaseWidget { this.activeElement = undefined; super.close(); } - protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.validate(); } - protected validate(): void { + protected validateCancellationSource = new CancellationTokenSource(); + protected async validate(): Promise { if (!this.resolve) { return; } + this.validateCancellationSource.cancel(); + this.validateCancellationSource = new CancellationTokenSource(); + const token = this.validateCancellationSource.token; const value = this.value; - const error = this.isValid(value, 'preview'); + const error = await this.isValid(value, 'preview'); + if (token.isCancellationRequested) { + return; + } this.setErrorMessage(error); } - protected accept(): void { - if (this.resolve) { - const value = this.value; - const error = this.isValid(value, 'open'); - if (!DialogError.getResult(error)) { - this.setErrorMessage(error); - } else { - this.resolve(value); - Widget.detach(this); - } + protected acceptCancellationSource = new CancellationTokenSource(); + protected async accept(): Promise { + if (!this.resolve) { + return; + } + this.acceptCancellationSource.cancel(); + this.acceptCancellationSource = new CancellationTokenSource(); + const token = this.acceptCancellationSource.token; + const value = this.value; + const error = await this.isValid(value, 'open'); + if (token.isCancellationRequested) { + return; + } + if (!DialogError.getResult(error)) { + this.setErrorMessage(error); + } else { + this.resolve(value); + Widget.detach(this); } } @@ -227,7 +241,7 @@ export abstract class AbstractDialog extends BaseWidget { /** * Return a string of zero-length or true if valid. */ - protected isValid(value: T, mode: DialogMode): DialogError { + protected isValid(value: T, mode: DialogMode): MaybePromise { return ''; } @@ -299,7 +313,7 @@ export class SingleTextInputDialogProps extends DialogProps { end: number direction?: 'forward' | 'backward' | 'none' }; - readonly validate?: (input: string, mode: DialogMode) => DialogError; + readonly validate?: (input: string, mode: DialogMode) => MaybePromise; } export class SingleTextInputDialog extends AbstractDialog { @@ -333,7 +347,7 @@ export class SingleTextInputDialog extends AbstractDialog { return this.inputField.value; } - protected isValid(value: string, mode: DialogMode): DialogError { + protected isValid(value: string, mode: DialogMode): MaybePromise { if (this.props.validate) { return this.props.validate(value, mode); } diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index 052e5f74e7e91..eeff9e95a0a4e 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -44,7 +44,9 @@ import { LocalStorageService, StorageService } from './storage-service'; import { WidgetFactory, WidgetManager } from './widget-manager'; import { ApplicationShell, ApplicationShellOptions, DockPanelRenderer, TabBarRenderer, - TabBarRendererFactory, ShellLayoutRestorer, SidePanelHandler, SidePanelHandlerFactory, SplitPositionHandler, DockPanelRendererFactory + TabBarRendererFactory, ShellLayoutRestorer, + SidePanelHandler, SidePanelHandlerFactory, + SplitPositionHandler, DockPanelRendererFactory } from './shell'; import { StatusBar, StatusBarImpl } from './status-bar/status-bar'; import { LabelParser } from './label-parser'; diff --git a/packages/core/src/browser/frontend-application.ts b/packages/core/src/browser/frontend-application.ts index f33184cabce3f..4ee111a39809d 100644 --- a/packages/core/src/browser/frontend-application.ts +++ b/packages/core/src/browser/frontend-application.ts @@ -171,7 +171,7 @@ export class FrontendApplication { document.addEventListener('keydown', event => this.keybindings.run(event), true); // Prevent forward/back navigation by scrolling in OS X if (isOSX) { - document.body.addEventListener('wheel', preventNavigation); + document.body.addEventListener('wheel', preventNavigation, { passive: false }); } } diff --git a/packages/core/src/browser/icons/open-change-bright.svg b/packages/core/src/browser/icons/open-change-bright.svg new file mode 100644 index 0000000000000..8c2b0f890b0f7 --- /dev/null +++ b/packages/core/src/browser/icons/open-change-bright.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/core/src/browser/icons/open-change-dark.svg b/packages/core/src/browser/icons/open-change-dark.svg new file mode 100644 index 0000000000000..4e655f075e133 --- /dev/null +++ b/packages/core/src/browser/icons/open-change-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/src/browser/icons/preview-bright.svg b/packages/core/src/browser/icons/preview-bright.svg new file mode 100644 index 0000000000000..d4282dc517c65 --- /dev/null +++ b/packages/core/src/browser/icons/preview-bright.svg @@ -0,0 +1,4 @@ + + + +PreviewInRightPanel_16x \ No newline at end of file diff --git a/packages/core/src/browser/icons/preview-dark.svg b/packages/core/src/browser/icons/preview-dark.svg new file mode 100644 index 0000000000000..f65df35e141d8 --- /dev/null +++ b/packages/core/src/browser/icons/preview-dark.svg @@ -0,0 +1,4 @@ + + + +PreviewInRightPanel_16x \ No newline at end of file diff --git a/packages/core/src/browser/messaging/ws-connection-provider.ts b/packages/core/src/browser/messaging/ws-connection-provider.ts index fec8cb55464ed..e6e4f2ab4e522 100644 --- a/packages/core/src/browser/messaging/ws-connection-provider.ts +++ b/packages/core/src/browser/messaging/ws-connection-provider.ts @@ -14,13 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, interfaces } from 'inversify'; +import { injectable, interfaces, decorate, unmanaged } from 'inversify'; import { createWebSocketConnection, Logger, ConsoleLogger } from 'vscode-ws-jsonrpc/lib'; import { ConnectionHandler, JsonRpcProxyFactory, JsonRpcProxy, Emitter, Event } from '../../common'; import { WebSocketChannel } from '../../common/messaging/web-socket-channel'; import { Endpoint } from '../endpoint'; const ReconnectingWebSocket = require('reconnecting-websocket'); +decorate(injectable(), JsonRpcProxyFactory); +decorate(unmanaged(), JsonRpcProxyFactory, 0); + export interface WebSocketOptions { /** * True by default. @@ -31,8 +34,21 @@ export interface WebSocketOptions { @injectable() export class WebSocketConnectionProvider { - static createProxy(container: interfaces.Container, path: string, target?: object): JsonRpcProxy { - return container.get(WebSocketConnectionProvider).createProxy(path, target); + /** + * Create a proxy object to remote interface of T type + * over a web socket connection for the given path and proxy factory. + */ + static createProxy(container: interfaces.Container, path: string, factory: JsonRpcProxyFactory): JsonRpcProxy; + /** + * Create a proxy object to remote interface of T type + * over a web socket connection for the given path. + * + * An optional target can be provided to handle + * notifications and requests from a remote side. + */ + static createProxy(container: interfaces.Container, path: string, target?: object): JsonRpcProxy; + static createProxy(container: interfaces.Container, path: string, arg?: object): JsonRpcProxy { + return container.get(WebSocketConnectionProvider).createProxy(path, arg); } protected channelIdSeq = 0; @@ -64,6 +80,11 @@ export class WebSocketConnectionProvider { this.socket = socket; } + /** + * Create a proxy object to remote interface of T type + * over a web socket connection for the given path and proxy factory. + */ + createProxy(path: string, factory: JsonRpcProxyFactory): JsonRpcProxy; /** * Create a proxy object to remote interface of T type * over a web socket connection for the given path. @@ -71,8 +92,9 @@ export class WebSocketConnectionProvider { * An optional target can be provided to handle * notifications and requests from a remote side. */ - createProxy(path: string, target?: object): JsonRpcProxy { - const factory = new JsonRpcProxyFactory(target); + createProxy(path: string, target?: object): JsonRpcProxy; + createProxy(path: string, arg?: object): JsonRpcProxy { + const factory = arg instanceof JsonRpcProxyFactory ? arg : new JsonRpcProxyFactory(arg); this.listen({ path, onConnection: c => factory.listen(c) diff --git a/packages/core/src/browser/opener-service.ts b/packages/core/src/browser/opener-service.ts index d3ef9325f365f..5739b83870093 100644 --- a/packages/core/src/browser/opener-service.ts +++ b/packages/core/src/browser/opener-service.ts @@ -95,7 +95,7 @@ export class DefaultOpenerService implements OpenerService { if (handlers.length >= 1) { return handlers[0]; } - return Promise.reject(`There is no opener for ${uri}.`); + return Promise.reject(new Error(`There is no opener for ${uri}.`)); } async getOpeners(uri?: URI, options?: OpenerOptions): Promise { diff --git a/packages/core/src/browser/preferences/preference-contribution.ts b/packages/core/src/browser/preferences/preference-contribution.ts index 8bfa705519c00..2f6471f208d7e 100644 --- a/packages/core/src/browser/preferences/preference-contribution.ts +++ b/packages/core/src/browser/preferences/preference-contribution.ts @@ -20,6 +20,13 @@ import { ContributionProvider, bindContributionProvider, escapeRegExpCharacters, import { PreferenceScope } from './preference-scope'; import { PreferenceProvider, PreferenceProviderPriority, PreferenceProviderDataChange } from './preference-provider'; +import { + PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty, JsonType +} from '../../common/preferences/preference-schema'; +import { FrontendApplicationConfigProvider } from '../frontend-application-config-provider'; +import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props'; +export { PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty, JsonType }; + // tslint:disable:no-any // tslint:disable:forin @@ -28,73 +35,6 @@ export interface PreferenceContribution { readonly schema: PreferenceSchema; } -export interface PreferenceSchema { - [name: string]: any, - scope?: 'application' | 'window' | 'resource' | PreferenceScope, - overridable?: boolean; - properties: PreferenceSchemaProperties -} -export namespace PreferenceSchema { - export function getDefaultScope(schema: PreferenceSchema): PreferenceScope { - let defaultScope: PreferenceScope = PreferenceScope.Workspace; - if (!PreferenceScope.is(schema.scope)) { - defaultScope = PreferenceScope.fromString(schema.scope) || PreferenceScope.Workspace; - } else { - defaultScope = schema.scope; - } - return defaultScope; - } -} - -export interface PreferenceSchemaProperties { - [name: string]: PreferenceSchemaProperty -} - -export interface PreferenceDataSchema { - [name: string]: any, - scope?: PreferenceScope, - properties: { - [name: string]: PreferenceDataProperty - } - patternProperties: { - [name: string]: PreferenceDataProperty - }; -} - -export interface PreferenceItem { - type?: JsonType | JsonType[]; - minimum?: number; - default?: any; - enum?: string[]; - items?: PreferenceItem; - properties?: { [name: string]: PreferenceItem }; - additionalProperties?: object; - [name: string]: any; - overridable?: boolean; -} - -export interface PreferenceSchemaProperty extends PreferenceItem { - description?: string; - scope?: 'application' | 'window' | 'resource' | PreferenceScope; -} - -export interface PreferenceDataProperty extends PreferenceItem { - description?: string; - scope?: PreferenceScope; -} -export namespace PreferenceDataProperty { - export function fromPreferenceSchemaProperty(schemaProps: PreferenceSchemaProperty, defaultScope: PreferenceScope = PreferenceScope.Workspace): PreferenceDataProperty { - if (!schemaProps.scope) { - schemaProps.scope = defaultScope; - } else if (typeof schemaProps.scope === 'string') { - return Object.assign(schemaProps, { scope: PreferenceScope.fromString(schemaProps.scope) || defaultScope }); - } - return schemaProps; - } -} - -export type JsonType = 'string' | 'array' | 'number' | 'integer' | 'object' | 'boolean' | 'null'; - export function bindPreferenceSchemaProvider(bind: interfaces.Bind): void { bind(PreferenceSchemaProvider).toSelf().inSingletonScope(); bindContributionProvider(bind, PreferenceContribution); @@ -110,6 +50,17 @@ export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY); const OVERRIDE_PATTERN_WITH_SUBSTITUTION = '\\[(${0})\\]$'; +export interface FrontendApplicationPreferenceConfig extends FrontendApplicationConfig { + preferences: { + [preferenceName: string]: any + } +} +export namespace FrontendApplicationPreferenceConfig { + export function is(config: FrontendApplicationConfig): config is FrontendApplicationPreferenceConfig { + return 'preferences' in config && typeof config['preferences'] === 'object'; + } +} + @injectable() export class PreferenceSchemaProvider extends PreferenceProvider { @@ -196,16 +147,39 @@ export class PreferenceSchemaProvider extends PreferenceProvider { if (schemaProps.overridable) { this.overridePatternProperties.properties[preferenceName] = schemaProps; } - const newValue = schemaProps.default = this.getDefaultValue(schemaProps); this.combinedSchema.properties[preferenceName] = schemaProps; - this.preferences[preferenceName] = newValue; - changes.push({ preferenceName, newValue, scope, domain }); + + const value = schemaProps.default = this.getDefaultValue(schemaProps, preferenceName); + if (this.testOverrideValue(preferenceName, value)) { + for (const overridenPreferenceName in value) { + const overrideValue = value[overridenPreferenceName]; + const overridePreferenceName = `${preferenceName}.${overridenPreferenceName}`; + changes.push(this.doSetPreferenceValue(overridePreferenceName, overrideValue, { scope, domain })); + } + } else { + changes.push(this.doSetPreferenceValue(preferenceName, value, { scope, domain })); + } } } return changes; } + protected doSetPreferenceValue(preferenceName: string, newValue: any, { scope, domain }: { + scope: PreferenceScope, + domain: string[] + }): PreferenceProviderDataChange { + const oldValue = this.preferences[preferenceName]; + this.preferences[preferenceName] = newValue; + return { preferenceName, oldValue, newValue, scope, domain }; + } - protected getDefaultValue(property: PreferenceItem): any { + /** @deprecated since 0.6.0 pass preferenceName as the second arg */ + protected getDefaultValue(property: PreferenceItem): any; + protected getDefaultValue(property: PreferenceItem, preferenceName: string): any; + protected getDefaultValue(property: PreferenceItem, preferenceName?: string): any { + const config = FrontendApplicationConfigProvider.get(); + if (preferenceName && FrontendApplicationPreferenceConfig.is(config) && preferenceName in config.preferences) { + return config.preferences[preferenceName]; + } if (property.default) { return property.default; } @@ -305,7 +279,7 @@ export class PreferenceSchemaProvider extends PreferenceProvider { return { preferenceName, overrideIdentifier }; } - testOverrideValue(name: string, value: any): boolean { - return typeof value === 'object' && OVERRIDE_PROPERTY_PATTERN.test(name); + testOverrideValue(name: string, value: any): value is PreferenceSchemaProperties { + return PreferenceSchemaProperties.is(value) && OVERRIDE_PROPERTY_PATTERN.test(name); } } diff --git a/packages/core/src/browser/preferences/preference-scope.ts b/packages/core/src/browser/preferences/preference-scope.ts index e05e001b69ca9..4625439147eb5 100644 --- a/packages/core/src/browser/preferences/preference-scope.ts +++ b/packages/core/src/browser/preferences/preference-scope.ts @@ -14,52 +14,5 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -// tslint:disable:no-any - -export enum PreferenceScope { - Default, - User, - Workspace, - Folder -} - -export namespace PreferenceScope { - export function is(scope: any): scope is PreferenceScope { - return typeof scope === 'number' && getScopes().findIndex(s => s === scope) >= 0; - } - - export function getScopes(): PreferenceScope[] { - return Object.keys(PreferenceScope) - .filter(k => typeof PreferenceScope[k as any] === 'string') - .map(v => Number(v)); - } - - export function getReversedScopes(): PreferenceScope[] { - return getScopes().reverse(); - } - - export function getScopeNames(scope?: PreferenceScope): string[] { - const names: string[] = []; - const allNames = Object.keys(PreferenceScope) - .filter(k => typeof PreferenceScope[k as any] === 'number'); - if (scope) { - for (const name of allNames) { - if ((PreferenceScope)[name] <= scope) { - names.push(name); - } - } - } - return names; - } - - export function fromString(strScope: string): PreferenceScope | undefined { - switch (strScope) { - case 'application': - return PreferenceScope.User; - case 'window': - return PreferenceScope.Workspace; - case 'resource': - return PreferenceScope.Folder; - } - } -} +import { PreferenceScope } from '../../common/preferences/preference-scope'; +export { PreferenceScope }; diff --git a/packages/core/src/browser/quick-open/index.ts b/packages/core/src/browser/quick-open/index.ts index 8652830a95c3e..cf5bd3fa0e1b7 100644 --- a/packages/core/src/browser/quick-open/index.ts +++ b/packages/core/src/browser/quick-open/index.ts @@ -15,6 +15,7 @@ ********************************************************************************/ export * from './quick-open-model'; +export * from './quick-open-action-provider'; export * from './quick-open-service'; export * from './quick-pick-service'; export * from './quick-input-service'; diff --git a/packages/core/src/browser/quick-open/quick-open-action-provider.ts b/packages/core/src/browser/quick-open/quick-open-action-provider.ts new file mode 100644 index 0000000000000..d5deda316dccf --- /dev/null +++ b/packages/core/src/browser/quick-open/quick-open-action-provider.ts @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. 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 { Disposable } from '../../common/disposable'; +import { injectable } from 'inversify'; +import { QuickOpenItem } from './quick-open-model'; + +export interface QuickOpenActionProvider { + hasActions(item: QuickOpenItem): boolean; + getActions(item: QuickOpenItem): Promise; +} + +export interface QuickOpenActionOptions { + id: string; + label?: string; + tooltip?: string; + class?: string | undefined; + enabled?: boolean; + checked?: boolean; + radio?: boolean; +} + +export interface QuickOpenAction extends QuickOpenActionOptions, Disposable { + run(item?: QuickOpenItem): PromiseLike; +} + +@injectable() +export abstract class QuickOpenBaseAction implements QuickOpenAction { + constructor(protected options: QuickOpenActionOptions) { + } + + get id(): string { + return this.options.id; + } + + get label(): string { + return this.options.label || ''; + } + + set label(value: string) { + this.options.label = value; + } + + get tooltip(): string { + return this.options.tooltip || ''; + } + + set tooltip(value: string) { + this.options.tooltip = value; + } + + get class(): string | undefined { + return this.options.class || ''; + } + + set class(value: string | undefined) { + this.options.class = value; + } + + get enabled(): boolean { + return this.options.enabled || true; + } + + set enabled(value: boolean) { + this.options.enabled = value; + } + + get checked(): boolean { + return this.options.checked || false; + } + + set checked(value: boolean) { + this.options.checked = value; + } + + get radio(): boolean { + return this.options.radio || false; + } + + set radio(value: boolean) { + this.options.radio = value; + } + + abstract run(item?: QuickOpenItem): PromiseLike; + + dispose(): void { } +} diff --git a/packages/core/src/browser/quick-open/quick-open-model.ts b/packages/core/src/browser/quick-open/quick-open-model.ts index 970fb80b981b5..7b66e1046d456 100644 --- a/packages/core/src/browser/quick-open/quick-open-model.ts +++ b/packages/core/src/browser/quick-open/quick-open-model.ts @@ -16,6 +16,7 @@ import URI from '../../common/uri'; import { Keybinding } from '../keybinding'; +import { QuickOpenActionProvider } from './quick-open-action-provider'; export interface Highlight { start: number @@ -105,5 +106,5 @@ export class QuickOpenGroupItem void): void; + onType(lookFor: string, acceptor: (items: QuickOpenItem[], actionProvider?: QuickOpenActionProvider) => void): void; } diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts index a372eb34661ee..8a946539214b6 100644 --- a/packages/core/src/browser/shell/application-shell.ts +++ b/packages/core/src/browser/shell/application-shell.ts @@ -1095,7 +1095,7 @@ export class ApplicationShell extends Widget { alignment: StatusBarAlignment.RIGHT, tooltip: 'Toggle Bottom Panel', command: 'core.toggle.bottom.panel', - priority: 0 + priority: -1000 }; this.statusBar.setElement(BOTTOM_PANEL_TOGGLE_ID, element); } @@ -1444,19 +1444,19 @@ export namespace ApplicationShell { bottomPanel: Object.freeze({ emptySize: 140, expandThreshold: 160, - expandDuration: 150, + expandDuration: 0, initialSizeRatio: 0.382 }), leftPanel: Object.freeze({ emptySize: 140, expandThreshold: 140, - expandDuration: 150, + expandDuration: 0, initialSizeRatio: 0.191 }), rightPanel: Object.freeze({ emptySize: 140, expandThreshold: 140, - expandDuration: 150, + expandDuration: 0, initialSizeRatio: 0.191 }) }); diff --git a/packages/core/src/browser/shell/side-panel-handler.ts b/packages/core/src/browser/shell/side-panel-handler.ts index 29bd26ad3530a..255edf49f755b 100644 --- a/packages/core/src/browser/shell/side-panel-handler.ts +++ b/packages/core/src/browser/shell/side-panel-handler.ts @@ -24,6 +24,8 @@ import { TabBarRendererFactory, TabBarRenderer, SHELL_TABBAR_CONTEXT_MENU, SideT import { SplitPositionHandler, SplitPositionOptions } from './split-panels'; import { FrontendApplicationStateService } from '../frontend-application-state'; import { TheiaDockPanel } from './theia-dock-panel'; +import { SidePanelToolbar } from './side-panel-toolbar'; +import { TabBarToolbarRegistry, TabBarToolbarFactory, TabBarToolbar } from './tab-bar-toolbar'; /** The class name added to the left and right area panels. */ export const LEFT_RIGHT_AREA_CLASS = 'theia-app-sides'; @@ -56,6 +58,10 @@ export class SidePanelHandler { * tab bar itself remains visible as long as there is at least one widget. */ tabBar: SideTabBar; + /** + * A tool bar, which displays a title and widget specific command buttons. + */ + toolBar: SidePanelToolbar; /** * The widget container is a dock panel in `single-document` mode, which means that the panel * cannot be split. @@ -85,6 +91,8 @@ export class SidePanelHandler { */ protected options: SidePanel.Options; + @inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry; + @inject(TabBarToolbarFactory) protected tabBarToolBarFactory: () => TabBarToolbar; @inject(TabBarRendererFactory) protected tabBarRendererFactory: () => TabBarRenderer; @inject(SplitPositionHandler) protected splitPositionHandler: SplitPositionHandler; @inject(FrontendApplicationStateService) protected readonly applicationStateService: FrontendApplicationStateService; @@ -96,6 +104,7 @@ export class SidePanelHandler { this.side = side; this.options = options; this.tabBar = this.createSideBar(); + this.toolBar = this.createToolbar(); this.dockPanel = this.createSidePanel(); this.container = this.createContainer(); @@ -143,6 +152,7 @@ export class SidePanelHandler { mode: 'single-document' }); sidePanel.id = 'theia-' + this.side + '-side-panel'; + sidePanel.addClass('theia-side-panel'); sidePanel.widgetActivated.connect((sender, widget) => { this.tabBar.currentTitle = widget.title; @@ -152,7 +162,19 @@ export class SidePanelHandler { return sidePanel; } + protected createToolbar(): SidePanelToolbar { + const toolbar = new SidePanelToolbar(this.tabBarToolBarRegistry, this.tabBarToolBarFactory, this.side); + return toolbar; + } + protected createContainer(): Panel { + const contentBox = new BoxLayout({ direction: 'top-to-bottom', spacing: 0 }); + BoxPanel.setStretch(this.toolBar, 0); + contentBox.addWidget(this.toolBar); + BoxPanel.setStretch(this.dockPanel, 1); + contentBox.addWidget(this.dockPanel); + const contentPanel = new BoxPanel({layout: contentBox}); + const side = this.side; let direction: BoxLayout.Direction; switch (side) { @@ -165,12 +187,12 @@ export class SidePanelHandler { default: throw new Error('Illegal argument: ' + side); } - const boxLayout = new BoxLayout({ direction, spacing: 0 }); + const containerLayout = new BoxLayout({ direction, spacing: 0 }); BoxPanel.setStretch(this.tabBar, 0); - boxLayout.addWidget(this.tabBar); - BoxPanel.setStretch(this.dockPanel, 1); - boxLayout.addWidget(this.dockPanel); - const boxPanel = new BoxPanel({ layout: boxLayout }); + containerLayout.addWidget(this.tabBar); + BoxPanel.setStretch(contentPanel, 1); + containerLayout.addWidget(contentPanel); + const boxPanel = new BoxPanel({ layout: containerLayout }); boxPanel.id = 'theia-' + side + '-content-panel'; return boxPanel; } @@ -327,6 +349,8 @@ export class SidePanelHandler { const hideDockPanel = currentTitle === null; let relativeSizes: number[] | undefined; + this.toolBar.toolbarTitle = currentTitle || undefined; + if (hideDockPanel) { container.addClass(COLLAPSED_CLASS); if (this.state.expansion === SidePanel.ExpansionState.expanded && !this.state.empty) { diff --git a/packages/core/src/browser/shell/side-panel-toolbar.ts b/packages/core/src/browser/shell/side-panel-toolbar.ts new file mode 100644 index 0000000000000..4c0ddcd1974aa --- /dev/null +++ b/packages/core/src/browser/shell/side-panel-toolbar.ts @@ -0,0 +1,89 @@ +/******************************************************************************** + * 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 { Widget, Title } from '@phosphor/widgets'; +import { TabBarToolbar, TabBarToolbarRegistry } from './tab-bar-toolbar'; +import { Message } from '@phosphor/messaging'; + +export class SidePanelToolbar extends Widget { + + protected titleContainer: HTMLElement | undefined; + private _toolbarTitle: Title | undefined; + protected toolbar: TabBarToolbar | undefined; + + constructor( + protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry, + protected readonly tabBarToolbarFactory: () => TabBarToolbar, + protected readonly side: 'left' | 'right') { + super(); + this.init(); + this.tabBarToolbarRegistry.onDidChange(() => this.update()); + } + + protected onAfterAttach(msg: Message): void { + if (this.toolbar) { + if (this.toolbar.isAttached) { + Widget.detach(this.toolbar); + } + Widget.attach(this.toolbar, this.node); + } + super.onAfterAttach(msg); + } + + protected onBeforeDetach(msg: Message): void { + if (this.titleContainer) { + this.node.removeChild(this.titleContainer); + } + if (this.toolbar && this.toolbar.isAttached) { + Widget.detach(this.toolbar); + } + super.onBeforeDetach(msg); + } + + protected onUpdateRequest(msg: Message): void { + super.onUpdateRequest(msg); + this.updateToolbar(); + } + + protected updateToolbar(): void { + if (!this.toolbar) { + return; + } + const current = this._toolbarTitle; + const widget = current && current.owner || undefined; + const items = widget ? this.tabBarToolbarRegistry.visibleItems(widget) : []; + this.toolbar.updateItems(items, widget); + } + + protected init(): void { + this.titleContainer = document.createElement('div'); + this.titleContainer.classList.add('theia-sidepanel-title'); + this.titleContainer.classList.add('noWrapInfo'); + this.node.appendChild(this.titleContainer); + this.node.classList.add('theia-sidepanel-toolbar'); + this.node.classList.add(`theia-${this.side}-side-panel`); + this.toolbar = this.tabBarToolbarFactory(); + this.update(); + } + + set toolbarTitle(title: Title | undefined) { + if (this.titleContainer && title) { + this._toolbarTitle = title; + this.titleContainer.innerHTML = this._toolbarTitle.label; + this.update(); + } + } +} diff --git a/packages/core/src/browser/shell/split-panels.ts b/packages/core/src/browser/shell/split-panels.ts index 55ad655d6e73d..bce4c6b3c55f9 100644 --- a/packages/core/src/browser/shell/split-panels.ts +++ b/packages/core/src/browser/shell/split-panels.ts @@ -52,11 +52,11 @@ export class SplitPositionHandler { */ setSidePanelSize(sidePanel: Widget, targetSize: number, options: SplitPositionOptions): Promise { if (targetSize < 0) { - return Promise.reject('Cannot resize to negative value.'); + return Promise.reject(new Error('Cannot resize to negative value.')); } const parent = sidePanel.parent; if (!(parent instanceof SplitPanel)) { - return Promise.reject('Widget must be contained in a SplitPanel.'); + return Promise.reject(new Error('Widget must be contained in a SplitPanel.')); } let index = parent.widgets.indexOf(sidePanel); if (index > 0 && (options.side === 'right' || options.side === 'bottom')) { diff --git a/packages/core/src/browser/shell/tab-bar-toolbar.tsx b/packages/core/src/browser/shell/tab-bar-toolbar.tsx index 2c411ec389621..46f3136cf3b53 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar.tsx +++ b/packages/core/src/browser/shell/tab-bar-toolbar.tsx @@ -23,6 +23,9 @@ import { FrontendApplicationContribution } from '../frontend-application'; import { CommandRegistry, CommandService } from '../../common/command'; import { Disposable } from '../../common/disposable'; import { ContextKeyService } from '../context-key-service'; +import { Event, Emitter } from '../../common/event'; + +import debounce = require('lodash.debounce'); /** * Factory for instantiating tab-bar toolbars. @@ -80,15 +83,21 @@ export class TabBarToolbar extends ReactWidget { } } const command = this.commands.getCommand(item.command); - const iconClass = command && command.iconClass; - if (iconClass) { - classNames.push(iconClass); + if (command) { + const iconClass = command.iconClass; + if (iconClass) { + classNames.push(iconClass); + } } - return
+ return
{innerText}
; } + protected commandIsEnabled(command: string): boolean { + return this.commands.isEnabled(command, this.current); + } + protected executeCommand = (e: React.MouseEvent) => { const item = this.items.get(e.currentTarget.id); if (item) { @@ -174,6 +183,8 @@ export interface TabBarToolbarItem { */ readonly when?: string; + readonly onDidChange?: Event; + } export namespace TabBarToolbarItem { @@ -227,6 +238,11 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution { @named(TabBarToolbarContribution) protected readonly contributionProvider: ContributionProvider; + protected readonly onDidChangeEmitter = new Emitter(); + readonly onDidChange: Event = this.onDidChangeEmitter.event; + // debounce in order to avoid to fire more than once in the same tick + protected fireOnDidChange = debounce(() => this.onDidChangeEmitter.fire(undefined), 0); + onStart(): void { const contributions = this.contributionProvider.getContributions(); for (const contribution of contributions) { @@ -245,6 +261,10 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution { throw new Error(`A toolbar item is already registered with the '${id}' ID.`); } this.items.set(id, item); + this.fireOnDidChange(); + if (item.onDidChange) { + item.onDidChange(() => this.fireOnDidChange()); + } } /** diff --git a/packages/core/src/browser/shell/tab-bars.ts b/packages/core/src/browser/shell/tab-bars.ts index 63bb8eed1cedb..93b99351bd8ee 100644 --- a/packages/core/src/browser/shell/tab-bars.ts +++ b/packages/core/src/browser/shell/tab-bars.ts @@ -305,6 +305,7 @@ export class ToolbarAwareTabBar extends ScrollableTabBar { super(options); this.rewireDOM(); + this.tabBarToolbarRegistry.onDidChange(() => this.update()); } /** @@ -336,9 +337,6 @@ export class ToolbarAwareTabBar extends ScrollableTabBar { } protected onBeforeDetach(msg: Message): void { - if (this.contentContainer) { - this.node.removeChild(this.contentContainer); - } if (this.toolbar && this.toolbar.isAttached) { Widget.detach(this.toolbar); } @@ -363,7 +361,7 @@ export class ToolbarAwareTabBar extends ScrollableTabBar { /** * Restructures the DOM defined in PhosphorJS. * - * By default the tabs (`li`) are contained in the `this.contentNode` (`lu`) which is wrapped in a `div` (`this.node`). + * By default the tabs (`li`) are contained in the `this.contentNode` (`ul`) which is wrapped in a `div` (`this.node`). * Instead of this structure, we add a container for the `this.contentNode` and for the toolbar. * The scrollbar will only work for the `ul` part but it does not affect the toolbar, so it can be on the right hand-side. */ diff --git a/packages/core/src/browser/style/alert-messages.css b/packages/core/src/browser/style/alert-messages.css index ba096e45689f8..2cd54b50dbbcb 100644 --- a/packages/core/src/browser/style/alert-messages.css +++ b/packages/core/src/browser/style/alert-messages.css @@ -20,6 +20,10 @@ padding: 10px; } +.theia-side-panel .theia-alert-message-container { + padding-left: 20px; +} + .theia-alert-message-container i { padding-right: 3px; } diff --git a/packages/core/src/browser/style/dockpanel.css b/packages/core/src/browser/style/dockpanel.css index c3969c661845c..8f6a8636d87f3 100644 --- a/packages/core/src/browser/style/dockpanel.css +++ b/packages/core/src/browser/style/dockpanel.css @@ -23,7 +23,6 @@ } .p-DockPanel-widget { - background: var(--theia-layout-color0); min-width: 100px; min-height: 100px; } diff --git a/packages/core/src/browser/style/icons.css b/packages/core/src/browser/style/icons.css new file mode 100644 index 0000000000000..0da9414f01116 --- /dev/null +++ b/packages/core/src/browser/style/icons.css @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +.theia-open-change-icon { + width: 16px; + height: 16px; + background: var(--theia-icon-open-change) no-repeat; +} + +.theia-open-file-icon { + width: 16px; + height: 16px; + background: var(--theia-icon-open-file) no-repeat; +} + +.theia-open-preview-icon { + width: 16px; + height: 16px; + background: var(--theia-icon-preview) no-repeat; +} diff --git a/packages/core/src/browser/style/index.css b/packages/core/src/browser/style/index.css index b6c0e9c932bef..9993e7c08bf84 100644 --- a/packages/core/src/browser/style/index.css +++ b/packages/core/src/browser/style/index.css @@ -59,7 +59,7 @@ body { } .theia-icon { - width: 18px; + width: 32px; height: 18px; margin: 5px; margin-left: 8px; @@ -184,3 +184,4 @@ textarea { @import './view-container.css'; @import './notification.css'; @import './alert-messages.css'; +@import './icons.css'; \ No newline at end of file diff --git a/packages/core/src/browser/style/menus.css b/packages/core/src/browser/style/menus.css index 176037c5578c5..9b2edcd9230ba 100644 --- a/packages/core/src/browser/style/menus.css +++ b/packages/core/src/browser/style/menus.css @@ -20,7 +20,7 @@ :root { - --theia-private-menubar-height: 28px; + --theia-private-menubar-height: 32px; --theia-private-menu-item-height: 24px; } diff --git a/packages/core/src/browser/style/notification.css b/packages/core/src/browser/style/notification.css index cd77cf95a5204..3c89a0f3aced4 100644 --- a/packages/core/src/browser/style/notification.css +++ b/packages/core/src/browser/style/notification.css @@ -14,6 +14,10 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +:root { + --theia-notification-count-height: 15.5px; +} + .notification-count-container { align-self: center; background-color: var(--theia-ui-font-color3); @@ -22,7 +26,7 @@ display: flex; font-size: calc(var(--theia-ui-font-size0) * 0.8); font-weight: 500; - height: calc(var(--theia-private-horizontal-tab-height) * 0.7); + height: var(--theia-notification-count-height); justify-content: center; min-width: 6px; padding: 0 5px; diff --git a/packages/core/src/browser/style/scrollbars.css b/packages/core/src/browser/style/scrollbars.css index c256eb4ce264c..b518b17896f4f 100644 --- a/packages/core/src/browser/style/scrollbars.css +++ b/packages/core/src/browser/style/scrollbars.css @@ -68,7 +68,7 @@ #theia-app-shell .ps__rail-x:focus > .ps__thumb-x, #theia-app-shell .ps__rail-x.ps--clicking .ps__thumb-x, #theia-dialog-shell .ps__rail-x:hover > .ps__thumb-x, -#ttheia-dialog-shell .ps__rail-x:focus > .ps__thumb-x, +#theia-dialog-shell .ps__rail-x:focus > .ps__thumb-x, #theia-dialog-shell .ps__rail-x.ps--clicking .ps__thumb-x { height: var(--theia-scrollbar-width); } diff --git a/packages/core/src/browser/style/sidepanel.css b/packages/core/src/browser/style/sidepanel.css index 32ee05c42dd82..0407aa37f1f35 100644 --- a/packages/core/src/browser/style/sidepanel.css +++ b/packages/core/src/browser/style/sidepanel.css @@ -19,9 +19,11 @@ |----------------------------------------------------------------------------*/ :root { - --theia-private-sidebar-tab-width: 32px; + --theia-private-sidebar-tab-width: 50px; + --theia-private-sidebar-tab-height: 32px; --theia-private-sidebar-scrollbar-rail-width: 7px; --theia-private-sidebar-scrollbar-width: 5px; + --theia-private-sidebar-icon-size: 28px; } @@ -31,10 +33,10 @@ .p-TabBar.theia-app-sides { display: block; - color: var(--theia-ui-font-color1); + color: var(--theia-tab-font-color); background: var(--theia-layout-color2); font-size: var(--theia-ui-font-size1); - min-width: var(--theia-private-sidebar-tab-width); + min-width: var(--theia-private-sidebar-tab-width); max-width: var(--theia-private-sidebar-tab-width); } @@ -44,22 +46,32 @@ .p-TabBar.theia-app-sides .p-TabBar-tab { position: relative; - padding: 12px 8px; + padding: 11px 10px; background: var(--theia-layout-color2); flex-direction: column; + justify-content: center; + align-items: center; + min-height: var(--theia-private-sidebar-tab-height); + cursor: pointer; } .p-TabBar.theia-app-left .p-TabBar-tab { border-left: var(--theia-panel-border-width) solid var(--theia-layout-color2); + margin-right: var(--theia-panel-border-width); } .p-TabBar.theia-app-right .p-TabBar-tab { border-right: var(--theia-panel-border-width) solid var(--theia-layout-color2); + margin-left: var(--theia-panel-border-width); } .p-TabBar.theia-app-sides .p-TabBar-tab.p-mod-current { - color: var(--theia-ui-font-color0); - background: var(--theia-layout-color0); + color: var(--theia-tab-font-color); + opacity: 1.0; + background: none; + min-height: var(--theia-private-sidebar-tab-height); + height: var(--theia-private-sidebar-tab-height); + border-top: none; } .p-TabBar.theia-app-left .p-TabBar-tab.p-mod-current.theia-mod-active { @@ -72,25 +84,45 @@ border-top-color: transparent; } -.p-TabBar.theia-app-sides .p-TabBar-tab:hover { - background: var(--theia-accent-color3); -} - -.p-TabBar.theia-app-sides .p-TabBar-tabIcon, +.p-TabBar.theia-app-sides .p-TabBar-tabLabel, .p-TabBar.theia-app-sides .p-TabBar-tabCloseIcon { display: none; } -.p-TabBar.theia-app-sides .p-TabBar-tabLabel { - position: absolute; - min-height: var(--theia-private-sidebar-tab-width); - max-height: var(--theia-private-sidebar-tab-width); - align-items: flex-start; +.p-TabBar.theia-app-sides .p-TabBar-tabIcon { + width: var(--theia-private-sidebar-icon-size); + height: var(--theia-private-sidebar-icon-size); + background-color: var(--theia-tab-icon-color); + opacity: 0.6; + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: var(--theia-private-sidebar-icon-size); + -webkit-mask-size: var(--theia-private-sidebar-icon-size); } -.p-TabBar.theia-app-sides .p-TabBar-tabIcon { - transform: scale(1.5); - max-height: 15px; +.p-TabBar.theia-app-sides .file-icon.p-TabBar-tabIcon, +.p-TabBar.theia-app-sides .file-icon.p-TabBar-tabIcon:hover, +.p-TabBar.theia-app-sides .p-mod-current .file-icon.p-TabBar-tabIcon, +.p-TabBar.theia-app-sides .fa.p-TabBar-tabIcon:hover, +.p-TabBar.theia-app-sides .p-mod-current .fa.p-TabBar-tabIcon { + opacity: 1.0; + background: none; +} + +.p-TabBar.theia-app-sides .p-TabBar-tabIcon:hover, +.p-TabBar.theia-app-sides .p-mod-current .p-TabBar-tabIcon { + opacity: 1.0; +} + +.p-TabBar.theia-app-sides .fa.p-TabBar-tabIcon { + display: flex; + align-items: center; + justify-content: center; + font-size: 23px; + opacity: 0.6; + text-align: center; + background: none; + color: var(--theia-tab-icon-color); } .p-TabBar.theia-app-left .p-TabBar-tabLabel { @@ -108,12 +140,8 @@ max-width: var(--theia-private-sidebar-tab-width); } -#theia-left-side-panel { - border-right: var(--theia-panel-border-width) solid var(--theia-border-color1); -} - -#theia-right-side-panel { - border-left: var(--theia-panel-border-width) solid var(--theia-border-color1); +.theia-side-panel { + background-color: var(--theia-layout-color1); } @@ -173,3 +201,33 @@ .p-TabBar.theia-app-right > .theia-TabBar-hidden-content .p-TabBar-tabLabel { transform: none; } + +/*----------------------------------------------------------------------------- +| Sidepanel Toolbar +|----------------------------------------------------------------------------*/ + +.theia-sidepanel-toolbar { + min-height: calc(var(--theia-private-horizontal-tab-height) + var(--theia-private-horizontal-tab-scrollbar-rail-height) / 2); + display: flex; + padding-left: 5px; + align-items: center; + background-color: var(--theia-layout-color1); +} + +.theia-sidepanel-toolbar .theia-sidepanel-title { + color: var(--theia-ui-font-color1); + flex: 1; + margin-left: 14px; + text-transform: uppercase; + font-size: var(--theia-ui-font-size0); +} + +.theia-sidepanel-toolbar .p-TabBar-toolbar .item { + color: var(--theia-ui-font-color1); +} + +.theia-sidepanel-toolbar .p-TabBar-toolbar .item > div{ + height: 18px; + width: 18px; + background-repeat: no-repeat; +} diff --git a/packages/core/src/browser/style/tabs.css b/packages/core/src/browser/style/tabs.css index 54d1953de9a0c..b7c4785f88549 100644 --- a/packages/core/src/browser/style/tabs.css +++ b/packages/core/src/browser/style/tabs.css @@ -4,8 +4,7 @@ :root { /* These need to be root because tabs get attached to the body during dragging. */ - --theia-private-horizontal-tab-height: 22px; - --theia-private-horizontal-tab-active-top-border: 2px; + --theia-private-horizontal-tab-height: 28.5px; --theia-private-horizontal-tab-scrollbar-rail-height: 7px; --theia-private-horizontal-tab-scrollbar-height: 5px; } @@ -18,12 +17,30 @@ color: var(--theia-ui-font-color1); background: var(--theia-layout-color1); font-size: var(--theia-ui-font-size1); + cursor: pointer; +} + +.p-TabBar[data-orientation='horizontal'].theia-app-bottom { + background: var(--theia-layout-color0); +} + +.p-TabBar[data-orientation='horizontal'].theia-app-bottom .p-TabBar-tab { + background: var(--theia-layout-color0); +} + +.p-TabBar[data-orientation='horizontal'].theia-app-bottom .p-TabBar-tab.p-mod-current { + background: var(--theia-layout-color0); + border-top: var(--theia-border-width) solid var(--theia-ui-font-color2); +} + +.p-TabBar[data-orientation='horizontal'].theia-app-bottom .p-TabBar-tab.p-mod-current.theia-mod-active { + border-top-color: var(--theia-accent-color2); } .p-TabBar[data-orientation='horizontal'] { overflow-x: hidden; overflow-y: hidden; - min-height: calc(var(--theia-private-horizontal-tab-height) + var(--theia-border-width) + var(--theia-private-horizontal-tab-scrollbar-rail-height) / 2); + min-height: calc(var(--theia-private-horizontal-tab-height) + var(--theia-private-horizontal-tab-scrollbar-rail-height) / 2); } .p-TabBar[data-orientation='horizontal'] .p-TabBar-content { @@ -38,7 +55,8 @@ min-width: 35px; line-height: var(--theia-private-horizontal-tab-height); padding: 0px 8px; - background: var(--theia-layout-color3); + background: var(--theia-layout-color2); + align-items: center; } .p-TabBar[data-orientation='horizontal'] .p-TabBar-tab:last-child { @@ -90,13 +108,41 @@ } .p-TabBar .p-TabBar-tabIcon { - width: 15px; - line-height: 1.7; + width: 15px; + line-height: 1.7; + font-size: 12px; + text-align: center; + background-repeat: no-repeat; +} + +.p-TabBar.theia-app-centers .p-TabBar-tabIcon { + height: 15px; + background-size: 13px; + background-position-y: 3px; + background-color: var(--theia-tab-icon-color); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 13px; + -webkit-mask-position-y: 1px; + mask-repeat: no-repeat; + mask-size: 13px; + mask-position: 0 1px; + padding-right: 2px; +} + +.p-TabBar[data-orientation='horizontal'] .file-icon.p-TabBar-tabIcon { + background: none; + padding-bottom: 0px; + padding-right: 0; + min-height: 20px; } +.p-TabBar[data-orientation='horizontal'] .fa.p-TabBar-tabIcon { + background: none; + padding-bottom: 6px; + min-height: 0; +} .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon { - padding-top: 6px; padding-left: 10px; height: 16px; width: 16px; @@ -139,18 +185,18 @@ } .p-TabBar[data-orientation='horizontal'] > .p-TabBar-content-container > .ps__rail-x > .ps__thumb-x { - height: var(--theia-private-horizontal-tab-scrollbar-height); + height: var(--theia-private-horizontal-tab-scrollbar-height) !important; bottom: calc((var(--theia-private-horizontal-tab-scrollbar-rail-height) - var(--theia-private-horizontal-tab-scrollbar-height)) / 2); } .p-TabBar[data-orientation='horizontal'] > .p-TabBar-content-container > .ps__rail-x:hover, .p-TabBar[data-orientation='horizontal'] > .p-TabBar-content-container > .ps__rail-x:focus { - height: var(--theia-private-horizontal-tab-scrollbar-rail-height); + height: var(--theia-private-horizontal-tab-scrollbar-rail-height) !important; } .p-TabBar[data-orientation='horizontal'] > .p-TabBar-content-container > .ps__rail-x:hover > .ps__thumb-x, .p-TabBar[data-orientation='horizontal'] > .p-TabBar-content-container > .ps__rail-x:focus > .ps__thumb-x { - height: var(--theia-private-horizontal-tab-scrollbar-height); + height: calc(var(--theia-private-horizontal-tab-scrollbar-height) / 2) !important; bottom: calc((var(--theia-private-horizontal-tab-scrollbar-rail-height) - var(--theia-private-horizontal-tab-scrollbar-height)) / 2); } @@ -207,8 +253,17 @@ display: flex; align-items: center; margin-left: 8px; /* `padding` + `margin-right` from the container toolbar */ + opacity: 0.25; + cursor: default; } -.p-TabBar-toolbar .item:hover { - cursor: pointer; +.p-TabBar-toolbar .item.enabled { + opacity: 1.0; + cursor: pointer; +} + +.p-TabBar-toolbar .item > div{ + height: 18px; + width: 18px; + background-repeat: no-repeat; } diff --git a/packages/core/src/browser/style/tree.css b/packages/core/src/browser/style/tree.css index e095bb0fb9489..e11e57f1a50a1 100644 --- a/packages/core/src/browser/style/tree.css +++ b/packages/core/src/browser/style/tree.css @@ -17,7 +17,6 @@ .theia-Tree { overflow: hidden; color: var(--theia-ui-font-color1); - background: var(--theia-layout-color0); font-size: var(--theia-ui-font-size1); max-height: calc(100% - var(--theia-border-width)); position: relative; @@ -35,7 +34,7 @@ } .theia-TreeNode { - line-height: var(--theia-private-horizontal-tab-height); + line-height: 22px; display: flex; } @@ -52,7 +51,9 @@ .theia-ExpansionToggle { padding-right: var(--theia-ui-padding); - min-width: 8px; + min-width: 10px; + display: flex; + justify-content: center; } .theia-ExpansionToggle:hover { @@ -68,7 +69,8 @@ .theia-ExpansionToggle:not(.theia-mod-collapsed)::before { font-family: FontAwesome; font-size: calc(var(--theia-content-font-size) * 0.8); - content: "\f0d7"; + content: "\f0da"; + transform: rotate(45deg); } .theia-Tree:focus .theia-TreeNode.theia-mod-selected, diff --git a/packages/core/src/browser/style/variables-bright.useable.css b/packages/core/src/browser/style/variables-bright.useable.css index 8e32bdccaed9d..2f1fbd1dfef26 100644 --- a/packages/core/src/browser/style/variables-bright.useable.css +++ b/packages/core/src/browser/style/variables-bright.useable.css @@ -78,14 +78,18 @@ is not optimized for dense, information rich UIs. --theia-terminal-font-family: monospace; --theia-ui-padding: 6px; + /* Tab Icon Colors */ + --theia-tab-icon-color: var(--theia-ui-font-color1); + --theia-tab-font-color: #000; + /* Main layout colors (bright to dark) ------------------------------------ */ --theia-layout-color0: #ffffff; - --theia-layout-color1: var(--md-grey-100); - --theia-layout-color2: var(--md-grey-200); - --theia-layout-color3: var(--md-grey-300); - --theia-layout-color4: var(--md-grey-400); + --theia-layout-color1: #f3f3f3; + --theia-layout-color2: #ececec; + --theia-layout-color3: #dcdcdc; + --theia-layout-color4: #dcdcdc; /* Brand colors */ @@ -181,9 +185,11 @@ is not optimized for dense, information rich UIs. --theia-icon-replace: url(../icons/replace.svg); --theia-icon-replace-all: url(../icons/replace-all.svg); --theia-icon-open-file: url(../icons/open-file-bright.svg); + --theia-icon-open-change: url(../icons/open-change-bright.svg); + --theia-icon-preview: url(../icons/preview-bright.svg); /* Scrollbars */ - --theia-scrollbar-width: 6px; + --theia-scrollbar-width: 10px; --theia-scrollbar-rail-width: 10px; --theia-scrollbar-thumb-color: hsla(0,0%,61%,.4); --theia-scrollbar-rail-color: transparent; @@ -191,7 +197,7 @@ is not optimized for dense, information rich UIs. --theia-scrollbar-active-rail-color: transparent; /* Menu */ - --theia-menu-color0: var(--theia-layout-color2); + --theia-menu-color0: var(--theia-layout-color3); --theia-menu-color1: var(--theia-layout-color0); --theia-menu-color2: var(--theia-layout-color3); diff --git a/packages/core/src/browser/style/variables-dark.useable.css b/packages/core/src/browser/style/variables-dark.useable.css index 63d9ffb8c46ec..7aa29eeae26bd 100644 --- a/packages/core/src/browser/style/variables-dark.useable.css +++ b/packages/core/src/browser/style/variables-dark.useable.css @@ -78,14 +78,18 @@ is not optimized for dense, information rich UIs. --theia-terminal-font-family: monospace; --theia-ui-padding: 6px; + /* Tab Colors */ + --theia-tab-icon-color: rgb(255, 255, 255); + --theia-tab-font-color: #FFF; + /* Main layout colors (dark to bright) ------------------------------------ */ - --theia-layout-color0: #1e1e1e; - --theia-layout-color1: #262626; - --theia-layout-color2: #2e2e2e; - --theia-layout-color3: #303030; - --theia-layout-color4: #333333; + --theia-layout-color0: #1d1d1d; + --theia-layout-color1: #252526; + --theia-layout-color2: #333333; + --theia-layout-color3: #383838; + --theia-layout-color4: #383838; /* Brand colors */ @@ -181,9 +185,11 @@ is not optimized for dense, information rich UIs. --theia-icon-replace: url(../icons/replace-inverse.svg); --theia-icon-replace-all: url(../icons/replace-all-inverse.svg); --theia-icon-open-file: url(../icons/open-file-dark.svg); + --theia-icon-open-change: url(../icons/open-change-dark.svg); + --theia-icon-preview: url(../icons/preview-dark.svg); /* Scrollbars */ - --theia-scrollbar-width: 6px; + --theia-scrollbar-width: 10px; --theia-scrollbar-rail-width: 10px; --theia-scrollbar-thumb-color: hsla(0,0%,39%,.4); --theia-scrollbar-rail-color: transparent; @@ -191,7 +197,7 @@ is not optimized for dense, information rich UIs. --theia-scrollbar-active-rail-color: transparent; /* Menu */ - --theia-menu-color0: var(--theia-layout-color2); + --theia-menu-color0: var(--theia-layout-color4); --theia-menu-color1: var(--theia-layout-color4); --theia-menu-color2: var(--theia-layout-color1); diff --git a/packages/core/src/browser/tree/tree-decorator.ts b/packages/core/src/browser/tree/tree-decorator.ts index 41f780a96c381..c1adc16a8bcc7 100644 --- a/packages/core/src/browser/tree/tree-decorator.ts +++ b/packages/core/src/browser/tree/tree-decorator.ts @@ -233,16 +233,53 @@ export namespace TreeDecoration { } + export interface BaseTailDecoration { + + /** + * Optional tooltip for the tail decoration. + */ + readonly tooltip?: string; + } + /** * Unlike caption suffixes, tail decorations appears right-aligned after the caption and the caption suffixes (is any). */ - export interface TailDecoration extends CaptionAffix { + export interface TailDecoration extends BaseTailDecoration { + /** + * The text content of the tail decoration. + */ + readonly data: string; /** - * Optional tooltip for the tail decoration. + * Font data for customizing the content. */ - readonly tooltip?: string; + readonly fontData?: FontData; + } + + export interface TailDecorationIcon extends BaseTailDecoration { + /** + * This should be the name of the Font Awesome icon with out the `fa fa-` prefix, just the name, for instance `paw`. + * For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/. + */ + readonly icon: string; + + /** + * The color of the icon. + */ + readonly color?: Color; + } + export interface TailDecorationIconClass extends BaseTailDecoration { + /** + * This should be the entire Font Awesome class array, for instance ['fa', 'fa-paw'] + * For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/. + */ + readonly iconClass: string[]; + + /** + * The color of the icon. + */ + readonly color?: Color; } /** @@ -307,19 +344,13 @@ export namespace TreeDecoration { /** * Has not effect if the tree node being decorated has no associated icon. */ - export interface IconOverlay { + export interface BaseOverlay { /** * The position where the decoration will be placed on the top of the original icon. */ readonly position: IconOverlayPosition; - /** - * This should be the name of the Font Awesome icon with out the `fa fa-` prefix, just the name, for instance `paw`. - * For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/. - */ - readonly icon: string; - /** * The color of the overlaying icon. If not specified, then the default icon color will be used. */ @@ -332,6 +363,22 @@ export namespace TreeDecoration { } + export interface IconOverlay extends BaseOverlay { + /** + * This should be the name of the Font Awesome icon with out the `fa fa-` prefix, just the name, for instance `paw`. + * For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/. + */ + readonly icon: string; + } + + export interface IconClassOverlay extends BaseOverlay { + /** + * This should be the entire Font Awesome class array, for instance ['fa', 'fa-paw'] + * For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/. + */ + readonly iconClass: string[]; + } + /** * The caption highlighting with the highlighted ranges and an optional background color. */ @@ -468,7 +515,7 @@ export namespace TreeDecoration { /** * Optional right-aligned decorations that appear after the node caption and after the caption suffixes (is any). */ - readonly tailDecorations?: TailDecoration[]; + readonly tailDecorations?: Array; /** * Custom tooltip for the decorated item. Tooltip will be appended to the original tooltip, if any. @@ -483,7 +530,7 @@ export namespace TreeDecoration { /** * Has not effect if given, but the tree node does not have an associated image. */ - readonly iconOverlay?: IconOverlay; + readonly iconOverlay?: IconOverlay | IconClassOverlay; /** * An array of ranges to highlight the caption. diff --git a/packages/core/src/browser/tree/tree-expansion.spec.ts b/packages/core/src/browser/tree/tree-expansion.spec.ts new file mode 100644 index 0000000000000..103e1c6939a33 --- /dev/null +++ b/packages/core/src/browser/tree/tree-expansion.spec.ts @@ -0,0 +1,158 @@ +/******************************************************************************** + * Copyright (C) 2019 Thomas Drosdzoll. + * + * 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 { 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'; + +// 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(); + }); + }); + + 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('won\'t expand an undefined node', done => { + model.expandNode(undefined).then(result => { + expect(result).to.be.false; + 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(); + }); + }); + + 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(); + }); + }); + }); + + describe('collapseAll', () => { + it('will collapse all nodes recursively', done => { + 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.true; + 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(); + }); + }); + }); + + 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; + } +}); diff --git a/packages/core/src/browser/tree/tree-expansion.ts b/packages/core/src/browser/tree/tree-expansion.ts index a275bbfe7ca08..01c8dcfb737be 100644 --- a/packages/core/src/browser/tree/tree-expansion.ts +++ b/packages/core/src/browser/tree/tree-expansion.ts @@ -65,15 +65,15 @@ export interface ExpandableTreeNode extends CompositeTreeNode { } export namespace ExpandableTreeNode { - export function is(node: TreeNode | undefined): node is ExpandableTreeNode { + export function is(node: Object | undefined): node is ExpandableTreeNode { return !!node && CompositeTreeNode.is(node) && 'expanded' in node; } - export function isExpanded(node: TreeNode | undefined): node is ExpandableTreeNode { + export function isExpanded(node: Object | undefined): node is ExpandableTreeNode { return ExpandableTreeNode.is(node) && node.expanded; } - export function isCollapsed(node: TreeNode | undefined): node is ExpandableTreeNode { + export function isCollapsed(node: Object | undefined): node is ExpandableTreeNode { return ExpandableTreeNode.is(node) && !node.expanded; } } diff --git a/packages/core/src/browser/tree/tree-selection-state.ts b/packages/core/src/browser/tree/tree-selection-state.ts index 8f56c1158f697..045cfe7f1337c 100644 --- a/packages/core/src/browser/tree/tree-selection-state.ts +++ b/packages/core/src/browser/tree/tree-selection-state.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Tree } from './tree'; +import { Tree, TreeNode } from './tree'; import { DepthFirstTreeIterator } from './tree-iterator'; import { TreeSelection, SelectableTreeNode } from './tree-selection'; @@ -74,26 +74,29 @@ export class TreeSelectionState { selection(): ReadonlyArray { const copy = this.checkNoDefaultSelection(this.selectionStack); - const nodes = new Set(); + const nodeIds = new Set(); for (let i = 0; i < copy.length; i++) { const { node, type } = copy[i]; if (TreeSelection.isRange(type)) { const selection = copy[i]; - this.selectionRange(selection).forEach(n => nodes.add(n)); + for (const id of this.selectionRange(selection).map(n => n.id)) { + nodeIds.add(id); + } } else if (TreeSelection.isToggle(type)) { - if (nodes.has(node)) { - nodes.delete(node); + if (nodeIds.has(node.id)) { + nodeIds.delete(node.id); } else { - nodes.add(node); + nodeIds.add(node.id); } } } - return Array.from(nodes.keys()).reverse(); + return Array.from(nodeIds.keys()).map(id => this.tree.getNode(id)).filter(SelectableTreeNode.is).reverse(); } get focus(): SelectableTreeNode | undefined { const copy = this.checkNoDefaultSelection(this.selectionStack); - return copy[copy.length - 1].focus; + const candidate = copy[copy.length - 1].focus; + return this.toSelectableTreeNode(candidate); } protected handleDefault(state: TreeSelectionState, node: Readonly): TreeSelectionState { @@ -139,7 +142,7 @@ export class TreeSelectionState { // Drop the previous range when we are trying to modify that. if (TreeSelection.isRange(copy[copy.length - 1])) { const range = this.selectionRange(copy.pop()!); - // And we drop all preceeding individual nodes that were contained in the range we are dropping. + // And we drop all preceding individual nodes that were contained in the range we are dropping. // That means, anytime we cover individual nodes with a range, they will belong to the range so we need to drop them now. for (let i = copy.length - 1; i >= 0; i--) { if (range.indexOf(copy[i].node) !== -1) { @@ -208,6 +211,22 @@ export class TreeSelectionState { return range.filter(SelectableTreeNode.is); } + protected toSelectableTreeNode(node: TreeNode | undefined): SelectableTreeNode | undefined { + if (!!node) { + const candidate = this.tree.getNode(node.id); + if (!!candidate) { + if (SelectableTreeNode.is(candidate)) { + return candidate; + } else { + console.warn(`Could not map to a selectable tree node. Node with ID: ${node.id} is not a selectable node.`); + } + } else { + console.warn(`Could not map to a selectable tree node. Node does not exist with ID: ${node.id}.`); + } + } + return undefined; + } + /** * Checks whether the argument contains any `DEFAULT` tree selection type. If yes, throws an error, otherwise returns with a reference the argument. */ diff --git a/packages/core/src/browser/tree/tree-widget-selection.ts b/packages/core/src/browser/tree/tree-widget-selection.ts new file mode 100644 index 0000000000000..0868c0f541495 --- /dev/null +++ b/packages/core/src/browser/tree/tree-widget-selection.ts @@ -0,0 +1,37 @@ +/******************************************************************************** + * 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 { TreeWidget } from './tree-widget'; +import { SelectableTreeNode } from './tree-selection'; + +export type TreeWidgetSelection = ReadonlyArray> & { + source: TreeWidget +}; +export namespace TreeWidgetSelection { + export function isSource(selection: Object | undefined, source: TreeWidget) { + return getSource(selection) === source; + } + export function getSource(selection: Object | undefined): TreeWidget | undefined { + return is(selection) ? selection.source : undefined; + } + export function is(selection: Object | undefined): selection is TreeWidgetSelection { + // tslint:disable-next-line:no-any + return Array.isArray(selection) && ('source' in selection) && selection['source'] instanceof TreeWidget; + } + export function create(source: TreeWidget): TreeWidgetSelection { + return Object.assign(source.model.selectedNodes, { source }); + } +} diff --git a/packages/core/src/browser/tree/tree-widget.tsx b/packages/core/src/browser/tree/tree-widget.tsx index 736d9f09a572e..600d1eb450551 100644 --- a/packages/core/src/browser/tree/tree-widget.tsx +++ b/packages/core/src/browser/tree/tree-widget.tsx @@ -16,7 +16,7 @@ import { injectable, inject, postConstruct } from 'inversify'; import { Message } from '@phosphor/messaging'; -import { Disposable, MenuPath } from '../../common'; +import { Disposable, MenuPath, SelectionService } from '../../common'; import { Key, KeyCode, KeyModifier } from '../keys'; import { ContextMenuRenderer } from '../context-menu-renderer'; import { StatefulWidget } from '../shell'; @@ -35,6 +35,7 @@ import { TopDownTreeIterator } from './tree-iterator'; import { SearchBox, SearchBoxFactory, SearchBoxProps } from './search-box'; import { TreeSearch } from './tree-search'; import { ElementExt } from '@phosphor/domutils'; +import { TreeWidgetSelection } from './tree-widget-selection'; const debounce = require('lodash.debounce'); @@ -83,6 +84,11 @@ export interface TreeProps { * 'true' if the selected node should be auto scrolled only if the widget is active. Otherwise, `false`. Defaults to `false`. */ readonly scrollIfActive?: boolean + + /** + * `true` if a tree widget contributes to the global selection. Defaults to `false`. + */ + readonly globalSelection?: boolean; } export interface NodeProps { @@ -126,6 +132,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { protected decorations: Map = new Map(); + @inject(SelectionService) + protected readonly selectionService: SelectionService; + constructor( @inject(TreeProps) readonly props: TreeProps, @inject(TreeModel) readonly model: TreeModel, @@ -133,7 +142,8 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { ) { super(); this.scrollOptions = { - suppressScrollX: true + suppressScrollX: true, + minScrollbarLength: 35 }; this.addClass(TREE_CLASS); this.node.tabIndex = 0; @@ -178,6 +188,25 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { this.updateRows(); this.updateDecorations(); }); + if (this.props.globalSelection) { + this.toDispose.pushAll([ + this.model.onSelectionChanged(() => { + if (this.node.contains(document.activeElement)) { + this.updateGlobalSelection(); + } + }), + Disposable.create(() => { + const selection = this.selectionService.selection; + if (TreeWidgetSelection.isSource(selection, this)) { + this.selectionService.selection = undefined; + } + }) + ]); + } + } + + protected updateGlobalSelection(): void { + this.selectionService.selection = TreeWidgetSelection.create(this); } protected rows = new Map(); @@ -255,6 +284,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } } + // it has to be called after nodes are selected + if (this.props.globalSelection) { + this.updateGlobalSelection(); + } this.forceUpdate(); } @@ -513,15 +546,16 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { const overlayIcons: React.ReactNode[] = []; new Map(this.getDecorationData(node, 'iconOverlay').reverse().filter(notEmpty) - .map(overlay => [overlay.position, overlay] as [TreeDecoration.IconOverlayPosition, TreeDecoration.IconOverlay])) + .map(overlay => [overlay.position, overlay] as [TreeDecoration.IconOverlayPosition, TreeDecoration.IconOverlay | TreeDecoration.IconClassOverlay])) .forEach((overlay, position) => { - const overlayClass = (iconName: string) => - ['a', 'fa', `fa-${iconName}`, TreeDecoration.Styles.DECORATOR_SIZE_CLASS, TreeDecoration.IconOverlayPosition.getStyle(position)].join(' '); + const iconClasses = [TreeDecoration.Styles.DECORATOR_SIZE_CLASS, TreeDecoration.IconOverlayPosition.getStyle(position)]; const style = (color?: string) => color === undefined ? {} : { color }; if (overlay.background) { - overlayIcons.push(); + overlayIcons.push( + ); } - overlayIcons.push(); + const overlayIcon = (overlay as TreeDecoration.IconOverlay).icon || (overlay as TreeDecoration.IconClassOverlay).iconClass; + overlayIcons.push(); }); if (overlayIcons.length > 0) { @@ -532,18 +566,29 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } protected renderTailDecorations(node: TreeNode, props: NodeProps): React.ReactNode { - const style = (fontData: TreeDecoration.FontData | undefined) => this.applyFontStyles({}, fontData); return {this.getDecorationData(node, 'tailDecorations').filter(notEmpty).reduce((acc, current) => acc.concat(current), []).map((decoration, index) => { - const { fontData, data, tooltip } = decoration; + const { tooltip } = decoration; + const { data, fontData } = decoration as TreeDecoration.TailDecoration; + const color = (decoration as TreeDecoration.TailDecorationIcon).color; + const icon = (decoration as TreeDecoration.TailDecorationIcon).icon || (decoration as TreeDecoration.TailDecorationIconClass).iconClass; const className = [TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS].join(' '); - return
- {data} + const style = fontData ? this.applyFontStyles({}, fontData) : color ? { color } : undefined; + const content = data ? data : icon ? : ''; + return
+ {content}
; })} ; } + // Determine the classes to use for an icon + // Assumes a Font Awesome name when passed a single string, otherwise uses the passed string array + private getIconClass(iconName: string | string[], additionalClasses: string[] = []): string { + const iconClass = (typeof iconName === 'string') ? ['a', 'fa', `fa-${iconName}`] : ['a'].concat(iconName); + return iconClass.concat(additionalClasses).join(' '); + } + protected renderNode(node: TreeNode, props: NodeProps): React.ReactNode { if (!TreeNode.isVisible(node)) { return undefined; diff --git a/packages/core/src/browser/tree/tree.spec.ts b/packages/core/src/browser/tree/tree.spec.ts index 6254dd83ddb05..d8893092fe274 100644 --- a/packages/core/src/browser/tree/tree.spec.ts +++ b/packages/core/src/browser/tree/tree.spec.ts @@ -15,8 +15,21 @@ ********************************************************************************/ import * as assert from 'assert'; -import { TreeNode, CompositeTreeNode } from './tree'; +import { TreeNode, CompositeTreeNode, TreeImpl, Tree } from './tree'; +import { TreeModel, TreeModelImpl } from './tree-model'; +import { MockTreeModel } from './test/mock-tree-model'; +import { expect } from 'chai'; +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'; +// tslint:disable:no-unused-expression describe('Tree', () => { it('addChildren', () => { @@ -93,7 +106,7 @@ describe('Tree', () => { }`, node); }); - it('removeChild - thrid', () => { + it('removeChild - third', () => { const node = getNode(); CompositeTreeNode.removeChild(node, node.children[2]); assertTreeNode(`{ @@ -116,6 +129,83 @@ describe('Tree', () => { }`, node); }); + let model: TreeModel; + beforeEach(() => { + model = createTreeModel(); + model.root = MockTreeModel.HIERARCHICAL_MOCK_ROOT(); + }); + describe('getNode', () => { + it('returns undefined for undefined nodes', done => { + expect(model.getNode(undefined)).to.be.undefined; + done(); + }); + + it('returns undefined for a non-existing id', done => { + expect(model.getNode('10')).to.be.undefined; + done(); + }); + + it('returns a valid node for existing an id', done => { + expect(model.getNode('1.1')).not.to.be.undefined; + done(); + }); + }); + + describe('validateNode', () => { + it('returns undefined for undefined nodes', done => { + expect(model.validateNode(undefined)).to.be.undefined; + done(); + }); + + it('returns undefined for non-existing nodes', done => { + expect(model.validateNode(MockTreeModel.Node.toTreeNode({ 'id': '10' }))).to.be.undefined; + done(); + }); + + it('returns a valid node for an existing node', done => { + expect(model.validateNode(retrieveNode('1.1'))).not.to.be.undefined; + done(); + }); + }); + + describe('refresh', () => { + it('refreshes all composite nodes starting with the root', done => { + let result: Boolean = true; + const expectedRefreshedNodes = new Set([ + retrieveNode('1'), + retrieveNode('1.1'), + retrieveNode('1.2')]); + model.onNodeRefreshed((e: Readonly) => { + result = result && expectedRefreshedNodes.has(e); + expectedRefreshedNodes.delete(e); + }); + model.refresh().then(() => { + expect(result).to.be.true; + expect(expectedRefreshedNodes.size).to.be.equal(0); + done(); + }); + }); + }); + + describe('refresh(parent: Readonly)', () => { + it('refreshes all composite nodes starting with the provided node', done => { + let result: Boolean = true; + const expectedRefreshedNodes = new Set([ + retrieveNode('1.2'), + retrieveNode('1.2.1') + ]); + model.onNodeRefreshed((e: Readonly) => { + result = result && expectedRefreshedNodes.has(e); + expectedRefreshedNodes.delete(e); + }); + model.refresh(retrieveNode('1.2')).then(() => { + expect(result).to.be.true; + expect(expectedRefreshedNodes.size).to.be.equal(0); + done(); + }); + }); + }); + function getNode(): CompositeTreeNode { return CompositeTreeNode.addChildren({ id: 'parent', @@ -147,4 +237,26 @@ describe('Tree', () => { }, 2)); } + 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; + } + }); diff --git a/packages/core/src/browser/tree/tree.ts b/packages/core/src/browser/tree/tree.ts index 66d265e55ef32..41d923832ef17 100644 --- a/packages/core/src/browser/tree/tree.ts +++ b/packages/core/src/browser/tree/tree.ts @@ -116,7 +116,7 @@ export interface CompositeTreeNode extends TreeNode { } export namespace CompositeTreeNode { - export function is(node: TreeNode | undefined): node is CompositeTreeNode { + export function is(node: Object | undefined): node is CompositeTreeNode { return !!node && 'children' in node; } diff --git a/packages/core/src/browser/widgets/react-widget.tsx b/packages/core/src/browser/widgets/react-widget.tsx index 9d0268cc725a9..9c0dde62f0549 100644 --- a/packages/core/src/browser/widgets/react-widget.tsx +++ b/packages/core/src/browser/widgets/react-widget.tsx @@ -28,7 +28,8 @@ export abstract class ReactWidget extends BaseWidget { constructor() { super(); this.scrollOptions = { - suppressScrollX: true + suppressScrollX: true, + minScrollbarLength: 35, }; this.toDispose.push(Disposable.create(() => { ReactDOM.unmountComponentAtNode(this.node); diff --git a/packages/core/src/common/menu.ts b/packages/core/src/common/menu.ts index b33907ab2fb7a..752cd0f058ea5 100644 --- a/packages/core/src/common/menu.ts +++ b/packages/core/src/common/menu.ts @@ -142,7 +142,7 @@ export class MenuModelRegistry { return sub; } if (sub) { - throw Error(`'${menuId}' is not a menu group.`); + throw new Error(`'${menuId}' is not a menu group.`); } const newSub = new CompositeMenuNode(menuId); current.addNode(newSub); diff --git a/packages/core/src/common/messaging/proxy-factory.spec.ts b/packages/core/src/common/messaging/proxy-factory.spec.ts index 2b7f8cd05cd26..924b797da2bb9 100644 --- a/packages/core/src/common/messaging/proxy-factory.spec.ts +++ b/packages/core/src/common/messaging/proxy-factory.spec.ts @@ -42,7 +42,7 @@ class TestServer { } fails2(arg: string, otherArg: string): Promise { - return Promise.reject('fails2 failed'); + return Promise.reject(new Error('fails2 failed')); } } diff --git a/packages/core/src/common/preferences/preference-schema.ts b/packages/core/src/common/preferences/preference-schema.ts new file mode 100644 index 0000000000000..c3ad1e90a0edf --- /dev/null +++ b/packages/core/src/common/preferences/preference-schema.ts @@ -0,0 +1,94 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +// tslint:disable:no-any + +import { PreferenceScope } from './preference-scope'; + +export interface PreferenceSchema { + [name: string]: any, + scope?: 'application' | 'window' | 'resource' | PreferenceScope, + overridable?: boolean; + properties: PreferenceSchemaProperties +} +export namespace PreferenceSchema { + export function is(obj: Object | undefined): obj is PreferenceSchema { + return !!obj && ('properties' in obj) && PreferenceSchemaProperties.is((obj)['properties']); + } + export function getDefaultScope(schema: PreferenceSchema): PreferenceScope { + let defaultScope: PreferenceScope = PreferenceScope.Workspace; + if (!PreferenceScope.is(schema.scope)) { + defaultScope = PreferenceScope.fromString(schema.scope) || PreferenceScope.Workspace; + } else { + defaultScope = schema.scope; + } + return defaultScope; + } +} + +export interface PreferenceSchemaProperties { + [name: string]: PreferenceSchemaProperty +} +export namespace PreferenceSchemaProperties { + export function is(obj: Object | undefined): obj is PreferenceSchemaProperties { + return !!obj && typeof obj === 'object'; + } +} + +export interface PreferenceDataSchema { + [name: string]: any, + scope?: PreferenceScope, + properties: { + [name: string]: PreferenceDataProperty + } + patternProperties: { + [name: string]: PreferenceDataProperty + }; +} + +export interface PreferenceItem { + type?: JsonType | JsonType[]; + minimum?: number; + default?: any; + enum?: string[]; + items?: PreferenceItem; + properties?: { [name: string]: PreferenceItem }; + additionalProperties?: object; + [name: string]: any; + overridable?: boolean; +} + +export interface PreferenceSchemaProperty extends PreferenceItem { + description?: string; + scope?: 'application' | 'window' | 'resource' | PreferenceScope; +} + +export interface PreferenceDataProperty extends PreferenceItem { + description?: string; + scope?: PreferenceScope; +} +export namespace PreferenceDataProperty { + export function fromPreferenceSchemaProperty(schemaProps: PreferenceSchemaProperty, defaultScope: PreferenceScope = PreferenceScope.Workspace): PreferenceDataProperty { + if (!schemaProps.scope) { + schemaProps.scope = defaultScope; + } else if (typeof schemaProps.scope === 'string') { + return Object.assign(schemaProps, { scope: PreferenceScope.fromString(schemaProps.scope) || defaultScope }); + } + return schemaProps; + } +} + +export type JsonType = 'string' | 'array' | 'number' | 'integer' | 'object' | 'boolean' | 'null'; diff --git a/packages/core/src/common/preferences/preference-scope.ts b/packages/core/src/common/preferences/preference-scope.ts new file mode 100644 index 0000000000000..e05e001b69ca9 --- /dev/null +++ b/packages/core/src/common/preferences/preference-scope.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +// tslint:disable:no-any + +export enum PreferenceScope { + Default, + User, + Workspace, + Folder +} + +export namespace PreferenceScope { + export function is(scope: any): scope is PreferenceScope { + return typeof scope === 'number' && getScopes().findIndex(s => s === scope) >= 0; + } + + export function getScopes(): PreferenceScope[] { + return Object.keys(PreferenceScope) + .filter(k => typeof PreferenceScope[k as any] === 'string') + .map(v => Number(v)); + } + + export function getReversedScopes(): PreferenceScope[] { + return getScopes().reverse(); + } + + export function getScopeNames(scope?: PreferenceScope): string[] { + const names: string[] = []; + const allNames = Object.keys(PreferenceScope) + .filter(k => typeof PreferenceScope[k as any] === 'number'); + if (scope) { + for (const name of allNames) { + if ((PreferenceScope)[name] <= scope) { + names.push(name); + } + } + } + return names; + } + + export function fromString(strScope: string): PreferenceScope | undefined { + switch (strScope) { + case 'application': + return PreferenceScope.User; + case 'window': + return PreferenceScope.Workspace; + case 'resource': + return PreferenceScope.Folder; + } + } +} diff --git a/packages/core/src/common/resource.ts b/packages/core/src/common/resource.ts index e2506cb27b431..e095886221923 100644 --- a/packages/core/src/common/resource.ts +++ b/packages/core/src/common/resource.ts @@ -112,7 +112,7 @@ export class DefaultResourceProvider { // no-op } } - return Promise.reject(`A resource provider for '${uri.toString()}' is not registered.`); + return Promise.reject(new Error(`A resource provider for '${uri.toString()}' is not registered.`)); } } diff --git a/packages/core/src/common/selection-command-handler.ts b/packages/core/src/common/selection-command-handler.ts new file mode 100644 index 0000000000000..d8be975b16c0f --- /dev/null +++ b/packages/core/src/common/selection-command-handler.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 + ********************************************************************************/ + +// tslint:disable:no-any +import { CommandHandler } from './command'; +import { SelectionService } from '../common/selection-service'; + +export class SelectionCommandHandler implements CommandHandler { + + constructor( + protected readonly selectionService: SelectionService, + protected readonly toSelection: (arg: any) => S | undefined, + protected readonly options: SelectionCommandHandler.Options + ) { } + + execute(...args: any[]): Object | undefined { + const selection = this.getSelection(...args); + return selection ? (this.options.execute as any)(selection, ...args) : undefined; + } + + isVisible(...args: any[]): boolean { + const selection = this.getSelection(...args); + return !!selection && (!this.options.isVisible || (this.options.isVisible as any)(selection as any, ...args)); + } + + isEnabled(...args: any[]): boolean { + const selection = this.getSelection(...args); + return !!selection && (!this.options.isEnabled || (this.options.isEnabled as any)(selection as any, ...args)); + } + + protected isMulti(): boolean { + return this.options && !!this.options.multi; + } + + protected getSelection(...args: any[]): S | S[] | undefined { + const givenSelection = args.length && this.toSelection(args[0]); + if (givenSelection) { + return this.isMulti() ? [givenSelection] : givenSelection; + } + const globalSelection = this.getSingleSelection(this.selectionService.selection); + if (this.isMulti()) { + return this.getMulitSelection(globalSelection); + } + return this.getSingleSelection(globalSelection); + } + + protected getSingleSelection(arg: Object | undefined): S | undefined { + let selection = this.toSelection(arg); + if (selection) { + return selection; + } + if (Array.isArray(arg)) { + for (const element of arg) { + selection = this.toSelection(element); + if (selection) { + return selection; + } + } + } + return undefined; + } + + protected getMulitSelection(arg: Object | undefined): S[] | undefined { + let selection = this.toSelection(arg); + if (selection) { + return [selection]; + } + const result = []; + if (Array.isArray(arg)) { + for (const element of arg) { + selection = this.toSelection(element); + if (selection) { + result.push(selection); + } + } + } + return result.length ? result : undefined; + } +} +export namespace SelectionCommandHandler { + export type Options = SelectionOptions | SelectionOptions; + export interface SelectionOptions { + multi: Multi; + execute(selection: T, ...args: any[]): any; + isEnabled?(selection: T, ...args: any[]): boolean; + isVisible?(selection: T, ...args: any[]): boolean; + } +} diff --git a/packages/core/src/node/backend-application.ts b/packages/core/src/node/backend-application.ts index d24f46e32f9ce..ed7cdc8ff9dd3 100644 --- a/packages/core/src/node/backend-application.ts +++ b/packages/core/src/node/backend-application.ts @@ -24,6 +24,7 @@ import { ILogger, ContributionProvider, MaybePromise } from '../common'; import { CliContribution } from './cli'; import { Deferred } from '../common/promise-util'; import { environment } from '../common/index'; +import { AddressInfo } from 'net'; export const BackendApplicationContribution = Symbol('BackendApplicationContribution'); export interface BackendApplicationContribution { @@ -185,7 +186,7 @@ export class BackendApplication { server.listen(port, hostname, () => { const scheme = this.cliParams.ssl ? 'https' : 'http'; - this.logger.info(`Theia app listening on ${scheme}://${hostname || 'localhost'}:${server.address().port}.`); + this.logger.info(`Theia app listening on ${scheme}://${hostname || 'localhost'}:${(server.address() as AddressInfo).port}.`); deferred.resolve(server); }); diff --git a/packages/core/src/node/logger-cli-contribution.ts b/packages/core/src/node/logger-cli-contribution.ts index f891c5c414725..13369caddca90 100644 --- a/packages/core/src/node/logger-cli-contribution.ts +++ b/packages/core/src/node/logger-cli-contribution.ts @@ -19,7 +19,7 @@ import { injectable } from 'inversify'; import { LogLevel } from '../common/logger'; import { CliContribution } from './cli'; import * as fs from 'fs-extra'; -import * as nsfw from 'vscode-nsfw'; +import * as nsfw from 'nsfw'; import { Event, Emitter } from '../common/event'; import * as path from 'path'; diff --git a/packages/core/src/node/messaging/test/test-web-socket-channel.ts b/packages/core/src/node/messaging/test/test-web-socket-channel.ts index 355d963d1abbd..823e7b551cddb 100644 --- a/packages/core/src/node/messaging/test/test-web-socket-channel.ts +++ b/packages/core/src/node/messaging/test/test-web-socket-channel.ts @@ -19,6 +19,7 @@ import * as http from 'http'; import * as https from 'https'; import { WebSocketChannel } from '../../../common/messaging/web-socket-channel'; import { Disposable } from '../../../common/disposable'; +import { AddressInfo } from 'net'; export class TestWebSocketChannel extends WebSocketChannel { @@ -27,7 +28,7 @@ export class TestWebSocketChannel extends WebSocketChannel { path: string }) { super(0, content => socket.send(content)); - const socket = new ws(`ws://localhost:${server.address().port}${WebSocketChannel.wsPath}`); + const socket = new ws(`ws://localhost:${(server.address() as AddressInfo).port}${WebSocketChannel.wsPath}`); socket.on('error', error => this.fireError(error) ); diff --git a/packages/core/src/typings/nsfw/index.d.ts b/packages/core/src/typings/nsfw/index.d.ts index 62309125cab7b..de2f5475c0e23 100644 --- a/packages/core/src/typings/nsfw/index.d.ts +++ b/packages/core/src/typings/nsfw/index.d.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -declare module 'vscode-nsfw' { +declare module 'nsfw' { function nsfw(dir: string, eventHandler: (events: nsfw.ChangeEvent[]) => void, options?: nsfw.Options): Promise; namespace nsfw { diff --git a/packages/cpp/README.md b/packages/cpp/README.md index bb55ff4cf54ef..bb9a30bbddeb1 100644 --- a/packages/cpp/README.md +++ b/packages/cpp/README.md @@ -79,6 +79,30 @@ To get this working, you need to enable clangd's global index using the "cpp.clangdArgs": "--background-index" } +## Using the clang-tidy linter + +Note: This functionality is available when using clangd 9 and later. + +You can set the preference 'cpp.clangTidy' to enable the clang-tidy linter included in clangd. When the preference is set, there are two ways to chose which of its built-in checks clang-tidy will use: + +- using the preferences: 'cpp.clangTidyChecks' +- using the file '.clang-tidy' . The file is located in the same folder of the files or a parent folder. + +Note: using the preference checks will supersede the value found in the .clang-tidy file. + +The syntax used to fill the checks can be found at http://clang.llvm.org/extra/clang-tidy/ + +clang-tidy has its own checks and can also run Clang static analyzer checks. Each check has a name ([see link above for full list](http://clang.llvm.org/extra/clang-tidy/)). Clang-tidy takes as input the checks that should run, in the form of a comma-separated list of positive and negative (prefixed with -) globs. Positive globs add subsets of checks, negative globs remove them. + +There are two ways to configure clang-tidy's checks: through a Theia preference or using a .clang-tidy config file. Here are examples for both:" + + - for the preferences: "cpp.clangTidyChecks": "*,-readability-*" + - Meaning: enables all list-checks and disable all readability-* checks + + - for the .clang-tidy file: Checks: "-*,readability-*" + - Meaning: disable all list-checks and enable all readability-* checks + ## 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/cpp/package.json b/packages/cpp/package.json index 8ebeb650fd315..6ce6c0d6613bb 100644 --- a/packages/cpp/package.json +++ b/packages/cpp/package.json @@ -1,16 +1,16 @@ { "name": "@theia/cpp", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Cpp Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/preferences": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/task": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/preferences": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/task": "^0.5.0", "string-argv": "^0.1.1" }, "publishConfig": { @@ -44,11 +44,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/cpp/src/browser/cpp-language-client-contribution.ts b/packages/cpp/src/browser/cpp-language-client-contribution.ts index 039f94b3c6331..5200adf995d33 100644 --- a/packages/cpp/src/browser/cpp-language-client-contribution.ts +++ b/packages/cpp/src/browser/cpp-language-client-contribution.ts @@ -69,6 +69,11 @@ export class CppLanguageClientContribution extends BaseLanguageClientContributio @postConstruct() protected init() { this.cppBuildConfigurations.onActiveConfigChange(config => this.onActiveBuildConfigChanged(config)); + this.cppPreferences.onPreferenceChanged(e => { + if (this.running) { + this.restart(); + } + }); } protected onReady(languageClient: ILanguageClient): void { @@ -140,7 +145,9 @@ export class CppLanguageClientContribution extends BaseLanguageClientContributio protected getStartParameters(): CppStartParameters { return { clangdExecutable: this.cppPreferences['cpp.clangdExecutable'], - clangdArgs: this.cppPreferences['cpp.clangdArgs'] + clangdArgs: this.cppPreferences['cpp.clangdArgs'], + clangTidy: this.cppPreferences['cpp.clangTidy'], + clangTidyChecks: this.cppPreferences['cpp.clangTidyChecks'] }; } } diff --git a/packages/cpp/src/browser/cpp-preferences.ts b/packages/cpp/src/browser/cpp-preferences.ts index f91e5119e5579..731c6eb652a20 100644 --- a/packages/cpp/src/browser/cpp-preferences.ts +++ b/packages/cpp/src/browser/cpp-preferences.ts @@ -61,6 +61,16 @@ export const cppPreferencesSchema: PreferenceSchema = { description: 'Specify the arguments to pass to clangd when starting the language server.', default: '', type: 'string' + }, + 'cpp.clangTidy': { + description: 'Enable/disable C/C++ linting.', + default: false, + type: 'boolean' + }, + 'cpp.clangTidyChecks': { + description: 'Specify comma separated arguments to pass to clang-tidy. Activated only if cpp.clang-tidy is enabled', + default: '', + type: 'string' } } }; @@ -70,6 +80,8 @@ export class CppConfiguration { 'cpp.experimentalCommands': boolean; 'cpp.clangdExecutable': string; 'cpp.clangdArgs': string; + 'cpp.clangTidy': boolean; + 'cpp.clangTidyChecks': string; } export const CppPreferences = Symbol('CppPreferences'); diff --git a/packages/cpp/src/common/index.ts b/packages/cpp/src/common/index.ts index 08d489e56a1bf..3c4d79c626a34 100644 --- a/packages/cpp/src/common/index.ts +++ b/packages/cpp/src/common/index.ts @@ -27,4 +27,6 @@ export const CLANGD_EXECUTABLE_DEFAULT = 'clangd'; export interface CppStartParameters { clangdExecutable: string; clangdArgs: string; + clangTidy?: boolean; + clangTidyChecks?: string; } diff --git a/packages/cpp/src/node/cpp-contribution.ts b/packages/cpp/src/node/cpp-contribution.ts index 5bec335b74d09..aedef76bf05bf 100644 --- a/packages/cpp/src/node/cpp-contribution.ts +++ b/packages/cpp/src/node/cpp-contribution.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2017 Ericsson and others. + * Copyright (C) 2017-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 @@ -42,7 +42,34 @@ export class CppContribution extends BaseLanguageServerContribution { || undefined ); + const clangTidy = parameters && parameters.clangTidy; + const clangTidyChecks = parameters && parameters.clangTidyChecks; + + if (clangTidy) { + const supportsClangTidy = await this.testSupportsClangTidy(command); + if (supportsClangTidy) { + args.push('-clang-tidy'); + if (typeof clangTidyChecks === 'string' && clangTidyChecks.length > 0) { + args.push(`-clang-tidy-checks=${clangTidyChecks}`); + } + } + } const serverConnection = await this.createProcessStreamConnectionAsync(command, args); this.forward(clientConnection, serverConnection); } + + protected async testSupportsClangTidy(command: string): Promise { + // clangd should fail if -clang-tidy flag is not supported + // but if it is supported, it will run forever until killed/stopped. + // to avoid that, we pass -version flag which makes it exit early. + const process = await super.spawnProcessAsync(command, ['-clang-tidy', '-version']); + return new Promise(resolve => { + process.errorOutput.on('data', (data: string) => { + if (data.includes('-clang-tidy')) { + resolve(false); + } + }); + process.errorOutput.once('close', () => resolve(true)); + }); + } } diff --git a/packages/debug-nodejs/package.json b/packages/debug-nodejs/package.json index 62eb50b1d3a6c..6c09dc04db38b 100644 --- a/packages/debug-nodejs/package.json +++ b/packages/debug-nodejs/package.json @@ -1,9 +1,9 @@ { "name": "@theia/debug-nodejs", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - NodeJS Debug Extension", "dependencies": { - "@theia/debug": "^0.4.0", + "@theia/debug": "^0.5.0", "ps-list": "5.0.1", "vscode-debugprotocol": "^1.32.0" }, @@ -40,11 +40,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/debug/package.json b/packages/debug/package.json index ab85676001863..b7eee7d6085d5 100644 --- a/packages/debug/package.json +++ b/packages/debug/package.json @@ -1,20 +1,20 @@ { "name": "@theia/debug", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Debug Extension", "dependencies": { - "@theia/console": "^0.4.0", - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/json": "^0.4.0", - "@theia/markers": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/output": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/terminal": "^0.4.0", - "@theia/variable-resolver": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/console": "^0.5.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/json": "^0.5.0", + "@theia/markers": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/output": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/terminal": "^0.5.0", + "@theia/variable-resolver": "^0.5.0", + "@theia/workspace": "^0.5.0", "@types/p-debounce": "^1.0.0", "jsonc-parser": "^2.0.2", "mkdirp": "^0.5.0", @@ -60,11 +60,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/debug/src/browser/console/debug-console-contribution.ts b/packages/debug/src/browser/console/debug-console-contribution.ts index 32689a83913fb..458c53d0ff368 100644 --- a/packages/debug/src/browser/console/debug-console-contribution.ts +++ b/packages/debug/src/browser/console/debug-console-contribution.ts @@ -41,7 +41,8 @@ export class DebugConsoleContribution extends AbstractViewContribution { this.manager.onDidCreateDebugSession(session => this.openSession(session, { reveal: false })); this.manager.onDidStartDebugSession(session => { - const { noDebug, openDebug, internalConsoleOptions } = session.configuration; + const { noDebug } = session.configuration; + const openDebug = session.configuration.openDebug || this.preference['debug.openDebug']; + const internalConsoleOptions = session.configuration.internalConsoleOptions || this.preference['debug.internalConsoleOptions']; if (internalConsoleOptions === 'openOnSessionStart' || (internalConsoleOptions === 'openOnFirstSessionStart' && this.firstSessionStart)) { this.console.openView({ @@ -878,7 +884,7 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi } ): Promise { const { debugViewLocation, reveal } = { - debugViewLocation: session.configuration.debugViewLocation, + debugViewLocation: session.configuration.debugViewLocation || this.preference['debug.debugViewLocation'], reveal: true, ...options }; diff --git a/packages/debug/src/browser/debug-preferences.ts b/packages/debug/src/browser/debug-preferences.ts index 951774d6011fa..94e40814c5125 100644 --- a/packages/debug/src/browser/debug-preferences.ts +++ b/packages/debug/src/browser/debug-preferences.ts @@ -24,12 +24,30 @@ export const debugPreferencesSchema: PreferenceSchema = { type: 'boolean', default: false, description: 'Enable/disable tracing communications with debug adapters' + }, + 'debug.debugViewLocation': { + enum: ['default', 'left', 'right', 'bottom'], + default: 'default', + description: 'Controls the location of the debug view.' + }, + 'debug.openDebug': { + enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], + default: 'openOnSessionStart', + description: 'Controls when the debug view should open.' + }, + 'debug.internalConsoleOptions': { + enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'], + default: 'openOnFirstSessionStart', + description: 'Controls when the internal debug console should open.' } } }; export class DebugConfiguration { 'debug.trace': boolean; + 'debug.debugViewLocation': 'default' | 'left' | 'right' | 'bottom'; + 'debug.openDebug': 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak'; + 'debug.internalConsoleOptions': 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; } export const DebugPreferences = Symbol('DebugPreferences'); diff --git a/packages/debug/src/browser/debug-schema-updater.ts b/packages/debug/src/browser/debug-schema-updater.ts index 7b18d64721af1..03e4417718173 100644 --- a/packages/debug/src/browser/debug-schema-updater.ts +++ b/packages/debug/src/browser/debug-schema-updater.ts @@ -20,6 +20,7 @@ import { InMemoryResources, deepClone } from '@theia/core/lib/common'; import { IJSONSchema } from '@theia/core/lib/common/json-schema'; import URI from '@theia/core/lib/common/uri'; import { DebugService } from '../common/debug-service'; +import { debugPreferencesSchema } from './debug-preferences'; @injectable() export class DebugSchemaUpdater { @@ -37,24 +38,11 @@ export class DebugSchemaUpdater { const attributePromises = types.map(type => this.debug.getSchemaAttributes(type)); for (const attributes of await Promise.all(attributePromises)) { for (const attribute of attributes) { - attribute.properties = { - 'debugViewLocation': { - enum: ['default', 'left', 'right', 'bottom'], - default: 'default', - description: 'Controls the location of the debug view.' - }, - 'openDebug': { - enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], - default: 'openOnSessionStart', - description: 'Controls when the debug view should open.' - }, - 'internalConsoleOptions': { - enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'], - default: 'openOnFirstSessionStart', - description: 'Controls when the internal debug console should open.' - }, - ...attribute.properties - }; + const properties: typeof attribute['properties'] = {}; + for (const key of ['debugViewLocation', 'openDebug', 'internalConsoleOptions']) { + properties[key] = debugPreferencesSchema.properties[`debug.${key}`]; + } + attribute.properties = Object.assign(properties, attribute.properties); items.oneOf!.push(attribute); } } diff --git a/packages/debug/src/browser/debug-session.tsx b/packages/debug/src/browser/debug-session.tsx index d0bd64c151758..c6c3a4336bbfe 100644 --- a/packages/debug/src/browser/debug-session.tsx +++ b/packages/debug/src/browser/debug-session.tsx @@ -36,6 +36,7 @@ import { DebugSessionOptions, InternalDebugSessionOptions } from './debug-sessio import { DebugConfiguration } from '../common/debug-common'; import { SourceBreakpoint } from './breakpoint/breakpoint-marker'; import { FileSystem } from '@theia/filesystem/lib/common'; +import { TerminalWidgetOptions } from '@theia/terminal/lib/browser/base/terminal-widget'; export enum DebugState { Inactive, @@ -257,7 +258,7 @@ export class DebugSession implements CompositeTreeElement { supportsVariablePaging: false, supportsRunInTerminalRequest: true }); - this._capabilities = response.body || {}; + this.updateCapabilities(response.body || {}); } protected async launchOrAttach(): Promise { try { @@ -367,7 +368,11 @@ export class DebugSession implements CompositeTreeElement { } protected async runInTerminal({ arguments: { title, cwd, args, env } }: DebugProtocol.RunInTerminalRequest): Promise { - const terminal = await this.terminalServer.newTerminal({ title, cwd, shellPath: args[0], shellArgs: args.slice(1), env }); + return this.doRunInTerminal({ title, cwd, shellPath: args[0], shellArgs: args.slice(1), env }); + } + + protected async doRunInTerminal(options: TerminalWidgetOptions): Promise { + const terminal = await this.terminalServer.newTerminal(options); this.terminalServer.activateTerminal(terminal); const processId = await terminal.start(); return { processId }; diff --git a/packages/debug/src/browser/style/debug-dark.svg b/packages/debug/src/browser/style/debug-dark.svg new file mode 100644 index 0000000000000..5c4141704fbfb --- /dev/null +++ b/packages/debug/src/browser/style/debug-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/debug/src/browser/style/index.css b/packages/debug/src/browser/style/index.css index d4303c3d41ec7..ad1376f29fe95 100644 --- a/packages/debug/src/browser/style/index.css +++ b/packages/debug/src/browser/style/index.css @@ -23,6 +23,11 @@ color: var(--theia-ui-font-color1); } +.theia-side-panel .theia-debug-container .theia-ExpansionToggle { + padding-right: 4px; + min-width: 10px; +} + #theia-bottom-content-panel .theia-session-container .theia-view-container { flex-direction: row; } @@ -118,6 +123,10 @@ margin-left: var(--theia-ui-padding); } +.theia-side-panel .debug-toolbar { + padding-left: 3px; +} + .theia-session-container > .debug-toolbar { padding-top: var(--theia-ui-padding); padding-bottom: var(--theia-ui-padding); @@ -152,8 +161,14 @@ opacity: 1; } -.debug-tab-icon::before { - content: "\f188" +.debug-tab-icon { + -webkit-mask: url('debug-dark.svg') 50%; + mask: url('debug-dark.svg') 50%; +} + +.theia-debug-console-icon { + -webkit-mask: url('repl.svg'); + mask: url('repl.svg'); } /** Console */ diff --git a/packages/debug/src/browser/view/debug-widget.ts b/packages/debug/src/browser/view/debug-widget.ts index 1a527c6452cb2..6e7b07fb5b728 100644 --- a/packages/debug/src/browser/view/debug-widget.ts +++ b/packages/debug/src/browser/view/debug-widget.ts @@ -55,7 +55,7 @@ export class DebugWidget extends BaseWidget implements ApplicationShell.Trackabl this.title.label = DebugWidget.LABEL; this.title.caption = DebugWidget.LABEL; this.title.closable = true; - this.title.iconClass = 'fa debug-tab-icon'; + this.title.iconClass = 'debug-tab-icon'; this.addClass('theia-debug-container'); this.toDispose.pushAll([ this.toolbar, diff --git a/packages/editor-preview/package.json b/packages/editor-preview/package.json index 5f34d43682295..69be430d0d338 100644 --- a/packages/editor-preview/package.json +++ b/packages/editor-preview/package.json @@ -1,11 +1,11 @@ { "name": "@theia/editor-preview", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Editor Preview Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/navigator": "^0.4.0" + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/navigator": "^0.5.0" }, "publishConfig": { "access": "public" @@ -36,11 +36,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/editor-preview/src/browser/editor-preview-manager.ts b/packages/editor-preview/src/browser/editor-preview-manager.ts index 09665ffab324f..3f7a2fd0f8b4d 100644 --- a/packages/editor-preview/src/browser/editor-preview-manager.ts +++ b/packages/editor-preview/src/browser/editor-preview-manager.ts @@ -17,7 +17,7 @@ import { injectable, inject, postConstruct } from 'inversify'; import URI from '@theia/core/lib/common/uri'; import { ApplicationShell, DockPanel } from '@theia/core/lib/browser'; -import { EditorManager, EditorOpenerOptions, EditorWidget } from '@theia/editor/lib/browser'; +import { EditorManager, EditorOpenerOptions, EditorWidget } from '@theia/editor/lib/browser'; import { EditorPreviewWidget } from './editor-preview-widget'; import { EditorPreviewWidgetFactory, EditorPreviewWidgetOptions } from './editor-preview-factory'; import { EditorPreviewPreferences } from './editor-preview-preferences'; @@ -35,7 +35,7 @@ export interface PreviewEditorOpenerOptions extends EditorOpenerOptions { * Class for managing an editor preview widget. */ @injectable() -export class EditorPreviewManager extends WidgetOpenHandler { +export class EditorPreviewManager extends WidgetOpenHandler { readonly id = EditorPreviewWidgetFactory.ID; @@ -60,6 +60,14 @@ export class EditorPreviewManager extends WidgetOpenHandler { + this.currentEditorPreview.then(editorPreview => { + if (!change.newValue && editorPreview) { + editorPreview.pinEditorWidget(); + } + }); + }); } protected async handlePreviewWidgetCreated(widget: EditorPreviewWidget): Promise { @@ -72,13 +80,13 @@ export class EditorPreviewManager extends WidgetOpenHandler this.currentEditorPreview = Promise.resolve(undefined)); - widget.onPinned(({preview, editorWidget}) => { + widget.onPinned(({ preview, editorWidget }) => { // TODO(caseyflynn): I don't believe there is ever a case where // this will not hold true. if (preview.parent && preview.parent instanceof DockPanel) { - preview.parent.addWidget(editorWidget, {ref: preview}); + preview.parent.addWidget(editorWidget, { ref: preview }); } else { - this.shell.addWidget(editorWidget, {area: 'main'}); + this.shell.addWidget(editorWidget, { area: 'main' }); } preview.dispose(); this.shell.activateWidget(editorWidget.id); @@ -100,7 +108,7 @@ export class EditorPreviewManager extends WidgetOpenHandler { - options = {...options, mode: 'open'}; + options = { ...options, mode: 'open' }; const deferred = new Deferred(); const previousPreview = await this.currentEditorPreview; diff --git a/packages/editor/package.json b/packages/editor/package.json index 49ccf28a64de5..2aa989b8dda1f 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,11 +1,11 @@ { "name": "@theia/editor", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Editor Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/variable-resolver": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/variable-resolver": "^0.5.0", "@types/base64-arraybuffer": "0.1.0", "base64-arraybuffer": "^0.1.5" }, @@ -38,11 +38,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/editor/src/browser/editor-manager.ts b/packages/editor/src/browser/editor-manager.ts index 2d29c98e1b8bd..29b549d8f8dba 100644 --- a/packages/editor/src/browser/editor-manager.ts +++ b/packages/editor/src/browser/editor-manager.ts @@ -25,6 +25,7 @@ import { TextEditor } from './editor'; export interface EditorOpenerOptions extends WidgetOpenerOptions { selection?: RecursivePartial; + preview?: boolean; } @injectable() diff --git a/packages/editor/src/browser/editor-preferences.ts b/packages/editor/src/browser/editor-preferences.ts index 55b1fd69b5a9d..8cf52bd11cd52 100644 --- a/packages/editor/src/browser/editor-preferences.ts +++ b/packages/editor/src/browser/editor-preferences.ts @@ -96,8 +96,8 @@ export const editorPreferenceSchema: PreferenceSchema = { 'on', 'off' ], - 'default': 'on', - 'description': 'Configure whether the editor should be auto saved.', + 'default': 'off', + 'description': 'Controls auto save of dirty files.', overridable: false }, 'editor.autoSaveDelay': { diff --git a/packages/editorconfig/package.json b/packages/editorconfig/package.json index 5ba2f390d6730..ab44620d22e58 100644 --- a/packages/editorconfig/package.json +++ b/packages/editorconfig/package.json @@ -1,12 +1,12 @@ { "name": "@theia/editorconfig", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Editorconfig Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/monaco": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/monaco": "^0.5.0", "editorconfig": "^0.15.0" }, "publishConfig": { @@ -39,11 +39,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/editorconfig/src/browser/editorconfig-document-manager.ts b/packages/editorconfig/src/browser/editorconfig-document-manager.ts index 803e707744ae8..53d790a40ee4c 100644 --- a/packages/editorconfig/src/browser/editorconfig-document-manager.ts +++ b/packages/editorconfig/src/browser/editorconfig-document-manager.ts @@ -98,6 +98,7 @@ export class EditorconfigDocumentManager { const uri = editor.uri.toString(); this.editorconfigServer.getConfig(uri).then(properties => { this.properties[uri] = properties; + this.applyProperties(properties, editor); }); } } diff --git a/packages/extension-manager/package.json b/packages/extension-manager/package.json index af2701394401c..5c6e3aaf25442 100644 --- a/packages/extension-manager/package.json +++ b/packages/extension-manager/package.json @@ -1,12 +1,12 @@ { "name": "@theia/extension-manager", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Extension Manager", "dependencies": { - "@theia/application-manager": "^0.4.0", - "@theia/application-package": "^0.4.0", - "@theia/core": "^0.4.0", - "@theia/filesystem": "^0.4.0", + "@theia/application-manager": "^0.5.0", + "@theia/application-package": "^0.5.0", + "@theia/core": "^0.5.0", + "@theia/filesystem": "^0.5.0", "@types/fs-extra": "^4.0.2", "@types/sanitize-html": "^1.13.31", "@types/showdown": "^1.7.1", @@ -44,11 +44,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/extension-manager/src/browser/extension-contribution.ts b/packages/extension-manager/src/browser/extension-contribution.ts index 462ffcb4e5868..e5f70f62a0184 100644 --- a/packages/extension-manager/src/browser/extension-contribution.ts +++ b/packages/extension-manager/src/browser/extension-contribution.ts @@ -35,7 +35,7 @@ export class ExtensionContribution extends AbstractViewContribution + + + diff --git a/packages/file-search/package.json b/packages/file-search/package.json index b236492fc060e..12388d6cc4df1 100644 --- a/packages/file-search/package.json +++ b/packages/file-search/package.json @@ -1,13 +1,13 @@ { "name": "@theia/file-search", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - File Search Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/workspace": "^0.5.0", "fuzzy": "^0.1.3", "vscode-ripgrep": "^1.2.4" }, @@ -41,11 +41,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/file-search/src/browser/quick-file-open.ts b/packages/file-search/src/browser/quick-file-open.ts index f04120e7f1abd..9e99e61c2bab7 100644 --- a/packages/file-search/src/browser/quick-file-open.ts +++ b/packages/file-search/src/browser/quick-file-open.ts @@ -205,6 +205,7 @@ export class QuickFileOpenService implements QuickOpenModel, QuickOpenHandler { fuzzyMatch: true, limit: 200, useGitIgnore: this.hideIgnoredFiles, + excludePatterns: ['*.git*'] }, token).then(handler); } else { acceptor(recentlyUsedItems); diff --git a/packages/file-search/src/common/file-search-service.ts b/packages/file-search/src/common/file-search-service.ts index 3b1dbc0141c32..71ac6054e0238 100644 --- a/packages/file-search/src/common/file-search-service.ts +++ b/packages/file-search/src/common/file-search-service.ts @@ -33,13 +33,24 @@ export interface FileSearchService { export const FileSearchService = Symbol('FileSearchService'); export namespace FileSearchService { - export interface Options { - rootUris: string[] + export interface BaseOptions { + useGitIgnore?: boolean + includePatterns?: string[] + excludePatterns?: string[] + } + export interface RootOptions { + [rootUri: string]: BaseOptions + } + export interface Options extends BaseOptions { + rootUris?: string[] + rootOptions?: RootOptions fuzzyMatch?: boolean limit?: number - useGitIgnore?: boolean - /** when `undefined`, no excludes will apply, when empty array, default excludes will apply */ + /** + * when `undefined`, no excludes will apply, when empty array, default excludes will apply + * + * @deprecated since 0.5.0 use `excludePatterns` instead + */ defaultIgnorePatterns?: string[] - includePatterns?: string[] } } diff --git a/packages/file-search/src/node/file-search-service-impl.spec.ts b/packages/file-search/src/node/file-search-service-impl.spec.ts index 074413ef88715..70cfb4bc86cda 100644 --- a/packages/file-search/src/node/file-search-service-impl.spec.ts +++ b/packages/file-search/src/node/file-search-service-impl.spec.ts @@ -24,6 +24,7 @@ import { CancellationTokenSource } from '@theia/core'; import { bindLogger } from '@theia/core/lib/node/logger-backend-module'; import processBackendModule from '@theia/process/lib/node/process-backend-module'; import URI from '@theia/core/lib/common/uri'; +import { FileSearchService } from '../common/file-search-service'; // tslint:disable:no-unused-expression @@ -89,35 +90,71 @@ describe('search-service', function () { expect(matches.length).to.eq(1); }); - it('should support file searches with globs without the prefixed or trailing star (*)', async () => { + it('should NOT support file searches with globs without the prefixed or trailing star (*)', async () => { const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString(); const trailingMatches = await service.find('', { rootUris: [rootUri], includePatterns: ['*oo'] }); expect(trailingMatches).to.be.not.undefined; - expect(trailingMatches.length).to.eq(1); + expect(trailingMatches.length).to.eq(0); const prefixedMatches = await service.find('', { rootUris: [rootUri], includePatterns: ['oo*'] }); expect(prefixedMatches).to.be.not.undefined; - expect(prefixedMatches.length).to.eq(1); + expect(prefixedMatches.length).to.eq(0); }); }); describe('search with ignored patterns', () => { - it('should ignore strings passed through the search options', async () => { + it('should NOT ignore strings passed through the search options', async () => { const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString(); - const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'], defaultIgnorePatterns: ['foo'] }); + const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'], excludePatterns: ['foo'] }); expect(matches).to.be.not.undefined; - expect(matches.length).to.eq(0); + expect(matches.length).to.eq(1); }); - it('should ignore globs passed through the search options', async () => { - const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString(); - - const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'], defaultIgnorePatterns: ['*fo*'] }); + const ignoreGlobsUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString(); + it('should ignore globs passed through the search options #1', () => assertIgnoreGlobs({ + rootUris: [ignoreGlobsUri], + includePatterns: ['**/*oo.*'], + excludePatterns: ['*fo*'] + })); + it('should ignore globs passed through the search options #2', () => assertIgnoreGlobs({ + rootOptions: { + [ignoreGlobsUri]: { + includePatterns: ['**/*oo.*'], + excludePatterns: ['*fo*'] + } + } + })); + it('should ignore globs passed through the search options #3', () => assertIgnoreGlobs({ + rootOptions: { + [ignoreGlobsUri]: { + includePatterns: ['**/*oo.*'] + } + }, + excludePatterns: ['*fo*'] + })); + it('should ignore globs passed through the search options #4', () => assertIgnoreGlobs({ + rootOptions: { + [ignoreGlobsUri]: { + excludePatterns: ['*fo*'] + } + }, + includePatterns: ['**/*oo.*'] + })); + it('should ignore globs passed through the search options #5', () => assertIgnoreGlobs({ + rootOptions: { + [ignoreGlobsUri]: {} + }, + excludePatterns: ['*fo*'], + includePatterns: ['**/*oo.*'] + })); + + async function assertIgnoreGlobs(options: FileSearchService.Options): Promise { + const matches = await service.find('', options); expect(matches).to.be.not.undefined; expect(matches.length).to.eq(0); - }); + } }); describe('irrelevant absolute results', () => { diff --git a/packages/file-search/src/node/file-search-service-impl.ts b/packages/file-search/src/node/file-search-service-impl.ts index 72da3d32625b0..bafc5f03c62ed 100644 --- a/packages/file-search/src/node/file-search-service-impl.ts +++ b/packages/file-search/src/node/file-search-service-impl.ts @@ -37,23 +37,45 @@ export class FileSearchServiceImpl implements FileSearchService { clientToken.onCancellationRequested(() => cancellationSource.cancel()); } const token = cancellationSource.token; - - if (options.defaultIgnorePatterns && options.defaultIgnorePatterns.length === 0) { // default excludes - options.defaultIgnorePatterns.push('.git'); - } const opts = { fuzzyMatch: true, limit: Number.MAX_SAFE_INTEGER, useGitIgnore: true, ...options }; + + const roots: FileSearchService.RootOptions = options.rootOptions || {}; + if (options.rootUris) { + for (const rootUri of options.rootUris) { + if (!roots[rootUri]) { + roots[rootUri] = {}; + } + } + } + // tslint:disable-next-line:forin + for (const rootUri in roots) { + const rootOptions = roots[rootUri]; + if (opts.includePatterns) { + const includePatterns = rootOptions.includePatterns || []; + rootOptions.includePatterns = [...includePatterns, ...opts.includePatterns]; + } + if (opts.excludePatterns) { + const excludePatterns = rootOptions.excludePatterns || []; + rootOptions.excludePatterns = [...excludePatterns, ...opts.excludePatterns]; + } + if (rootOptions.useGitIgnore === undefined) { + rootOptions.useGitIgnore = opts.useGitIgnore; + } + } + const exactMatches = new Set(); const fuzzyMatches = new Set(); const stringPattern = searchPattern.toLocaleLowerCase(); - await Promise.all(options.rootUris.map(async root => { + await Promise.all(Object.keys(roots).map(async root => { try { const rootUri = new URI(root); - await this.doFind(rootUri, searchPattern, opts, candidate => { + const rootOptions = roots[root]; + await this.doFind(rootUri, rootOptions, candidate => { const fileUri = rootUri.resolve(candidate).toString(); if (exactMatches.has(fileUri) || fuzzyMatches.has(fileUri)) { return; @@ -77,7 +99,7 @@ export class FileSearchServiceImpl implements FileSearchService { return [...exactMatches, ...fuzzyMatches]; } - private doFind(rootUri: URI, searchPattern: string, options: FileSearchService.Options, + private doFind(rootUri: URI, options: FileSearchService.BaseOptions, accept: (fileUri: string) => void, token: CancellationToken): Promise { return new Promise((resolve, reject) => { try { @@ -106,38 +128,25 @@ export class FileSearchServiceImpl implements FileSearchService { }); } - private getSearchArgs(options: FileSearchService.Options): string[] { + private getSearchArgs(options: FileSearchService.BaseOptions): string[] { const args = ['--files', '--case-sensitive']; if (options.includePatterns) { for (const includePattern of options.includePatterns) { - let includeGlob = includePattern; - if (!includeGlob.endsWith('*')) { - includeGlob = `${includeGlob}*`; + if (includePattern) { + args.push('--glob', includePattern); } - if (!includeGlob.startsWith('*')) { - includeGlob = `*${includeGlob}`; + } + } + if (options.excludePatterns) { + for (const excludePattern of options.excludePatterns) { + if (excludePattern) { + args.push('--glob', `!${excludePattern}`); } - args.push('--glob', includeGlob); } } if (!options.useGitIgnore) { args.push('-uu'); } - if (options && options.defaultIgnorePatterns) { - options.defaultIgnorePatterns.filter(p => p !== '') - .forEach(ignore => { - if (!ignore.endsWith('*')) { - ignore = `${ignore}*`; - } - if (!ignore.startsWith('*')) { - ignore = `!*${ignore}`; - } else { - ignore = `!${ignore}`; - } - args.push('--glob'); - args.push(ignore); - }); - } return args; } diff --git a/packages/filesystem/package.json b/packages/filesystem/package.json index 32339e1aca79e..b71f7cfe05131 100644 --- a/packages/filesystem/package.json +++ b/packages/filesystem/package.json @@ -1,11 +1,12 @@ { "name": "@theia/filesystem", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - FileSystem Extension", "dependencies": { - "@theia/core": "^0.4.0", + "@theia/core": "^0.5.0", "@types/base64-js": "^1.2.5", "@types/body-parser": "^1.17.0", + "@types/formidable": "^1.0.31", "@types/fs-extra": "^4.0.2", "@types/mime-types": "^2.1.0", "@types/rimraf": "^2.0.2", @@ -15,6 +16,7 @@ "base64-js": "^1.2.1", "body-parser": "^1.18.3", "drivelist": "^6.4.3", + "formidable": "^1.2.1", "fs-extra": "^4.0.2", "http-status-codes": "^1.3.0", "mime-types": "^2.1.18", @@ -66,11 +68,10 @@ "build": "theiaext build", "watch": "theiaext watch", "test": "theiaext test", - "test:watch": "theiaext test:watch", - "docs": "theiaext docs" + "test:watch": "theiaext test:watch" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/filesystem/src/browser/download/file-download-command-contribution.ts b/packages/filesystem/src/browser/download/file-download-command-contribution.ts index 079ff055c1359..4e19defa86b9f 100644 --- a/packages/filesystem/src/browser/download/file-download-command-contribution.ts +++ b/packages/filesystem/src/browser/download/file-download-command-contribution.ts @@ -16,12 +16,14 @@ import { inject, injectable } from 'inversify'; import URI from '@theia/core/lib/common/uri'; -import { notEmpty } from '@theia/core/lib/common/objects'; -import { UriSelection } from '@theia/core/lib/common/selection'; import { SelectionService } from '@theia/core/lib/common/selection-service'; import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common/command'; import { UriAwareCommandHandler, UriCommandHandler } from '@theia/core/lib/common/uri-command-handler'; +import { ExpandableTreeNode } from '@theia/core/lib/browser/tree'; import { FileDownloadService } from './file-download-service'; +import { FileSelection } from '../file-selection'; +import { TreeWidgetSelection } from '@theia/core/lib/browser/tree/tree-widget-selection'; +import { isCancelled } from '@theia/core/lib/common/cancellation'; @injectable() export class FileDownloadCommandContribution implements CommandContribution { @@ -35,6 +37,30 @@ export class FileDownloadCommandContribution implements CommandContribution { registerCommands(registry: CommandRegistry): void { const handler = new UriAwareCommandHandler(this.selectionService, this.downloadHandler(), { multi: true }); registry.registerCommand(FileDownloadCommands.DOWNLOAD, handler); + registry.registerCommand(FileDownloadCommands.UPLOAD, new FileSelection.CommandHandler(this.selectionService, { + multi: false, + isEnabled: selection => this.canUpload(selection), + isVisible: selection => this.canUpload(selection), + execute: selection => this.upload(selection) + })); + } + + protected canUpload({ fileStat }: FileSelection): boolean { + return fileStat.isDirectory; + } + + protected async upload(selection: FileSelection): Promise { + try { + const source = TreeWidgetSelection.getSource(this.selectionService.selection); + await this.downloadService.upload(selection.fileStat.uri); + if (ExpandableTreeNode.is(selection) && source) { + await source.model.expandNode(selection); + } + } catch (e) { + if (!isCancelled(e)) { + console.error(e); + } + } } protected downloadHandler(): UriCommandHandler { @@ -57,29 +83,20 @@ export class FileDownloadCommandContribution implements CommandContribution { return this.isDownloadEnabled(uris); } - protected getUris(uri: Object | undefined): URI[] { - if (uri === undefined) { - return []; - } - return (Array.isArray(uri) ? uri : [uri]).map(u => this.getUri(u)).filter(notEmpty); - } - - protected getUri(uri: Object | undefined): URI | undefined { - if (uri instanceof URI) { - return uri; - } - if (UriSelection.is(uri)) { - return uri.uri; - } - return undefined; - } - } export namespace FileDownloadCommands { export const DOWNLOAD: Command = { - id: 'file.download' + id: 'file.download', + category: 'File', + label: 'Download' + }; + + export const UPLOAD: Command = { + id: 'file.upload', + category: 'File', + label: 'Upload Files...' }; } diff --git a/packages/filesystem/src/browser/download/file-download-service.ts b/packages/filesystem/src/browser/download/file-download-service.ts index 25d795ab9ec47..b870a97ca5824 100644 --- a/packages/filesystem/src/browser/download/file-download-service.ts +++ b/packages/filesystem/src/browser/download/file-download-service.ts @@ -14,13 +14,16 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from 'inversify'; +import { inject, injectable, postConstruct } from 'inversify'; import URI from '@theia/core/lib/common/uri'; +import { cancelled } from '@theia/core/lib/common/cancellation'; import { ILogger } from '@theia/core/lib/common/logger'; import { Endpoint } from '@theia/core/lib/browser/endpoint'; import { StatusBar, StatusBarAlignment } from '@theia/core/lib/browser/status-bar'; import { FileSystem } from '../../common/filesystem'; import { FileDownloadData } from '../../common/download/file-download-data'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { MessageService } from '@theia/core/lib/common/message-service'; @injectable() export class FileDownloadService { @@ -40,6 +43,110 @@ export class FileDownloadService { @inject(StatusBar) protected readonly statusBar: StatusBar; + @inject(MessageService) + protected readonly messageService: MessageService; + + protected uploadForm: { + target: HTMLInputElement + file: HTMLInputElement + }; + + @postConstruct() + protected init(): void { + this.uploadForm = this.createUploadForm(); + } + + protected createUploadForm(): { + target: HTMLInputElement + file: HTMLInputElement + } { + const target = document.createElement('input'); + target.type = 'text'; + target.name = 'target'; + + const file = document.createElement('input'); + file.type = 'file'; + file.name = 'upload'; + file.multiple = true; + + const form = document.createElement('form'); + form.style.display = 'none'; + form.enctype = 'multipart/form-data'; + form.append(target); + form.append(file); + + document.body.appendChild(form); + + file.addEventListener('change', async () => { + if (file.value) { + const body = new FormData(form); + // clean up to allow upload to the same folder twice + file.value = ''; + const filesUrl = this.filesUrl(); + const deferredUpload = this.deferredUpload; + try { + const request = new XMLHttpRequest(); + + const cb = () => { + if (request.status === 200) { + deferredUpload.resolve(); + } else { + let statusText = request.statusText; + if (!statusText) { + if (request.status === 413) { + statusText = 'Payload Too Large'; + } else if (request.status) { + statusText = String(request.status); + } else { + statusText = 'Network Failure'; + } + } + const message = 'Upload Failed: ' + statusText; + deferredUpload.reject(new Error(message)); + this.messageService.error(message); + } + }; + request.addEventListener('load', cb); + request.addEventListener('error', cb); + request.addEventListener('abort', () => deferredUpload.reject(cancelled())); + + const progress = await this.messageService.showProgress({ + text: 'Uploading Files...', options: { cancelable: true } + }, () => { + request.upload.removeEventListener('progress', progressListener); + request.abort(); + }); + deferredUpload.promise.then(() => progress.cancel(), () => progress.cancel()); + const progressListener = (event: ProgressEvent) => { + if (event.lengthComputable) { + progress.report({ + work: { + done: event.loaded, + total: event.total + } + }); + } + }; + request.upload.addEventListener('progress', progressListener); + + request.open('POST', filesUrl); + request.send(body); + } catch (e) { + deferredUpload.reject(e); + } + } + }); + return { target, file }; + } + + protected deferredUpload = new Deferred(); + upload(targetUri: string | URI): Promise { + this.deferredUpload = new Deferred(); + this.uploadForm.target.value = String(targetUri); + this.uploadForm.file.click(); + return this.deferredUpload.promise; + } + async download(uris: URI[]): Promise { if (uris.length === 0) { return; @@ -155,8 +262,12 @@ export class FileDownloadService { } protected endpoint(): string { - const url = new Endpoint({ path: 'files' }).getRestUrl().toString(); + const url = this.filesUrl(); return url.endsWith('/') ? url.slice(0, -1) : url; } + protected filesUrl(): string { + return new Endpoint({ path: 'files' }).getRestUrl().toString(); + } + } diff --git a/packages/filesystem/src/browser/file-dialog/file-dialog.ts b/packages/filesystem/src/browser/file-dialog/file-dialog.ts index bf1e012e849ba..fb035641049f0 100644 --- a/packages/filesystem/src/browser/file-dialog/file-dialog.ts +++ b/packages/filesystem/src/browser/file-dialog/file-dialog.ts @@ -261,9 +261,12 @@ export class OpenFileDialog extends FileDialog> { } } - protected accept(): void { - if (this.props.canSelectFolders === false && !Array.isArray(this.value)) { - this.widget.model.openNode(this.value); + protected async accept(): Promise { + const selection = this.value; + if (!this.props.canSelectFolders + && !Array.isArray(selection) + && selection.fileStat.isDirectory) { + this.widget.model.openNode(selection); return; } super.accept(); diff --git a/packages/filesystem/src/browser/file-resource.ts b/packages/filesystem/src/browser/file-resource.ts index 10d4203a76f39..79c365142d161 100644 --- a/packages/filesystem/src/browser/file-resource.ts +++ b/packages/filesystem/src/browser/file-resource.ts @@ -116,7 +116,7 @@ export class FileResource implements Resource { } protected async getFileStat(): Promise { - if (!this.fileSystem.exists(this.uriString)) { + if (!await this.fileSystem.exists(this.uriString)) { return undefined; } try { diff --git a/packages/filesystem/src/browser/file-selection.ts b/packages/filesystem/src/browser/file-selection.ts new file mode 100644 index 0000000000000..d26fff13eedbd --- /dev/null +++ b/packages/filesystem/src/browser/file-selection.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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 { SelectionService } from '@theia/core/lib/common/selection-service'; +import { SelectionCommandHandler } from '@theia/core/lib/common/selection-command-handler'; +import { FileStat } from '../common/filesystem'; + +export interface FileSelection { + fileStat: FileStat +} +export namespace FileSelection { + export function is(arg: Object | undefined): arg is FileSelection { + return typeof arg === 'object' && ('fileStat' in arg) && FileStat.is(arg['fileStat']); + } + export class CommandHandler extends SelectionCommandHandler { + + constructor( + protected readonly selectionService: SelectionService, + protected readonly options: SelectionCommandHandler.Options + ) { + super( + selectionService, + arg => FileSelection.is(arg) ? arg : undefined, + options + ); + } + + } + +} diff --git a/packages/filesystem/src/browser/file-tree/file-tree.ts b/packages/filesystem/src/browser/file-tree/file-tree.ts index c796d8325641c..3466ea959d8eb 100644 --- a/packages/filesystem/src/browser/file-tree/file-tree.ts +++ b/packages/filesystem/src/browser/file-tree/file-tree.ts @@ -20,6 +20,7 @@ import { TreeNode, CompositeTreeNode, SelectableTreeNode, ExpandableTreeNode, Tr import { FileSystem, FileStat } from '../../common'; import { LabelProvider } from '@theia/core/lib/browser/label-provider'; import { UriSelection } from '@theia/core/lib/common/selection'; +import { FileSelection } from '../file-selection'; @injectable() export class FileTree extends TreeImpl { @@ -91,8 +92,7 @@ export class FileTree extends TreeImpl { } } -export interface FileStatNode extends SelectableTreeNode, UriSelection { - fileStat: FileStat; +export interface FileStatNode extends SelectableTreeNode, UriSelection, FileSelection { } export namespace FileStatNode { export function is(node: object | undefined): node is FileStatNode { diff --git a/packages/filesystem/src/browser/filesystem-frontend-module.ts b/packages/filesystem/src/browser/filesystem-frontend-module.ts index 5f3583842d87e..d8a7c749bb01e 100644 --- a/packages/filesystem/src/browser/filesystem-frontend-module.ts +++ b/packages/filesystem/src/browser/filesystem-frontend-module.ts @@ -14,6 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import '../../src/browser/style/index.css'; + import { ContainerModule } from 'inversify'; import { ResourceResolver } from '@theia/core/lib/common'; import { WebSocketConnectionProvider, FrontendApplicationContribution, ConfirmDialog } from '@theia/core/lib/browser'; @@ -26,8 +28,7 @@ import { FileResourceResolver } from './file-resource'; import { bindFileSystemPreferences } from './filesystem-preferences'; import { FileSystemWatcher } from './filesystem-watcher'; import { FileSystemFrontendContribution } from './filesystem-frontend-contribution'; - -import '../../src/browser/style/index.css'; +import { FileSystemProxyFactory } from './filesystem-proxy-factory'; export default new ContainerModule(bind => { bindFileSystemPreferences(bind); @@ -47,9 +48,11 @@ export default new ContainerModule(bind => { return !!await dialog.open(); }); - bind(FileSystem).toDynamicValue(ctx => - WebSocketConnectionProvider.createProxy(ctx.container, fileSystemPath) - ).inSingletonScope(); + bind(FileSystemProxyFactory).toSelf(); + bind(FileSystem).toDynamicValue(ctx => { + const proxyFactory = ctx.container.get(FileSystemProxyFactory); + return WebSocketConnectionProvider.createProxy(ctx.container, fileSystemPath, proxyFactory); + }).inSingletonScope(); bind(FileResourceResolver).toSelf().inSingletonScope(); bind(ResourceResolver).toService(FileResourceResolver); diff --git a/packages/filesystem/src/browser/filesystem-preferences.ts b/packages/filesystem/src/browser/filesystem-preferences.ts index 4adacc6dd02ef..aee86e22f7fd9 100644 --- a/packages/filesystem/src/browser/filesystem-preferences.ts +++ b/packages/filesystem/src/browser/filesystem-preferences.ts @@ -43,6 +43,11 @@ export const filesystemPreferenceSchema: PreferenceSchema = { 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, 'description': 'Configure glob patterns for excluding files and folders.', 'scope': 'resource' + }, + 'files.enableTrash': { + 'type': 'boolean', + 'default': true, + 'description': 'Moves files/folders to the OS trash (recycle bin on Windows) when deleting. Disabling this will delete files/folders permanently.' } } }; @@ -50,6 +55,7 @@ export const filesystemPreferenceSchema: PreferenceSchema = { export interface FileSystemConfiguration { 'files.watcherExclude': { [globPattern: string]: boolean }; 'files.exclude': { [key: string]: boolean }; + 'files.enableTrash': boolean; } export const FileSystemPreferences = Symbol('FileSystemPreferences'); diff --git a/packages/filesystem/src/browser/filesystem-proxy-factory.ts b/packages/filesystem/src/browser/filesystem-proxy-factory.ts new file mode 100644 index 0000000000000..f93a54eadabfb --- /dev/null +++ b/packages/filesystem/src/browser/filesystem-proxy-factory.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 + ********************************************************************************/ + +// tslint:disable:no-any + +import { injectable, inject } from 'inversify'; +import { JsonRpcProxyFactory } from '@theia/core/lib/common/messaging/proxy-factory'; +import { FileSystem, FileDeleteOptions } from '../common/filesystem'; +import { FileSystemPreferences } from './filesystem-preferences'; + +@injectable() +export class FileSystemProxyFactory extends JsonRpcProxyFactory { + + @inject(FileSystemPreferences) + protected readonly preferences: FileSystemPreferences; + + get(target: FileSystem, propertyKey: PropertyKey, receiver: any): any { + const property = super.get(target, propertyKey, receiver); + if (propertyKey !== 'delete') { + return property; + } + const deleteFn: FileSystem['delete'] = (uri, options) => { + const opt: FileDeleteOptions = { ...options }; + if (opt.moveToTrash === undefined) { + opt.moveToTrash = this.preferences['files.enableTrash']; + } + return property(uri, opt); + }; + return deleteFn; + } + +} diff --git a/packages/filesystem/src/browser/style/file-dialog.css b/packages/filesystem/src/browser/style/file-dialog.css index 1b6e3c3c2b80e..fa4aed9435bdf 100644 --- a/packages/filesystem/src/browser/style/file-dialog.css +++ b/packages/filesystem/src/browser/style/file-dialog.css @@ -25,6 +25,7 @@ .dialogContent .theia-FileDialog { height: 500px; + background-color: var(--theia-layout-color0); } .dialogContent .theia-SaveFileDialog { diff --git a/packages/filesystem/src/browser/style/file-icons.css b/packages/filesystem/src/browser/style/file-icons.css index 165ff782e10ea..0627fbc315480 100644 --- a/packages/filesystem/src/browser/style/file-icons.css +++ b/packages/filesystem/src/browser/style/file-icons.css @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -.file-icon { - max-height: calc(var(--theia-content-font-size) * 0.8); + .file-icon { /* Here, the `line-height` ensures that FontAwesome and `file-icons-js` container has the same height. Ideally, it would be 1 em, but since we set the max height above (and other places too) with 0.8 @@ -28,4 +27,14 @@ font-size: calc(var(--theia-content-font-size) * 0.8); text-align: center; margin-right: 4px; -} \ No newline at end of file +} + +.theia-app-sides .file-icon { + max-height: none; + line-height: inherit; +} + +.theia-app-sides .file-icon:before { + font-size: var(--theia-private-sidebar-icon-size); + margin-right: 0px; +} diff --git a/packages/filesystem/src/common/filesystem.ts b/packages/filesystem/src/common/filesystem.ts index 8971948c48785..cfddd74da11af 100644 --- a/packages/filesystem/src/common/filesystem.ts +++ b/packages/filesystem/src/common/filesystem.ts @@ -258,10 +258,8 @@ export interface FileStat { } export namespace FileStat { - export function is(candidate: object): candidate is FileStat { - return candidate.hasOwnProperty('uri') - && candidate.hasOwnProperty('lastModification') - && candidate.hasOwnProperty('isDirectory'); + export function is(candidate: Object | undefined): candidate is FileStat { + return typeof candidate === 'object' && ('uri' in candidate) && ('lastModification' in candidate) && ('isDirectory' in candidate); } export function equals(one: object | undefined, other: object | undefined): boolean { diff --git a/packages/filesystem/src/node/download/file-download-endpoint.ts b/packages/filesystem/src/node/download/file-download-endpoint.ts index 86212d79662f8..be81f6306df09 100644 --- a/packages/filesystem/src/node/download/file-download-endpoint.ts +++ b/packages/filesystem/src/node/download/file-download-endpoint.ts @@ -16,10 +16,20 @@ import { injectable, inject, named } from 'inversify'; import { json } from 'body-parser'; -import { Application, Router } from 'express'; +// tslint:disable-next-line:no-implicit-dependencies +import { Application, Router, Request, Response, NextFunction } from 'express'; +import * as formidable from 'formidable'; +import URI from '@theia/core/lib/common/uri'; +import { FileUri } from '@theia/core/lib/node/file-uri'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { FileDownloadHandler } from './file-download-handler'; +// upload max file size in MB, default 2048 +let uploadMaxFileSize = Number(process.env.THEIA_UPLOAD_MAX_FILE_SIZE); +if (typeof uploadMaxFileSize !== 'number' || Number.isNaN(uploadMaxFileSize) || !Number.isFinite(uploadMaxFileSize)) { + uploadMaxFileSize = 2048; +} + @injectable() export class FileDownloadEndpoint implements BackendApplicationContribution { @@ -34,12 +44,53 @@ export class FileDownloadEndpoint implements BackendApplicationContribution { protected readonly multiFileDownloadHandler: FileDownloadHandler; configure(app: Application): void { + const upload = this.upload.bind(this); const router = Router(); router.get('/', (request, response) => this.singleFileDownloadHandler.handle(request, response)); router.put('/', (request, response) => this.multiFileDownloadHandler.handle(request, response)); + router.post('/', upload); // Content-Type: application/json app.use(json()); app.use(FileDownloadEndpoint.PATH, router); } + protected upload(req: Request, res: Response, next: NextFunction): void { + const form = new formidable.IncomingForm(); + form.multiples = true; + form.maxFileSize = uploadMaxFileSize * 1024 * 1024; + + let targetUri: URI | undefined; + const clientErrors: string[] = []; + form.on('field', (name: string, value: string) => { + if (name === 'target') { + targetUri = new URI(value); + } + }); + form.on('fileBegin', (_: string, file: formidable.File) => { + if (targetUri) { + file.path = FileUri.fsPath(targetUri.resolve(file.name)); + } else { + clientErrors.push(`cannot upload "${file.name}", target is not provided`); + } + }); + form.on('error', (error: Error) => { + if (String(error).indexOf('maxFileSize') !== -1) { + res.writeHead(413, 'Payload Exceeded ' + uploadMaxFileSize + 'MB'); + } else { + console.error(error); + res.writeHead(500, String(error)); + } + res.end(); + }); + form.on('end', () => { + if (clientErrors.length) { + res.writeHead(400, clientErrors.join('\n')); + } else { + res.writeHead(200); + } + res.end(); + }); + form.parse(req); + } + } diff --git a/packages/filesystem/src/node/download/file-download-handler.ts b/packages/filesystem/src/node/download/file-download-handler.ts index e53cb5a8e51df..52888e49fe182 100644 --- a/packages/filesystem/src/node/download/file-download-handler.ts +++ b/packages/filesystem/src/node/download/file-download-handler.ts @@ -89,7 +89,7 @@ export abstract class FileDownloadHandler { protected async handleError(response: Response, reason: string | Error, status: number = INTERNAL_SERVER_ERROR): Promise { this.logger.error(reason); - response.status(status).send(reason).end(); + response.status(status).send('Unable to download file.').end(); } } diff --git a/packages/filesystem/src/node/node-filesystem.spec.ts b/packages/filesystem/src/node/node-filesystem.spec.ts index 66ac753caeecb..3edfacbe17fb7 100644 --- a/packages/filesystem/src/node/node-filesystem.spec.ts +++ b/packages/filesystem/src/node/node-filesystem.spec.ts @@ -446,6 +446,14 @@ describe('NodeFileSystem', function () { await expectThrowsAsync(fileSystem.copy(sourceUri.toString(), targetUri.toString()), Error); }); + it('Copy a file to existing location with the same file name. Should be rejected with an error.', async () => { + const sourceUri = root.resolve('foo'); + fs.mkdirSync(FileUri.fsPath(sourceUri)); + expect(fs.statSync(FileUri.fsPath(sourceUri)).isDirectory()).to.be.true; + + await expectThrowsAsync(fileSystem.copy(sourceUri.toString(), sourceUri.toString()), Error); + }); + it('Copy an empty directory to a non-existing location. Should return with the file stat representing the new file at the target location.', async () => { const sourceUri = root.resolve('foo'); const targetUri = root.resolve('bar'); diff --git a/packages/filesystem/src/node/node-filesystem.ts b/packages/filesystem/src/node/node-filesystem.ts index 229330e91cfc9..64a580367b208 100644 --- a/packages/filesystem/src/node/node-filesystem.ts +++ b/packages/filesystem/src/node/node-filesystem.ts @@ -231,6 +231,9 @@ export class FileSystemNode implements FileSystem { if (targetStat && !overwrite) { throw FileSystemError.FileExists(targetUri, "Did you set the 'overwrite' flag to true?"); } + if (targetStat && targetStat.uri === sourceStat.uri) { + throw FileSystemError.FileExists(targetUri, 'Cannot perform copy, source and destination are the same.'); + } await fs.copy(FileUri.fsPath(_sourceUri), FileUri.fsPath(_targetUri), { overwrite, recursive }); const newStat = await this.doGetStat(_targetUri, 1); if (newStat) { diff --git a/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-watcher.ts b/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-watcher.ts index 51eb1056d9f08..7138e7275f7e1 100644 --- a/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-watcher.ts +++ b/packages/filesystem/src/node/nsfw-watcher/nsfw-filesystem-watcher.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import * as fs from 'fs'; -import * as nsfw from 'vscode-nsfw'; +import * as nsfw from 'nsfw'; import * as paths from 'path'; import { IMinimatch, Minimatch } from 'minimatch'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; @@ -111,17 +111,17 @@ export class NsfwFileSystemWatcherServer implements FileSystemWatcherServer { let watcher: nsfw.NSFW | undefined = await nsfw(fs.realpathSync(basePath), (events: nsfw.ChangeEvent[]) => { for (const event of events) { if (event.action === nsfw.actions.CREATED) { - this.pushAdded(watcherId, paths.join(event.directory, event.file!)); + this.pushAdded(watcherId, this.resolvePath(event.directory, event.file!)); } if (event.action === nsfw.actions.DELETED) { - this.pushDeleted(watcherId, paths.join(event.directory, event.file!)); + this.pushDeleted(watcherId, this.resolvePath(event.directory, event.file!)); } if (event.action === nsfw.actions.MODIFIED) { - this.pushUpdated(watcherId, paths.join(event.directory, event.file!)); + this.pushUpdated(watcherId, this.resolvePath(event.directory, event.file!)); } if (event.action === nsfw.actions.RENAMED) { - this.pushDeleted(watcherId, paths.join(event.directory, event.oldFile!)); - this.pushAdded(watcherId, paths.join(event.directory, event.newFile!)); + this.pushDeleted(watcherId, this.resolvePath(event.directory, event.oldFile!)); + this.pushAdded(watcherId, this.resolvePath(event.directory, event.newFile!)); } } }); @@ -192,6 +192,21 @@ export class NsfwFileSystemWatcherServer implements FileSystemWatcherServer { this.fireDidFilesChanged(); } + protected resolvePath(directory: string, file: string): string { + const path = paths.join(directory, file); + try { + return fs.realpathSync(path); + } catch { + try { + // file does not exist try to resolve directory + return paths.join(fs.realpathSync(directory), file); + } catch { + // directory does not exist fall back to symlink + return path; + } + } + } + /** * Fires file changes to clients. * It is debounced in the case if the filesystem is spamming to avoid overwhelming clients with events. diff --git a/packages/getting-started/package.json b/packages/getting-started/package.json index 783ea464afc66..f6ddfb3de97d9 100644 --- a/packages/getting-started/package.json +++ b/packages/getting-started/package.json @@ -1,12 +1,12 @@ { "name": "@theia/getting-started", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - GettingStarted Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/keymaps": "^0.4.0", - "@theia/workspace": "^0.4.0" + "@theia/core": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/keymaps": "^0.5.0", + "@theia/workspace": "^0.5.0" }, "publishConfig": { "access": "public" @@ -38,11 +38,10 @@ "build": "theiaext build", "watch": "theiaext watch", "test": "theiaext test", - "test:watch": "theiaext test:watch", - "docs": "theiaext docs" + "test:watch": "theiaext test:watch" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/git/package.json b/packages/git/package.json index e4cd60fc49dc1..d4fbd7c6d19d6 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -1,14 +1,14 @@ { "name": "@theia/git", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Git Integration", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/navigator": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/navigator": "^0.5.0", + "@theia/workspace": "^0.5.0", "@types/diff": "^3.2.2", "@types/fs-extra": "^4.0.2", "@types/p-queue": "^2.3.1", @@ -61,11 +61,10 @@ "build": "theiaext build", "watch": "theiaext watch", "test": "theiaext test", - "test:watch": "theiaext test:watch", - "docs": "theiaext docs" + "test:watch": "theiaext test:watch" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0", + "@theia/ext-scripts": "^0.5.0", "upath": "^1.0.2" }, "nyc": { diff --git a/packages/git/src/browser/diff/git-diff-contribution.ts b/packages/git/src/browser/diff/git-diff-contribution.ts index f069b8df27d6b..ba7187acb68c2 100644 --- a/packages/git/src/browser/diff/git-diff-contribution.ts +++ b/packages/git/src/browser/diff/git-diff-contribution.ts @@ -56,7 +56,7 @@ export class GitDiffContribution extends AbstractViewContribution widgetName: 'Git diff', defaultWidgetOptions: { area: 'left', - rank: 400 + rank: 500 } }); } diff --git a/packages/git/src/browser/diff/git-diff-widget.tsx b/packages/git/src/browser/diff/git-diff-widget.tsx index 505786062773c..e084ff1cf3f98 100644 --- a/packages/git/src/browser/diff/git-diff-widget.tsx +++ b/packages/git/src/browser/diff/git-diff-widget.tsx @@ -32,6 +32,8 @@ export const GIT_DIFF = 'git-diff'; @injectable() export class GitDiffWidget extends GitNavigableListWidget implements StatefulWidget { + protected readonly GIT_DIFF_TITLE = 'Diff'; + protected fileChangeNodes: GitFileChangeNode[] = []; protected options: Git.Options.Diff; @@ -48,7 +50,10 @@ export class GitDiffWidget extends GitNavigableListWidget imp super(); this.id = GIT_DIFF; this.scrollContainer = 'git-diff-list-container'; - this.title.label = 'Diff'; + this.title.label = this.GIT_DIFF_TITLE; + this.title.caption = this.GIT_DIFF_TITLE; + this.title.closable = true; + this.title.iconClass = 'theia-git-diff-icon'; this.addClass('theia-git'); } @@ -401,7 +406,7 @@ export class GitDiffListContainer extends React.Component this.listContainer = ref || undefined} className='listContainer' id={id} tabIndex={0}>{...files}
; + return
this.listContainer = ref || undefined} className='listContainer filesChanged' id={id} tabIndex={0}>{...files}
; } componentDidMount() { diff --git a/packages/git/src/browser/git-view-contribution.ts b/packages/git/src/browser/git-view-contribution.ts index 5939b6e0d575e..5788509685c6c 100644 --- a/packages/git/src/browser/git-view-contribution.ts +++ b/packages/git/src/browser/git-view-contribution.ts @@ -80,12 +80,14 @@ export namespace GIT_COMMANDS { export const OPEN_FILE: Command = { id: 'git.open.file', category: 'Git', - label: 'Open File' + label: 'Open File', + iconClass: 'theia-open-file-icon' }; export const OPEN_CHANGES: Command = { id: 'git.open.changes', category: 'Git', - label: 'Open Changes' + label: 'Open Changes', + iconClass: 'theia-open-change-icon' }; export const SYNC = { id: 'git.sync', @@ -160,7 +162,7 @@ export class GitViewContribution extends AbstractViewContribution widgetName: 'Git', defaultWidgetOptions: { area: 'left', - rank: 200 + rank: 300 }, toggleCommandId: 'gitView:toggle', toggleKeybinding: 'ctrlcmd+shift+g' @@ -411,13 +413,11 @@ export class GitViewContribution extends AbstractViewContribution registry.registerItem({ id: GIT_COMMANDS.OPEN_FILE.id, command: GIT_COMMANDS.OPEN_FILE.id, - text: '$(file-o)', tooltip: GIT_COMMANDS.OPEN_FILE.label }); registry.registerItem({ id: GIT_COMMANDS.OPEN_CHANGES.id, command: GIT_COMMANDS.OPEN_CHANGES.id, - text: '$(files-o)', tooltip: GIT_COMMANDS.OPEN_CHANGES.label }); } diff --git a/packages/git/src/browser/git-widget.tsx b/packages/git/src/browser/git-widget.tsx index 2d38acca26380..f0c13a2d0d168 100644 --- a/packages/git/src/browser/git-widget.tsx +++ b/packages/git/src/browser/git-widget.tsx @@ -80,7 +80,8 @@ export class GitWidget extends GitDiffWidget implements StatefulWidget { this.id = 'theia-gitContainer'; this.title.label = 'Git'; this.title.caption = 'Git'; - this.title.iconClass = 'fa git-tab-icon'; + this.title.iconClass = 'git-tab-icon'; + this.title.closable = true; this.scrollContainer = GitWidget.Styles.CHANGES_CONTAINER; this.addClass('theia-git'); this.node.tabIndex = 0; diff --git a/packages/git/src/browser/history/git-history-contribution.ts b/packages/git/src/browser/history/git-history-contribution.ts index b503c065c299e..0cbbba6b9c697 100644 --- a/packages/git/src/browser/history/git-history-contribution.ts +++ b/packages/git/src/browser/history/git-history-contribution.ts @@ -61,7 +61,7 @@ export class GitHistoryContribution extends AbstractViewContribution this.title.label = GIT_HISTORY_LABEL; this.title.caption = GIT_HISTORY_LABEL; this.title.iconClass = 'fa git-history-tab-icon'; + this.title.closable = true; this.addClass('theia-git'); this.resetState(); this.cancelIndicator = new CancellationTokenSource(); @@ -186,7 +187,7 @@ export class GitHistoryWidget extends GitNavigableListWidget if (commit.expanded) { this.removeFileChangeNodes(commit, id); } else { - this.addFileChangeNodes(commit, id); + await this.addFileChangeNodes(commit, id); } commit.expanded = !commit.expanded; this.update(); @@ -340,7 +341,6 @@ export class GitHistoryWidget extends GitNavigableListWidget e => { if (commit.selected && !this.singleFileMode) { this.addOrRemoveFileChangeNodes(commit); - this.update(); } else { this.selectNode(commit); } diff --git a/packages/git/src/browser/style/diff.css b/packages/git/src/browser/style/diff.css index 59bd6078840fd..ce8e54a95d9de 100644 --- a/packages/git/src/browser/style/diff.css +++ b/packages/git/src/browser/style/diff.css @@ -18,6 +18,11 @@ color: var(--theia-ui-font-color1); } +.theia-git-diff-icon { + -webkit-mask: url('git-diff.svg'); + mask: url('git-diff.svg'); +} + .theia-git .git-diff-container { display: flex; flex-direction: column; @@ -36,7 +41,7 @@ .theia-git .header-row { display: flex; - flex-direction: row; + flex-direction: row; } .theia-git .header-value { @@ -64,3 +69,8 @@ margin-left: 10px; cursor: pointer; } + +.theia-git .listContainer.filesChanged { + flex: 1; + overflow: auto; +} diff --git a/packages/git/src/browser/style/git-diff.svg b/packages/git/src/browser/style/git-diff.svg new file mode 100644 index 0000000000000..262a9b3f00af5 --- /dev/null +++ b/packages/git/src/browser/style/git-diff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/git/src/browser/style/git-icons.css b/packages/git/src/browser/style/git-icons.css index cf2c0224acdbe..c3d6274a1a24c 100644 --- a/packages/git/src/browser/style/git-icons.css +++ b/packages/git/src/browser/style/git-icons.css @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - + .icon-git-commit { mask-repeat: no-repeat; mask-position: center; diff --git a/packages/git/src/browser/style/git.svg b/packages/git/src/browser/style/git.svg new file mode 100644 index 0000000000000..9280ae1dbf89d --- /dev/null +++ b/packages/git/src/browser/style/git.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/git/src/browser/style/history.css b/packages/git/src/browser/style/history.css index f593e6ee8dead..43ace1ef80266 100644 --- a/packages/git/src/browser/style/history.css +++ b/packages/git/src/browser/style/history.css @@ -18,11 +18,7 @@ margin: 3px 0; } -.theia-git .commitListElement { - border-top: 1px solid var(--theia-layout-color4); -} - -.theia-git .commitListElement.first { +.theia-git .commitListElement.first .containerHead{ border: none; } @@ -31,6 +27,7 @@ height: 50px; display: flex; align-items: center; + border-top: 1px solid var(--theia-layout-color4); } .theia-git .commitListElement .containerHead:hover { diff --git a/packages/git/src/browser/style/index.css b/packages/git/src/browser/style/index.css index 18dfd485bd2f6..287c7a280335b 100644 --- a/packages/git/src/browser/style/index.css +++ b/packages/git/src/browser/style/index.css @@ -18,7 +18,10 @@ color: var(--theia-ui-font-color1); padding: 5px; box-sizing: border-box; - background: var(--theia-layout-color0); +} + +.theia-side-panel .theia-git { + padding-left: 19px; } .theia-git:focus, .theia-git :focus { @@ -326,8 +329,9 @@ outline: none; } -.git-tab-icon::before { - content: "\f126" +.git-tab-icon { + -webkit-mask: url('git.svg'); + mask: url('git.svg'); } .git-change-count { diff --git a/packages/git/src/electron-node/askpass/askpass.ts b/packages/git/src/electron-node/askpass/askpass.ts index 7b7715ed896fc..c61d9b6a0e76a 100644 --- a/packages/git/src/electron-node/askpass/askpass.ts +++ b/packages/git/src/electron-node/askpass/askpass.ts @@ -14,6 +14,7 @@ import { MaybePromise } from '@theia/core/lib/common/types'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { GitPrompt } from '../../common/git-prompt'; import { DugiteGitPromptServer } from '../../node/dugite-git-prompt'; +import { AddressInfo } from 'net'; /** * Environment for the Git askpass helper. @@ -89,7 +90,7 @@ export class Askpass implements Disposable { return new Promise
(resolve => { this.server.on('error', err => this.logger.error(err)); this.server.listen(0, this.hostname(), () => { - resolve(this.server.address()); + resolve(this.server.address() as AddressInfo); }); }); } catch (err) { diff --git a/packages/java-debug/package.json b/packages/java-debug/package.json index ae37fe750f27a..4d06fee68407b 100644 --- a/packages/java-debug/package.json +++ b/packages/java-debug/package.json @@ -1,10 +1,10 @@ { "name": "@theia/java-debug", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Java Debug Extension", "dependencies": { - "@theia/debug": "^0.4.0", - "@theia/java": "^0.4.0", + "@theia/debug": "^0.5.0", + "@theia/java": "^0.5.0", "lodash": "^4.17.10" }, "publishConfig": { @@ -41,11 +41,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/java/package.json b/packages/java/package.json index 47c12277faee6..3f4c9d5f01be7 100644 --- a/packages/java/package.json +++ b/packages/java/package.json @@ -1,12 +1,12 @@ { "name": "@theia/java", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Java Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/monaco": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/monaco": "^0.5.0", "@types/glob": "^5.0.30", "@types/tar": "4.0.0", "glob": "^7.1.2", @@ -15,7 +15,7 @@ "tar": "^4.0.0" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "scripts": { "prepare": "yarn run clean && yarn run build", @@ -24,7 +24,6 @@ "build": "theiaext build", "watch": "theiaext watch", "test": "theiaext test", - "docs": "theiaext docs", "dev-server": "node ./scripts/get-dev-server.js" }, "publishConfig": { diff --git a/packages/java/src/browser/java-protocol.ts b/packages/java/src/browser/java-protocol.ts index 8e1ec36436cd7..9e332b10d8333 100644 --- a/packages/java/src/browser/java-protocol.ts +++ b/packages/java/src/browser/java-protocol.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { RequestType, NotificationType } from 'vscode-jsonrpc'; -import { VersionedTextDocumentIdentifier, TextDocumentIdentifier, Command, MessageType, ExecuteCommandParams } from '@theia/languages/lib/browser'; +import { TextDocumentIdentifier, Command, MessageType, ExecuteCommandParams } from '@theia/languages/lib/browser'; export interface StatusReport { message: string; @@ -34,16 +34,6 @@ export interface ActionableMessage { commands?: Command[]; } -export interface SemanticHighlightingParams { - readonly textDocument: VersionedTextDocumentIdentifier; - readonly lines: SemanticHighlightingInformation[]; -} - -export interface SemanticHighlightingInformation { - readonly line: number; - readonly tokens: string | undefined; -} - export namespace ClassFileContentsRequest { export const type = new RequestType('java/classFileContents'); } @@ -52,10 +42,6 @@ export namespace ActionableNotification { export const type = new NotificationType('language/actionableNotification'); } -export namespace SemanticHighlight { - export const type = new NotificationType('textDocument/semanticHighlighting'); -} - export enum CompileWorkspaceStatus { FAILED = 0, SUCCEED = 1, diff --git a/packages/java/src/node/java-contribution.ts b/packages/java/src/node/java-contribution.ts index 6b28e14c2563f..f78ea079389dc 100644 --- a/packages/java/src/node/java-contribution.ts +++ b/packages/java/src/node/java-contribution.ts @@ -28,6 +28,7 @@ import { JAVA_LANGUAGE_ID, JAVA_LANGUAGE_NAME, JavaStartParams } from '../common import { JavaCliContribution } from './java-cli-contribution'; import { ContributionProvider } from '@theia/core'; import { JavaExtensionContribution } from './java-extension-model'; +import { AddressInfo } from 'net'; const sha1 = require('sha1'); export type ConfigurationType = 'config_win' | 'config_mac' | 'config_linux'; @@ -137,8 +138,8 @@ export class JavaContribution extends BaseLanguageServerContribution { this.logInfo('logs at ' + path.resolve(workspacePath, '.metadata', '.log')); const env = Object.create(process.env); const address = server.address(); - env.CLIENT_HOST = address.address; - env.CLIENT_PORT = address.port; + env.CLIENT_HOST = (address as AddressInfo).address; + env.CLIENT_PORT = (address as AddressInfo).port; const serverConnection = await this.createProcessSocketConnection(socket, socket, command, args, { env }); this.forward(clientConnection, serverConnection); } diff --git a/packages/json/package.json b/packages/json/package.json index 4a73440b3853a..d137e2dd4a310 100644 --- a/packages/json/package.json +++ b/packages/json/package.json @@ -1,23 +1,22 @@ { "name": "@theia/json", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - JSON Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/monaco": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/monaco": "^0.5.0", "vscode-json-languageserver": "^1.0.1" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "scripts": { "prepare": "yarn run clean && yarn run build", "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "publishConfig": { "access": "public" diff --git a/packages/json/src/browser/json-client-contribution.ts b/packages/json/src/browser/json-client-contribution.ts index 7e580d9ad4d15..782afef264aa3 100644 --- a/packages/json/src/browser/json-client-contribution.ts +++ b/packages/json/src/browser/json-client-contribution.ts @@ -67,7 +67,7 @@ export class JsonClientContribution extends BaseLanguageClientContribution { for (const s of allConfigs) { if (s.fileMatch) { for (let fileMatch of s.fileMatch) { - if (fileMatch.charAt(0) !== '/' && !fileMatch.match(/\w+:\/\//)) { + if (fileMatch.charAt(0) !== '/' && !fileMatch.match(/\w+:/)) { fileMatch = '/' + fileMatch; } registry[fileMatch] = [s.url]; diff --git a/packages/keymaps/package.json b/packages/keymaps/package.json index f5b7221369cc3..ed0e7938276be 100644 --- a/packages/keymaps/package.json +++ b/packages/keymaps/package.json @@ -1,12 +1,12 @@ { "name": "@theia/keymaps", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Custom Keymaps Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/userstorage": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/userstorage": "^0.5.0", + "@theia/workspace": "^0.5.0", "@types/lodash.debounce": "4.0.3", "ajv": "^6.5.3", "fuzzy": "^0.1.3", @@ -14,7 +14,7 @@ "lodash.debounce": "^4.0.8" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0", + "@theia/ext-scripts": "^0.5.0", "@types/temp": "^0.8.29", "temp": "^0.8.3" }, @@ -47,8 +47,7 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/keymaps/src/browser/keybindings-widget.tsx b/packages/keymaps/src/browser/keybindings-widget.tsx index 52f94d3a33c12..920a0e4820e5a 100644 --- a/packages/keymaps/src/browser/keybindings-widget.tsx +++ b/packages/keymaps/src/browser/keybindings-widget.tsx @@ -61,7 +61,7 @@ export class KeybindingWidget extends ReactWidget { protected query: string = ''; protected readonly regexp = /(.*?)<\/match>/g; - protected readonly keybindingSeperator = /\+<\/match>/g; + protected readonly keybindingSeparator = /\+<\/match>/g; protected readonly fuzzyOptions = { pre: '', @@ -131,7 +131,7 @@ export class KeybindingWidget extends ReactWidget { } protected render(): React.ReactNode { - return
+ return
{this.renderSearch()} {(this.items.length > 0) ? this.renderTable() : this.renderMessage()}
; @@ -158,7 +158,7 @@ export class KeybindingWidget extends ReactWidget { } protected renderTable(): React.ReactNode { - return
+ return
@@ -214,7 +214,7 @@ export class KeybindingWidget extends ReactWidget { } protected renderKeybinding(keybinding: string): React.ReactNode { - const regex = new RegExp(this.keybindingSeperator); + const regex = new RegExp(this.keybindingSeparator); keybinding = keybinding.replace(regex, '+'); const keys = keybinding.split('+'); @@ -227,7 +227,7 @@ export class KeybindingWidget extends ReactWidget { ; } else { return - + + + {this.renderKeybinding(key)} ; } diff --git a/packages/keymaps/src/browser/style/index.css b/packages/keymaps/src/browser/style/index.css index 5add1fd81ad76..49bb35764b3e8 100644 --- a/packages/keymaps/src/browser/style/index.css +++ b/packages/keymaps/src/browser/style/index.css @@ -14,6 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +#kb-main-container { + display: flex; + flex-direction: column; + height: 100%; +} + +#kb-table-container { + flex: 1; + overflow: auto; +} + .fuzzy-match { font-weight: 600; color: var(--theia-brand-color1); @@ -104,6 +115,9 @@ padding-top: 5px; text-align: left; vertical-align: middle; + position: sticky; + top: 0; + background-color: var(--theia-layout-color2); } .kb table .th-action { diff --git a/packages/languages/package.json b/packages/languages/package.json index b076296c7d31c..4b4617824a35b 100644 --- a/packages/languages/package.json +++ b/packages/languages/package.json @@ -1,12 +1,12 @@ { "name": "@theia/languages", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Languages Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/output": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/output": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/workspace": "^0.5.0", "@typefox/monaco-editor-core": "^0.14.6", "@types/uuid": "^3.4.3", "monaco-languageclient": "^0.9.0", @@ -42,11 +42,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/markers/package.json b/packages/markers/package.json index 48c2b702aedbf..67208021e2e8b 100644 --- a/packages/markers/package.json +++ b/packages/markers/package.json @@ -1,12 +1,12 @@ { "name": "@theia/markers", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Markers Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/navigator": "^0.4.0", - "@theia/workspace": "^0.4.0" + "@theia/core": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/navigator": "^0.5.0", + "@theia/workspace": "^0.5.0" }, "publishConfig": { "access": "public" @@ -37,11 +37,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/markers/src/browser/style/index.css b/packages/markers/src/browser/style/index.css index 3a2a5d243f9ac..0f4755d127df4 100644 --- a/packages/markers/src/browser/style/index.css +++ b/packages/markers/src/browser/style/index.css @@ -23,6 +23,10 @@ padding: 5px; } +.theia-side-panel .theia-marker-container .noMarkers { + padding-left: 19px; +} + .theia-marker-container .markerNode, .theia-marker-container .markerFileNode { display: flex; diff --git a/packages/merge-conflicts/package.json b/packages/merge-conflicts/package.json index 1f20dde7ce58c..8c7e590025fe8 100644 --- a/packages/merge-conflicts/package.json +++ b/packages/merge-conflicts/package.json @@ -1,11 +1,11 @@ { "name": "@theia/merge-conflicts", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Merge Conflicts Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/languages": "^0.4.0" + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/languages": "^0.5.0" }, "publishConfig": { "access": "public" @@ -36,11 +36,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/messages/package.json b/packages/messages/package.json index 745b4ebe4bac8..2c21aff47ad75 100644 --- a/packages/messages/package.json +++ b/packages/messages/package.json @@ -1,9 +1,9 @@ { "name": "@theia/messages", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Messages Extension", "dependencies": { - "@theia/core": "^0.4.0" + "@theia/core": "^0.5.0" }, "publishConfig": { "access": "public" @@ -34,11 +34,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 0b0f5c347cfb8..6152090ce3085 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -1,10 +1,10 @@ { "name": "@theia/metrics", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Metrics Extension", "dependencies": { - "@theia/application-package": "^0.4.0", - "@theia/core": "^0.4.0", + "@theia/application-package": "^0.5.0", + "@theia/core": "^0.5.0", "prom-client": "^10.2.0" }, "publishConfig": { @@ -36,11 +36,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/mini-browser/package.json b/packages/mini-browser/package.json index 005f35e6d4805..8e7e6f86d73e9 100644 --- a/packages/mini-browser/package.json +++ b/packages/mini-browser/package.json @@ -1,10 +1,10 @@ { "name": "@theia/mini-browser", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Mini-Browser Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/filesystem": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/filesystem": "^0.5.0", "@types/fs-extra": "^4.0.2", "@types/mime-types": "^2.1.0", "fs-extra": "^4.0.2", @@ -41,11 +41,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/mini-browser/src/browser/mini-browser-frontend-module.ts b/packages/mini-browser/src/browser/mini-browser-frontend-module.ts index 06ad9e86d7cc2..5defe33ef6d1d 100644 --- a/packages/mini-browser/src/browser/mini-browser-frontend-module.ts +++ b/packages/mini-browser/src/browser/mini-browser-frontend-module.ts @@ -14,6 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import '../../src/browser/style/index.css'; + import { ContainerModule } from 'inversify'; import URI from '@theia/core/lib/common/uri'; import { OpenHandler } from '@theia/core/lib/browser/opener-service'; @@ -38,8 +40,6 @@ import { LocationWithoutSchemeMapper, } from './location-mapper-service'; -import '../../src/browser/style/index.css'; - export default new ContainerModule(bind => { bind(MiniBrowserMouseClickTracker).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(MiniBrowserMouseClickTracker); diff --git a/packages/mini-browser/src/browser/mini-browser-open-handler.ts b/packages/mini-browser/src/browser/mini-browser-open-handler.ts index 5b8d01233f63f..9eea089d06bc2 100644 --- a/packages/mini-browser/src/browser/mini-browser-open-handler.ts +++ b/packages/mini-browser/src/browser/mini-browser-open-handler.ts @@ -35,10 +35,12 @@ import { LocationMapperService } from './location-mapper-service'; export namespace MiniBrowserCommands { export const PREVIEW: Command = { id: 'mini-browser.preview', - label: 'Open Preview' + label: 'Open Preview', + iconClass: 'theia-open-preview-icon' }; export const OPEN_SOURCE: Command = { - id: 'mini-browser.open.source' + id: 'mini-browser.open.source', + iconClass: 'theia-open-file-icon' }; export const OPEN_URL: Command = { id: 'mini-browser.openUrl', @@ -191,13 +193,11 @@ export class MiniBrowserOpenHandler extends NavigatableWidgetOpenHandler + + + + + icon + Created with Sketch. + + + + + + + + + + diff --git a/packages/monaco/package.json b/packages/monaco/package.json index ede5e5733e2a0..668406f126656 100644 --- a/packages/monaco/package.json +++ b/packages/monaco/package.json @@ -1,15 +1,15 @@ { "name": "@theia/monaco", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Monaco Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/markers": "^0.4.0", - "@theia/outline-view": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/markers": "^0.5.0", + "@theia/outline-view": "^0.5.0", + "@theia/workspace": "^0.5.0", "deepmerge": "2.0.1", "jsonc-parser": "^2.0.2", "monaco-css": "^2.0.1", @@ -48,11 +48,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/monaco/src/browser/monaco-editor-model.ts b/packages/monaco/src/browser/monaco-editor-model.ts index d4bdfbd7247a9..e1fb8c789a058 100644 --- a/packages/monaco/src/browser/monaco-editor-model.ts +++ b/packages/monaco/src/browser/monaco-editor-model.ts @@ -19,6 +19,7 @@ import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from 'monaco-lan import { TextEditorDocument } from '@theia/editor/lib/browser'; import { DisposableCollection, Disposable, Emitter, Event, Resource, CancellationTokenSource, CancellationToken, ResourceError } from '@theia/core'; import ITextEditorModel = monaco.editor.ITextEditorModel; +import { Range } from 'vscode-languageserver-types'; export { TextDocumentSaveReason @@ -124,8 +125,15 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { return this.model.getVersionId(); } - getText(): string { - return this.model.getValue(); + /** + * Return selected text by Range or all text by default + */ + getText(range?: Range): string { + if (!range) { + return this.model.getValue(); + } else { + return this.model.getValueInRange(this.p2m.asRange(range)); + } } positionAt(offset: number): Position { @@ -141,6 +149,9 @@ export class MonacoEditorModel implements ITextEditorModel, TextEditorDocument { return this.model.getLineCount(); } + /** + * Retrieves a line in a text document expressed as a one-based position. + */ getLineContent(lineNumber: number): string { return this.model.getLineContent(lineNumber); } diff --git a/packages/monaco/src/browser/monaco-editor-service.ts b/packages/monaco/src/browser/monaco-editor-service.ts index 86b0f2eee40d1..000f0c10b9f22 100644 --- a/packages/monaco/src/browser/monaco-editor-service.ts +++ b/packages/monaco/src/browser/monaco-editor-service.ts @@ -17,7 +17,7 @@ import { injectable, inject, decorate } from 'inversify'; import { MonacoToProtocolConverter } from 'monaco-languageclient'; import URI from '@theia/core/lib/common/uri'; -import { OpenerService, open, WidgetOpenMode, ApplicationShell } from '@theia/core/lib/browser'; +import { OpenerService, open, WidgetOpenMode, ApplicationShell, PreferenceService } from '@theia/core/lib/browser'; import { EditorWidget, EditorOpenerOptions, EditorManager } from '@theia/editor/lib/browser'; import { MonacoEditor } from './monaco-editor'; @@ -30,6 +30,8 @@ decorate(injectable(), monaco.services.CodeEditorServiceImpl); @injectable() export class MonacoEditorService extends monaco.services.CodeEditorServiceImpl { + public static readonly ENABLE_PREVIEW_PREFERENCE: string = 'editor.enablePreview'; + @inject(OpenerService) protected readonly openerService: OpenerService; @@ -42,6 +44,9 @@ export class MonacoEditorService extends monaco.services.CodeEditorServiceImpl { @inject(EditorManager) protected readonly editors: EditorManager; + @inject(PreferenceService) + protected readonly preferencesService: PreferenceService; + constructor() { super(monaco.services.StaticServices.standaloneThemeService.get()); } @@ -66,7 +71,8 @@ export class MonacoEditorService extends monaco.services.CodeEditorServiceImpl { const mode = this.getEditorOpenMode(input); const selection = input.options && this.m2p.asRange(input.options.selection); const widgetOptions = this.getWidgetOptions(source, sideBySide); - return { mode, selection, widgetOptions }; + const preview = !!this.preferencesService.get(MonacoEditorService.ENABLE_PREVIEW_PREFERENCE, false); + return { mode, selection, widgetOptions, preview }; } protected getEditorOpenMode(input: IResourceInput): WidgetOpenMode { const options = { diff --git a/packages/monaco/src/browser/monaco-quick-open-service.ts b/packages/monaco/src/browser/monaco-quick-open-service.ts index 6fc9d0e0ad530..934f4c79a84e9 100644 --- a/packages/monaco/src/browser/monaco-quick-open-service.ts +++ b/packages/monaco/src/browser/monaco-quick-open-service.ts @@ -17,8 +17,8 @@ import { injectable, inject, postConstruct } from 'inversify'; import { MessageType } from '@theia/core/lib/common/message-service-protocol'; import { - QuickOpenService, QuickOpenModel, QuickOpenOptions, - QuickOpenItem, QuickOpenGroupItem, QuickOpenMode, KeySequence + QuickOpenService, QuickOpenModel, QuickOpenOptions, QuickOpenItem, + QuickOpenGroupItem, QuickOpenMode, KeySequence, QuickOpenActionProvider, QuickOpenAction } from '@theia/core/lib/browser'; import { KEY_CODE_MAP } from './monaco-keycode-map'; import { ContextKey } from '@theia/core/lib/browser/context-key-service'; @@ -215,7 +215,7 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl this.options.onClose(cancelled); } - private toOpenModel(lookFor: string, items: QuickOpenItem[]): monaco.quickOpen.QuickOpenModel { + private toOpenModel(lookFor: string, items: QuickOpenItem[], actionProvider?: QuickOpenActionProvider): monaco.quickOpen.QuickOpenModel { const entries: monaco.quickOpen.QuickOpenEntry[] = []; for (const item of items) { const entry = this.createEntry(item, lookFor); @@ -226,7 +226,7 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl if (this.options.fuzzySort) { entries.sort((a, b) => monaco.quickOpen.compareEntries(a, b, lookFor)); } - return new monaco.quickOpen.QuickOpenModel(entries); + return new monaco.quickOpen.QuickOpenModel(entries, actionProvider ? new MonacoQuickOpenActionProvider(actionProvider) : undefined); } getModel(lookFor: string): monaco.quickOpen.QuickOpenModel { @@ -234,8 +234,8 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl } onType(lookFor: string, acceptor: (model: monaco.quickOpen.QuickOpenModel) => void): void { - this.model.onType(lookFor, items => { - const result = this.toOpenModel(lookFor, items); + this.model.onType(lookFor, (items, actionProvider) => { + const result = this.toOpenModel(lookFor, items, actionProvider); acceptor(result); }); } @@ -408,3 +408,72 @@ export class QuickOpenEntryGroup extends monaco.quickOpen.QuickOpenEntryGroup { } } + +export class MonacoQuickOpenAction implements monaco.quickOpen.IAction { + constructor(public readonly action: QuickOpenAction) { } + + get id(): string { + return this.action.id; + } + + get label(): string { + return this.action.label || ''; + } + + get tooltip(): string { + return this.action.tooltip || ''; + } + + get class(): string | undefined { + return this.action.class; + } + + get enabled(): boolean { + return this.action.enabled || true; + } + + get checked(): boolean { + return this.action.checked || false; + } + + get radio(): boolean { + return this.action.radio || false; + } + + // tslint:disable-next-line:no-any + run(entry: QuickOpenEntry | QuickOpenEntryGroup): PromiseLike { + return this.action.run(entry.item); + } + + dispose(): void { + this.action.dispose(); + } +} + +export class MonacoQuickOpenActionProvider implements monaco.quickOpen.IActionProvider { + constructor(public readonly provider: QuickOpenActionProvider) { } + + // tslint:disable-next-line:no-any + hasActions(element: any, entry: QuickOpenEntry | QuickOpenEntryGroup): boolean { + return this.provider.hasActions(entry.item); + } + + // tslint:disable-next-line:no-any + async getActions(element: any, entry: QuickOpenEntry | QuickOpenEntryGroup): monaco.Promise { + const actions = await this.provider.getActions(entry.item); + const monacoActions = actions.map(action => new MonacoQuickOpenAction(action)); + return monaco.Promise.wrap(monacoActions); + } + + hasSecondaryActions(): boolean { + return false; + } + + getSecondaryActions(): monaco.Promise { + return monaco.Promise.wrap([]); + } + + getActionItem() { + return undefined; + } +} diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index bb24439df9800..46985f2bf6565 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -743,8 +743,28 @@ declare module monaco.quickOpen { setShowBorder(showBorder: boolean): void; getEntry(): QuickOpenEntry | undefined; } + + export interface IAction extends IDisposable { + id: string; + label: string; + tooltip: string; + class: string | undefined; + enabled: boolean; + checked: boolean; + radio: boolean; + run(event?: any): PromiseLike; + } + + export interface IActionProvider { + hasActions(element: any, item: any): boolean; + getActions(element: any, item: any): monaco.Promise; + hasSecondaryActions(element: any, item: any): boolean; + getSecondaryActions(element: any, item: any): monaco.Promise; + getActionItem(element: any, item: any, action: IAction): any; + } + export class QuickOpenModel implements IModel, IDataSource, IFilter, IRunner { - constructor(entries?: QuickOpenEntry[] /*, actionProvider?: IActionProvider */); + constructor(entries?: QuickOpenEntry[], actionProvider?: IActionProvider); addEntries(entries: QuickOpenEntry[]): void; entries: QuickOpenEntry[]; dataSource: IDataSource; diff --git a/packages/navigator/package.json b/packages/navigator/package.json index 7b02c6a56c036..6be2af06b07af 100644 --- a/packages/navigator/package.json +++ b/packages/navigator/package.json @@ -1,11 +1,11 @@ { "name": "@theia/navigator", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Navigator Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/workspace": "^0.5.0", "fuzzy": "^0.1.3", "minimatch": "^3.0.4" }, @@ -38,11 +38,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/navigator/src/browser/navigator-container.ts b/packages/navigator/src/browser/navigator-container.ts index 84e42b62aa471..022cb691f01c9 100644 --- a/packages/navigator/src/browser/navigator-container.ts +++ b/packages/navigator/src/browser/navigator-container.ts @@ -28,7 +28,8 @@ export const FILE_NAVIGATOR_PROPS = { ...defaultTreeProps, contextMenuPath: NAVIGATOR_CONTEXT_MENU, multiSelect: true, - search: true + search: true, + globalSelection: true }; export function createFileNavigatorContainer(parent: interfaces.Container): Container { diff --git a/packages/navigator/src/browser/navigator-contribution.ts b/packages/navigator/src/browser/navigator-contribution.ts index 5d3a5a6c70378..6dbbd41ca1b22 100644 --- a/packages/navigator/src/browser/navigator-contribution.ts +++ b/packages/navigator/src/browser/navigator-contribution.ts @@ -30,6 +30,7 @@ import { NavigatorKeybindingContexts } from './navigator-keybinding-context'; import { FileNavigatorFilter } from './navigator-filter'; import { WorkspaceNode } from './navigator-tree'; import { NavigatorContextKeyService } from './navigator-context-key-service'; +import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; export namespace FileNavigatorCommands { export const REVEAL_IN_NAVIGATOR: Command = { @@ -41,7 +42,8 @@ export namespace FileNavigatorCommands { label: 'Toggle Hidden Files' }; export const COLLAPSE_ALL: Command = { - id: 'navigator.collapse.all' + id: 'navigator.collapse.all', + iconClass: 'collapse-all' }; } @@ -77,7 +79,7 @@ export namespace NavigatorContextMenu { } @injectable() -export class FileNavigatorContribution extends AbstractViewContribution implements FrontendApplicationContribution { +export class FileNavigatorContribution extends AbstractViewContribution implements FrontendApplicationContribution, TabBarToolbarContribution { @inject(NavigatorContextKeyService) protected readonly contextKeyService: NavigatorContextKeyService; @@ -91,7 +93,7 @@ export class FileNavigatorContribution extends AbstractViewContribution { @@ -135,12 +136,19 @@ export class FileNavigatorContribution extends AbstractViewContribution true }); registry.registerCommand(FileNavigatorCommands.COLLAPSE_ALL, { - execute: () => this.collapseFileNavigatorTree(), - isEnabled: () => this.workspaceService.opened, - isVisible: () => this.workspaceService.opened + execute: widget => this.withWidget(widget, () => this.collapseFileNavigatorTree()), + isEnabled: widget => this.withWidget(widget, () => this.workspaceService.opened), + isVisible: wodget => this.withWidget(wodget, () => this.workspaceService.opened) }); } + protected withWidget(widget: Widget | undefined = this.tryGetWidget(), cb: (navigator: FileNavigatorWidget) => T): T | false { + if (widget instanceof FileNavigatorWidget && widget.id === FILE_NAVIGATOR_ID) { + return cb(widget); + } + return false; + } + registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, { @@ -184,10 +192,15 @@ export class FileNavigatorContribution extends AbstractViewContribution { + toolbarRegistry.registerItem({ + id: FileNavigatorCommands.COLLAPSE_ALL.id, + command: FileNavigatorCommands.COLLAPSE_ALL.id, + tooltip: 'Collapse All', + priority: 0, + }); + } + /** * Reveals and selects node in the file navigator to which given widget is related. * Does nothing if given widget undefined or doesn't have related resource. diff --git a/packages/navigator/src/browser/navigator-filter.ts b/packages/navigator/src/browser/navigator-filter.ts index e3f4b0853357a..f9030af166a46 100644 --- a/packages/navigator/src/browser/navigator-filter.ts +++ b/packages/navigator/src/browser/navigator-filter.ts @@ -22,8 +22,6 @@ import { PreferenceChangeEvent } from '@theia/core/lib/browser/preferences'; import { FileSystemPreferences, FileSystemConfiguration } from '@theia/filesystem/lib/browser/filesystem-preferences'; import { FileNavigatorPreferences, FileNavigatorConfiguration } from './navigator-preferences'; -const FILES_EXCLUDE_PREFERENCE: keyof FileSystemConfiguration = 'files.exclude'; - /** * Filter for omitting elements from the navigator. For more details on the exclusion patterns, * one should check either the manual with `man 5 gitignore` or just [here](https://git-scm.com/docs/gitignore). @@ -44,7 +42,7 @@ export class FileNavigatorFilter { @postConstruct() protected async init(): Promise { - this.filterPredicate = this.createFilterPredicate(this.filesPreferences[FILES_EXCLUDE_PREFERENCE]); + this.filterPredicate = this.createFilterPredicate(this.filesPreferences['files.exclude']); this.filesPreferences.onPreferenceChanged(event => this.onFilesPreferenceChanged(event)); this.preferences.onPreferenceChanged(event => this.onPreferenceChanged(event)); } @@ -67,7 +65,7 @@ export class FileNavigatorFilter { protected onFilesPreferenceChanged(event: PreferenceChangeEvent): void { const { preferenceName, newValue } = event; - if (preferenceName === FILES_EXCLUDE_PREFERENCE) { + if (preferenceName === 'files.exclude') { this.filterPredicate = this.createFilterPredicate(newValue as FileNavigatorFilter.Exclusions | undefined || {}); this.fireFilterChanged(); } @@ -82,7 +80,7 @@ export class FileNavigatorFilter { toggleHiddenFiles(): void { this.showHiddenFiles = !this.showHiddenFiles; - const filesExcludes = this.filesPreferences[FILES_EXCLUDE_PREFERENCE]; + const filesExcludes = this.filesPreferences['files.exclude']; this.filterPredicate = this.createFilterPredicate(filesExcludes || {}); this.fireFilterChanged(); diff --git a/packages/navigator/src/browser/navigator-frontend-module.ts b/packages/navigator/src/browser/navigator-frontend-module.ts index c045effc11609..b85fdceb89957 100644 --- a/packages/navigator/src/browser/navigator-frontend-module.ts +++ b/packages/navigator/src/browser/navigator-frontend-module.ts @@ -26,6 +26,7 @@ import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; import { bindFileNavigatorPreferences } from './navigator-preferences'; import { FileNavigatorFilter } from './navigator-filter'; import { NavigatorContextKeyService } from './navigator-context-key-service'; +import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; export default new ContainerModule(bind => { bindFileNavigatorPreferences(bind); @@ -35,6 +36,7 @@ export default new ContainerModule(bind => { bindViewContribution(bind, FileNavigatorContribution); bind(FrontendApplicationContribution).toService(FileNavigatorContribution); + bind(TabBarToolbarContribution).toService(FileNavigatorContribution); bind(KeybindingContext).to(NavigatorActiveContext).inSingletonScope(); diff --git a/packages/navigator/src/browser/navigator-model.spec.ts b/packages/navigator/src/browser/navigator-model.spec.ts index 582fa9dc1b873..741fa6532344d 100644 --- a/packages/navigator/src/browser/navigator-model.spec.ts +++ b/packages/navigator/src/browser/navigator-model.spec.ts @@ -129,6 +129,7 @@ describe('FileNavigatorModel', () => { let mockPreferences: CorePreferences; const mockWorkspaceServiceEmitter: Emitter = new Emitter(); + const mockWorkspaceOnLocationChangeEmitter: Emitter = new Emitter(); const mockFileChangeEmitter: Emitter = new Emitter(); const mockFileMoveEmitter: Emitter = new Emitter(); const mockTreeChangeEmitter: Emitter = new Emitter(); @@ -175,6 +176,7 @@ describe('FileNavigatorModel', () => { testContainer.bind(CorePreferences).toConstantValue(mockPreferences); sinon.stub(mockWorkspaceService, 'onWorkspaceChanged').value(mockWorkspaceServiceEmitter.event); + sinon.stub(mockWorkspaceService, 'onWorkspaceLocationChanged').value(mockWorkspaceOnLocationChangeEmitter.event); sinon.stub(mockFileSystemWatcher, 'onFilesChanged').value(mockFileChangeEmitter.event); sinon.stub(mockFileSystemWatcher, 'onDidMove').value(mockFileMoveEmitter.event); sinon.stub(mockFileNavigatorTree, 'onChanged').value(mockTreeChangeEmitter.event); diff --git a/packages/navigator/src/browser/navigator-model.ts b/packages/navigator/src/browser/navigator-model.ts index 1d1252aa71869..4f9b6c00b5bd4 100644 --- a/packages/navigator/src/browser/navigator-model.ts +++ b/packages/navigator/src/browser/navigator-model.ts @@ -35,6 +35,11 @@ export class FileNavigatorModel extends FileTreeModel { this.updateRoot(); }) ); + this.toDispose.push( + this.workspaceService.onWorkspaceLocationChanged(() => { + this.updateRoot(); + }) + ); super.init(); } @@ -71,7 +76,11 @@ export class FileNavigatorModel extends FileTreeModel { protected async createRoot(): Promise { if (this.workspaceService.opened) { - const workspaceNode = WorkspaceNode.createRoot(); + const stat = this.workspaceService.workspace; + const isMulti = (stat) ? !stat.isDirectory : false; + const workspaceNode = isMulti + ? this.createMultipleRootNode() + : WorkspaceNode.createRoot(); const roots = await this.workspaceService.roots; for (const root of roots) { workspaceNode.children.push( @@ -82,6 +91,20 @@ export class FileNavigatorModel extends FileTreeModel { } } + /** + * Create multiple root node used to display + * the multiple root workspace name. + * + * @returns `WorkspaceNode` + */ + protected createMultipleRootNode(): WorkspaceNode { + const workspace = this.workspaceService.workspace; + const name = (workspace) + ? new URI(workspace.uri).path.name + : 'untitled'; + return WorkspaceNode.createRoot(name); + } + /** * Move the given source file or directory to the given target directory. */ diff --git a/packages/navigator/src/browser/navigator-tree.ts b/packages/navigator/src/browser/navigator-tree.ts index 28fc0ecb46764..5d8968d9f513e 100644 --- a/packages/navigator/src/browser/navigator-tree.ts +++ b/packages/navigator/src/browser/navigator-tree.ts @@ -63,16 +63,16 @@ export namespace WorkspaceNode { export const name = 'WorkspaceNode'; export function is(node: TreeNode | undefined): node is WorkspaceNode { - return CompositeTreeNode.is(node) && node.name === WorkspaceNode.name; + return CompositeTreeNode.is(node) && node.id === WorkspaceNode.id; } - export function createRoot(): WorkspaceNode { + export function createRoot(multiRootName?: string): WorkspaceNode { return { id: WorkspaceNode.id, - name: WorkspaceNode.name, + name: multiRootName || WorkspaceNode.name, parent: undefined, children: [], - visible: false, + visible: !!multiRootName, selected: false }; } diff --git a/packages/navigator/src/browser/navigator-widget.tsx b/packages/navigator/src/browser/navigator-widget.tsx index 4f3f8f824c230..e1d341686e717 100644 --- a/packages/navigator/src/browser/navigator-widget.tsx +++ b/packages/navigator/src/browser/navigator-widget.tsx @@ -17,7 +17,7 @@ import { injectable, inject, postConstruct } from 'inversify'; import { Message } from '@phosphor/messaging'; import URI from '@theia/core/lib/common/uri'; -import { CommandService, SelectionService, Disposable } from '@theia/core/lib/common'; +import { CommandService, SelectionService } from '@theia/core/lib/common'; import { CommonCommands, CorePreferences } from '@theia/core/lib/browser'; import { ContextMenuRenderer, ExpandableTreeNode, @@ -35,7 +35,7 @@ import * as React from 'react'; import { NavigatorContextKeyService } from './navigator-context-key-service'; export const FILE_NAVIGATOR_ID = 'files'; -export const LABEL = 'Files'; +export const LABEL = 'Explorer'; export const CLASS = 'theia-Files'; @injectable() @@ -60,7 +60,8 @@ export class FileNavigatorWidget extends FileTreeWidget { this.id = FILE_NAVIGATOR_ID; this.title.label = LABEL; this.title.caption = LABEL; - this.title.iconClass = 'fa navigator-tab-icon'; + this.title.closable = true; + this.title.iconClass = 'navigator-tab-icon'; this.addClass(CLASS); this.initialize(); } @@ -70,12 +71,9 @@ export class FileNavigatorWidget extends FileTreeWidget { super.init(); this.updateSelectionContextKeys(); this.toDispose.pushAll([ - this.model.onSelectionChanged(selection => { - if (this.shell.activeWidget === this) { - this.selectionService.selection = selection; - } - this.updateSelectionContextKeys(); - }), + this.model.onSelectionChanged(() => + this.updateSelectionContextKeys() + ), this.model.onExpansionChanged(node => { if (node.expanded && node.children.length === 1) { const child = node.children[0]; @@ -83,18 +81,13 @@ export class FileNavigatorWidget extends FileTreeWidget { this.model.expandNode(child); } } - }), - Disposable.create(() => { - if (this.selectionService.selection === this) { - this.selectionService.selection = undefined; - } + }) ]); } - protected onActivateRequest(msg: Message): void { - super.onActivateRequest(msg); - this.selectionService.selection = this.model.selectedNodes; + protected async initialize(): Promise { + await this.model.updateRoot(); const root = this.model.root; if (CompositeTreeNode.is(root) && root.children.length === 1) { const child = root.children[0]; @@ -105,10 +98,6 @@ export class FileNavigatorWidget extends FileTreeWidget { } } - protected async initialize(): Promise { - await this.model.updateRoot(); - } - protected enableDndOnMainPanel(): void { const mainPanelNode = this.shell.mainPanel.node; this.addEventListener(mainPanelNode, 'drop', async ({ dataTransfer }) => { diff --git a/packages/navigator/src/browser/style/files.svg b/packages/navigator/src/browser/style/files.svg new file mode 100644 index 0000000000000..9ee9fdb925f13 --- /dev/null +++ b/packages/navigator/src/browser/style/files.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/navigator/src/browser/style/index.css b/packages/navigator/src/browser/style/index.css index f8c6dca6616f1..abc3a6ab43842 100644 --- a/packages/navigator/src/browser/style/index.css +++ b/packages/navigator/src/browser/style/index.css @@ -24,6 +24,9 @@ .theia-navigator-container .open-workspace-button-container { margin: auto; margin-top: 5px; + display: flex; + justify-content: center; + align-self: center; } .theia-navigator-container .center { @@ -45,6 +48,14 @@ width: calc(100% - var(--theia-ui-padding)*4); } -.navigator-tab-icon::before { - content: "\f0c5" +.navigator-tab-icon { + -webkit-mask: url('files.svg'); + mask: url('files.svg'); +} + +#files > div > div > div > div:first-child { + background-color: var(--theia-layout-color4); + text-transform: uppercase; + font-size: 80%; + font-weight: 500; } diff --git a/packages/outline-view/package.json b/packages/outline-view/package.json index d3cc888cb2a07..82f9d4638e2b2 100644 --- a/packages/outline-view/package.json +++ b/packages/outline-view/package.json @@ -1,9 +1,9 @@ { "name": "@theia/outline-view", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Outline View Extension", "dependencies": { - "@theia/core": "^0.4.0" + "@theia/core": "^0.5.0" }, "publishConfig": { "access": "public" @@ -34,11 +34,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/outline-view/src/browser/outline-view-widget.tsx b/packages/outline-view/src/browser/outline-view-widget.tsx index 888770869c23e..dc1086919efb3 100644 --- a/packages/outline-view/src/browser/outline-view-widget.tsx +++ b/packages/outline-view/src/browser/outline-view-widget.tsx @@ -58,6 +58,7 @@ export class OutlineViewWidget extends TreeWidget { this.id = 'outline-view'; this.title.label = 'Outline'; this.title.caption = 'Outline'; + this.title.closable = true; this.title.iconClass = 'fa outline-view-tab-icon'; this.addClass('theia-outline-view'); } diff --git a/packages/outline-view/src/browser/styles/index.css b/packages/outline-view/src/browser/styles/index.css index 3a680e34b2733..241cb2111bc72 100644 --- a/packages/outline-view/src/browser/styles/index.css +++ b/packages/outline-view/src/browser/styles/index.css @@ -23,3 +23,7 @@ padding: 10px; text-align: left; } + +.theia-side-panel .no-outline { + margin-left: 9px; +} diff --git a/packages/output/package.json b/packages/output/package.json index ed2b409361bb7..cf561faccecd2 100644 --- a/packages/output/package.json +++ b/packages/output/package.json @@ -1,9 +1,9 @@ { "name": "@theia/output", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Output Extension", "dependencies": { - "@theia/core": "^0.4.0" + "@theia/core": "^0.5.0" }, "publishConfig": { "access": "public" @@ -34,11 +34,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/output/src/browser/style/output.css b/packages/output/src/browser/style/output.css index e28472800b297..99a160622d4c3 100644 --- a/packages/output/src/browser/style/output.css +++ b/packages/output/src/browser/style/output.css @@ -26,6 +26,10 @@ box-sizing: border-box; } +.theia-side-panel #outputView #outputContents { + margin-left: 14px; +} + #outputView #outputOverlay { position: absolute; right: 24px; diff --git a/packages/plugin-ext-vscode/package.json b/packages/plugin-ext-vscode/package.json index c0c21524f20f2..bb28d96f068ae 100644 --- a/packages/plugin-ext-vscode/package.json +++ b/packages/plugin-ext-vscode/package.json @@ -1,11 +1,11 @@ { "name": "@theia/plugin-ext-vscode", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Plugin Extension for VsCode", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/plugin": "^0.4.0", - "@theia/plugin-ext": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/plugin": "^0.5.0", + "@theia/plugin-ext": "^0.5.0", "vscode-uri": "^1.0.1" }, "publishConfig": { @@ -38,11 +38,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts index 222ad178961bb..7bc4cb01f6e2d 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts @@ -20,11 +20,18 @@ import { PluginVsCodeFileHandler } from './plugin-vscode-file-handler'; import { PluginVsCodeDirectoryHandler } from './plugin-vscode-directory-handler'; import { VsCodePluginScanner } from './scanner-vscode'; import { VsCodePluginDeployerResolver } from './plugin-vscode-resolver'; +import { PluginVsCodeCliContribution } from './plugin-vscode-cli-contribution'; +import { CliContribution } from '@theia/core/lib/node'; +import { PluginHostEnvironmentVariable } from '@theia/plugin-ext/lib/common'; export default new ContainerModule(bind => { bind(PluginDeployerFileHandler).to(PluginVsCodeFileHandler).inSingletonScope(); bind(PluginDeployerDirectoryHandler).to(PluginVsCodeDirectoryHandler).inSingletonScope(); bind(PluginScanner).to(VsCodePluginScanner).inSingletonScope(); bind(PluginDeployerResolver).to(VsCodePluginDeployerResolver).inSingletonScope(); + + bind(PluginVsCodeCliContribution).toSelf().inSingletonScope(); + bind(CliContribution).toService(PluginVsCodeCliContribution); + bind(PluginHostEnvironmentVariable).toService(PluginVsCodeCliContribution); } ); diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts new file mode 100644 index 0000000000000..1cc721621c0b5 --- /dev/null +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. 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 { Argv, Arguments } from 'yargs'; +import { CliContribution } from '@theia/core/lib/node/cli'; +import { PluginHostEnvironmentVariable } from '@theia/plugin-ext/lib/common'; +import { VSCODE_DEFAULT_API_VERSION } from './plugin-vscode-init'; +/** + * CLI Contribution allowing to override the VS Code API version which is returned by `vscode.version` API call. + */ +@injectable() +export class PluginVsCodeCliContribution implements CliContribution, PluginHostEnvironmentVariable { + + static VSCODE_API_VERSION = 'vscode-api-version'; + + protected vsCodeApiVersion: string | undefined; + + configure(conf: Argv): void { + conf.option(PluginVsCodeCliContribution.VSCODE_API_VERSION, { + // tslint:disable-next-line:max-line-length + description: `Overrides the version returned by VSCode API 'vscode.version'. Example: --${PluginVsCodeCliContribution.VSCODE_API_VERSION}=. Default [${VSCODE_DEFAULT_API_VERSION}]`, + type: 'string', + nargs: 1 + }); + } + + setArguments(args: Arguments): void { + const arg = args[PluginVsCodeCliContribution.VSCODE_API_VERSION]; + if (arg) { + this.vsCodeApiVersion = arg; + } + } + + process(env: NodeJS.ProcessEnv): void { + if (this.vsCodeApiVersion) { + env['VSCODE_API_VERSION'] = this.vsCodeApiVersion; + } + } + +} diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts index 1e017b9fd7416..86c305834fea7 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2018 Red Hat, Inc. + * Copyright (C) 2018-2019 Red Hat, Inc. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,6 +19,8 @@ import * as theia from '@theia/plugin'; import { BackendInitializationFn, PluginAPIFactory, Plugin, emptyPlugin } from '@theia/plugin-ext'; +export const VSCODE_DEFAULT_API_VERSION = '1.32.3'; + /** Set up en as a default locale for VS Code extensions using vscode-nls */ process.env['VSCODE_NLS_CONFIG'] = JSON.stringify({ locale: 'en', availableLanguages: {} }); process.env['VSCODE_PID'] = process.env['THEIA_PARENT_PID']; @@ -72,7 +74,7 @@ export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIF }; // override the version for vscode to be a VSCode version - (vscode).version = '1.27.2'; + (vscode).version = process.env['VSCODE_API_VERSION'] || VSCODE_DEFAULT_API_VERSION; pluginsApiImpl.set(plugin.model.id, vscode); plugins.push(plugin); diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts index 08bd9caf904e7..40a2a0a70bea2 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts @@ -57,7 +57,7 @@ export class VsCodePluginDeployerResolver implements PluginDeployerResolver { const extracted = /^vscode:extension\/(.*)/gm.exec(pluginResolverContext.getOriginId()); if (!extracted || extracted === null) { - reject('Invalid extension' + pluginResolverContext.getOriginId()); + reject(new Error('Invalid extension' + pluginResolverContext.getOriginId())); return; } const extensionName = extracted[1]; @@ -84,7 +84,7 @@ export class VsCodePluginDeployerResolver implements PluginDeployerResolver { } else if (response.statusCode === 200) { const extension = body.results[0].extensions[0]; if (!extension) { - reject('No extension'); + reject(new Error('No extension')); } let asset; if (wantedExtensionVersion !== undefined) { diff --git a/packages/plugin-ext/package.json b/packages/plugin-ext/package.json index 9df5e35de5290..8db527c5fee4a 100644 --- a/packages/plugin-ext/package.json +++ b/packages/plugin-ext/package.json @@ -1,24 +1,24 @@ { "name": "@theia/plugin-ext", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Plugin Extension", "main": "lib/common/index.js", "typings": "lib/common/index.d.ts", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/debug": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/file-search": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/markers": "^0.4.0", - "@theia/messages": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/navigator": "^0.4.0", - "@theia/plugin": "^0.4.0", - "@theia/preferences": "^0.4.0", - "@theia/search-in-workspace": "^0.4.0", - "@theia/task": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/debug": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/file-search": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/markers": "^0.5.0", + "@theia/messages": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/navigator": "^0.5.0", + "@theia/plugin": "^0.5.0", + "@theia/preferences": "^0.5.0", + "@theia/search-in-workspace": "^0.5.0", + "@theia/task": "^0.5.0", + "@theia/workspace": "^0.5.0", "decompress": "^4.2.0", "jsonc-parser": "^2.0.2", "lodash.clonedeep": "^4.5.0", @@ -58,11 +58,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0", + "@theia/ext-scripts": "^0.5.0", "@types/decompress": "^4.2.2", "@types/lodash.clonedeep": "^4.5.3" }, diff --git a/packages/plugin-ext/src/api/async-util.ts b/packages/plugin-ext/src/api/async-util.ts index a048e915afe63..48e80583ad96d 100644 --- a/packages/plugin-ext/src/api/async-util.ts +++ b/packages/plugin-ext/src/api/async-util.ts @@ -18,7 +18,7 @@ import { Deferred } from '@theia/core/lib/common/promise-util'; export function hookCancellationToken(token: CancellationToken, promise: Promise): PromiseLike { return new Promise((resolve, reject) => { - const sub = token.onCancellationRequested(() => reject('This promise is cancelled')); + const sub = token.onCancellationRequested(() => reject(new Error('This promise is cancelled'))); promise.then(value => { sub.dispose(); resolve(value); diff --git a/packages/plugin-ext/src/api/plugin-api.ts b/packages/plugin-ext/src/api/plugin-api.ts index f407b48b9bd78..c18921aa51085 100644 --- a/packages/plugin-ext/src/api/plugin-api.ts +++ b/packages/plugin-ext/src/api/plugin-api.ts @@ -384,6 +384,7 @@ export interface WorkspaceMain { $onTextDocumentContentChange(uri: string, content: string): void; $registerFileSystemWatcher(options: FileWatcherSubscriberOptions): Promise; $unregisterFileSystemWatcher(watcherId: string): Promise; + $updateWorkspaceFolders(start: number, deleteCount?: number, ...rootsToAdd: string[]): Promise; } export interface WorkspaceExt { @@ -423,6 +424,8 @@ export class TreeViewItem { collapsibleState?: TreeViewItemCollapsibleState; + metadata?: any; + } /** @@ -740,6 +743,13 @@ export interface PreferenceChangeExt { preferenceName: string, newValue: any } + +export interface TerminalOptionsExt { + attributes?: { + [key: string]: string; + } +} + export interface PreferenceRegistryExt { $acceptConfigurationChanged(data: { [key: string]: any }, eventData: PreferenceChangeExt): void; } @@ -903,7 +913,7 @@ export interface LanguagesMain { $registerImplementationProvider(handle: number, selector: SerializedDocumentFilter[]): void; $registerTypeDefinitionProvider(handle: number, selector: SerializedDocumentFilter[]): void; $registerDefinitionProvider(handle: number, selector: SerializedDocumentFilter[]): void; - $registeReferenceProvider(handle: number, selector: SerializedDocumentFilter[]): void; + $registerReferenceProvider(handle: number, selector: SerializedDocumentFilter[]): void; $registerSignatureHelpProvider(handle: number, selector: SerializedDocumentFilter[], triggerCharacters: string[]): void; $registerHoverProvider(handle: number, selector: SerializedDocumentFilter[]): void; $registerDocumentHighlightProvider(handle: number, selector: SerializedDocumentFilter[]): void; @@ -983,6 +993,7 @@ export interface DebugExt { $getConfigurationSnippets(debugType: string): Promise; $createDebugSession(debugConfiguration: theia.DebugConfiguration): Promise; $terminateDebugSession(sessionId: string): Promise; + $getTerminalCreationOptions(debugType: string): Promise; } export interface DebugMain { diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index 1e9d8fc2c8910..0529608372f4a 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -18,10 +18,10 @@ import { RPCProtocol } from '../api/rpc-protocol'; import { Disposable } from '@theia/core/lib/common/disposable'; import { LogPart, KeysToAnyValues, KeysToKeysToAnyValue } from './types'; import { CharacterPair, CommentRule, PluginAPIFactory, Plugin } from '../api/plugin-api'; -// FIXME get rid of browser code in backend -import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/browser/preferences'; import { ExtPluginApi } from './plugin-ext-api-contribution'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; +import { RecursivePartial } from '@theia/core/lib/common/types'; +import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/common/preferences/preference-schema'; export const hostedServicePath = '/services/hostedPlugin'; @@ -55,8 +55,8 @@ export interface PluginPackage { * This interface describes a package.json contribution section object. */ export interface PluginPackageContribution { - configuration?: PreferenceSchema; - configurationDefaults?: PreferenceSchemaProperties; + configuration?: RecursivePartial; + configurationDefaults?: RecursivePartial; languages?: PluginPackageLanguageContribution[]; grammars?: PluginPackageGrammarsContribution[]; viewsContainers?: { [location: string]: PluginPackageViewContainer[] }; @@ -453,7 +453,7 @@ export interface FoldingRules { export interface ViewContainer { id: string; title: string; - icon: string; + iconUrl: string; } /** @@ -628,3 +628,8 @@ export interface ServerPluginRunner { */ getExtraPluginMetadata(): Promise; } + +export const PluginHostEnvironmentVariable = Symbol('PluginHostEnvironmentVariable'); +export interface PluginHostEnvironmentVariable { + process(env: NodeJS.ProcessEnv): void; +} diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin-manager-client.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin-manager-client.ts index 91c264f1d222e..7453fa705df7c 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin-manager-client.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin-manager-client.ts @@ -247,7 +247,10 @@ export class HostedPluginManagerClient { const dialog = this.openFileDialogFactory({ title: HostedPluginCommands.SELECT_PATH.label!, - canSelectFiles: false + openLabel: 'Select', + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false }); dialog.model.navigateTo(rootNode); const result = await dialog.open(); diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 231c3ca99201a..a8c5665abe017 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -65,6 +65,12 @@ export class HostedPluginSupport { private frontendExtManagerProxy: PluginManagerExt; private backendExtManagerProxy: PluginManagerExt; + // loaded plugins per #id + private loadedPlugins: Set = new Set(); + + // per #hostKey + private rpc: Map = new Map(); + constructor( @inject(PreferenceServiceImpl) private readonly preferenceServiceImpl: PreferenceServiceImpl, @inject(PluginPathsService) private readonly pluginPathsService: PluginPathsService, @@ -72,7 +78,6 @@ export class HostedPluginSupport { @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService, ) { this.theiaReadyPromise = Promise.all([this.preferenceServiceImpl.ready, this.workspaceService.roots]); - this.storagePathService.onStoragePathChanged(path => { this.updateStoragePath(path); }); @@ -110,6 +115,10 @@ export class HostedPluginSupport { if (initData.hostedPlugin) { initData.plugins.push(initData.hostedPlugin); } + + // don't load plugins twice + initData.plugins = initData.plugins.filter(value => !this.loadedPlugins.has(value.model.id)); + const confStorage: ConfigStorage = { hostLogPath: initData.logPath, hostStoragePath: initData.storagePath || '' @@ -151,8 +160,14 @@ export class HostedPluginSupport { if (plugins.length >= 1) { pluginID = getPluginId(plugins[0].model); } - const rpc = this.createServerRpc(pluginID, hostKey); - setUpPluginApi(rpc, container); + + let rpc = this.rpc.get(hostKey); + if (!rpc) { + rpc = this.createServerRpc(pluginID, hostKey); + setUpPluginApi(rpc, container); + this.rpc.set(hostKey, rpc); + } + const hostedExtManager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT); hostedExtManager.$init({ plugins: plugins, @@ -162,10 +177,13 @@ export class HostedPluginSupport { env: { queryParams: getQueryParameters() }, extApi: initData.pluginAPIs }, confStorage); - this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, container)); + this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc!, container)); this.backendExtManagerProxy = hostedExtManager; }); } + + // update list with loaded plugins + initData.plugins.forEach(value => this.loadedPlugins.add(value.model.id)); }); } diff --git a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts index f6b793e971cb1..bed91911901f9 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts @@ -27,6 +27,7 @@ import { ExtPluginApi } from '../../../common/plugin-ext-api-contribution'; import { createDebugExtStub } from './debug-stub'; import { EditorsAndDocumentsExtImpl } from '../../../plugin/editors-and-documents'; import { WorkspaceExtImpl } from '../../../plugin/workspace'; +import { MessageRegistryExt } from '../../../plugin/message-registry'; // tslint:disable-next-line:no-any const ctx = self as any; @@ -50,7 +51,8 @@ function initialize(contextPath: string, pluginMetadata: PluginMetadata): void { } const envExt = new EnvExtImpl(rpc); const editorsAndDocuments = new EditorsAndDocumentsExtImpl(rpc); -const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments); +const messageRegistryExt = new MessageRegistryExt(rpc); +const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments, messageRegistryExt); const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt); const debugExt = createDebugExtStub(rpc); @@ -130,7 +132,8 @@ const apiFactory = createAPIFactory( debugExt, preferenceRegistryExt, editorsAndDocuments, - workspaceExt + workspaceExt, + messageRegistryExt ); let defaultApi: typeof theia; diff --git a/packages/plugin-ext/src/hosted/node/hosted-instance-manager.ts b/packages/plugin-ext/src/hosted/node/hosted-instance-manager.ts index 9de9f9f3148f4..c30e5e39322c1 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-instance-manager.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-instance-manager.ts @@ -313,7 +313,7 @@ export abstract class AbstractHostedInstanceManager implements HostedInstanceMan if (!started) { this.terminate(); this.isPluginRunnig = false; - reject('Timeout.'); + reject(new Error('Timeout.')); } }, HOSTED_INSTANCE_START_TIMEOUT_MS); }); diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts index 52fb1bc5e6e2a..9a0b2b1c96a6a 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts @@ -50,6 +50,10 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler { for (const plugin of frontendPlugins) { const metadata = await this.reader.getPluginMetadata(plugin.path()); if (metadata) { + if (this.getDeployedFrontendMetadata().some(value => value.model.id === metadata.model.id)) { + continue; + } + this.currentFrontendPluginsMetadata.push(metadata); this.logger.info(`Deploying frontend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.frontend || plugin.path()}"`); } @@ -60,6 +64,10 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler { for (const plugin of backendPlugins) { const metadata = await this.reader.getPluginMetadata(plugin.path()); if (metadata) { + if (this.getDeployedBackendMetadata().some(value => value.model.id === metadata.model.id)) { + continue; + } + this.currentBackendPluginsMetadata.push(metadata); this.logger.info(`Deploying backend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.backend || plugin.path()}"`); } diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts index 5a9647ba48bba..696edae185c32 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts @@ -16,11 +16,11 @@ import * as path from 'path'; import * as cp from 'child_process'; -import { injectable, inject } from 'inversify'; -import { ILogger, ConnectionErrorHandler } from '@theia/core/lib/common'; +import { injectable, inject, named } from 'inversify'; +import { ILogger, ConnectionErrorHandler, ContributionProvider } from '@theia/core/lib/common'; import { Emitter } from '@theia/core/lib/common/event'; import { createIpcEnv } from '@theia/core/lib/node/messaging/ipc-protocol'; -import { HostedPluginClient, ServerPluginRunner, PluginMetadata } from '../../common/plugin-protocol'; +import { HostedPluginClient, ServerPluginRunner, PluginMetadata, PluginHostEnvironmentVariable } from '../../common/plugin-protocol'; import { RPCProtocolImpl } from '../../api/rpc-protocol'; import { MAIN_RPC_CONTEXT } from '../../api/plugin-api'; import { HostedPluginCliContribution } from './hosted-plugin-cli-contribution'; @@ -41,6 +41,10 @@ export class HostedPluginProcess implements ServerPluginRunner { @inject(HostedPluginCliContribution) protected readonly cli: HostedPluginCliContribution; + @inject(ContributionProvider) + @named(PluginHostEnvironmentVariable) + protected readonly pluginHostEnvironmentVariables: ContributionProvider; + private childProcess: cp.ChildProcess | undefined; private client: HostedPluginClient; @@ -128,6 +132,8 @@ export class HostedPluginProcess implements ServerPluginRunner { delete env[key]; } } + // apply external env variables + this.pluginHostEnvironmentVariables.getContributions().forEach(envVar => envVar.process(env)); if (this.cli.extensionTestsPath) { env.extensionTestsPath = this.cli.extensionTestsPath; } diff --git a/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts b/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts index 7ed9fcf9ac907..673b519b71e84 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts @@ -27,7 +27,7 @@ import { HostedPluginReader } from './plugin-reader'; import { HostedPluginSupport } from './hosted-plugin'; import { TheiaPluginScanner } from './scanners/scanner-theia'; import { HostedPluginsManager, HostedPluginsManagerImpl } from './hosted-plugins-manager'; -import { HostedPluginServer, PluginScanner, HostedPluginClient, hostedServicePath, PluginDeployerHandler } from '../../common/plugin-protocol'; +import { HostedPluginServer, PluginScanner, HostedPluginClient, hostedServicePath, PluginDeployerHandler, PluginHostEnvironmentVariable } from '../../common/plugin-protocol'; import { GrammarsReader } from './scanners/grammars-reader'; import { HostedPluginProcess } from './hosted-plugin-process'; import { ExtPluginApiProvider } from '../../common/plugin-ext-api-contribution'; @@ -42,6 +42,8 @@ const commonHostedConnectionModule = ConnectionContainerModule.create(({ bind, b bind(HostedPluginsManager).toService(HostedPluginsManagerImpl); bindContributionProvider(bind, Symbol.for(ExtPluginApiProvider)); + bindContributionProvider(bind, PluginHostEnvironmentVariable); + bind(HostedPluginServerImpl).toSelf().inSingletonScope(); bind(HostedPluginServer).toService(HostedPluginServerImpl); bindBackendService(hostedServicePath, HostedPluginServer, (server, client) => { diff --git a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts index e6ccc3a546446..c1532a0e787a0 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts @@ -24,6 +24,7 @@ import { ExtPluginApi } from '../../common/plugin-ext-api-contribution'; import { DebugExtImpl } from '../../plugin/node/debug/debug'; import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents'; import { WorkspaceExtImpl } from '../../plugin/workspace'; +import { MessageRegistryExt } from '../../plugin/message-registry'; /** * Handle the RPC calls. @@ -42,7 +43,8 @@ export class PluginHostRPC { const envExt = new EnvExtImpl(this.rpc); const debugExt = new DebugExtImpl(this.rpc); const editorsAndDocumentsExt = new EditorsAndDocumentsExtImpl(this.rpc); - const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt); + const messageRegistryExt = new MessageRegistryExt(this.rpc); + const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt); const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt); this.pluginManager = this.createPluginManager(envExt, preferenceRegistryExt, this.rpc); this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager); @@ -57,7 +59,8 @@ export class PluginHostRPC { debugExt, preferenceRegistryExt, editorsAndDocumentsExt, - workspaceExt + workspaceExt, + messageRegistryExt ); } diff --git a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts index 6331b304a1030..948ea701877d2 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -54,6 +54,8 @@ import * as jsoncparser from 'jsonc-parser'; import { IJSONSchema } from '@theia/core/lib/common/json-schema'; import { deepClone } from '@theia/core/lib/common/objects'; import { FileUri } from '@theia/core/lib/node/file-uri'; +import { PreferenceSchema, PreferenceSchemaProperties } from '@theia/core/lib/common/preferences/preference-schema'; +import { RecursivePartial } from '@theia/core/lib/common/types'; namespace nls { export function localize(key: string, _default: string) { @@ -115,11 +117,12 @@ export class TheiaPluginScanner implements PluginScanner { } const contributions: PluginContribution = {}; - if (rawPlugin.contributes!.configuration) { - const config = this.readConfiguration(rawPlugin.contributes.configuration!, rawPlugin.packagePath); + if (rawPlugin.contributes.configuration) { + const config = this.readConfiguration(rawPlugin.contributes.configuration, rawPlugin.packagePath); contributions.configuration = config; } - contributions.configurationDefaults = rawPlugin.contributes.configurationDefaults; + const configurationDefaults = rawPlugin.contributes.configurationDefaults; + contributions.configurationDefaults = PreferenceSchemaProperties.is(configurationDefaults) ? configurationDefaults : undefined; if (rawPlugin.contributes!.languages) { const languages = this.readLanguages(rawPlugin.contributes.languages!, rawPlugin.packagePath); @@ -135,7 +138,7 @@ export class TheiaPluginScanner implements PluginScanner { contributions.viewsContainers = {}; Object.keys(rawPlugin.contributes.viewsContainers!).forEach(location => { - const containers = this.readViewsContainers(rawPlugin.contributes!.viewsContainers![location], rawPlugin.packagePath); + const containers = this.readViewsContainers(rawPlugin.contributes!.viewsContainers![location], rawPlugin); if (location === 'activitybar') { location = 'left'; } @@ -235,12 +238,8 @@ export class TheiaPluginScanner implements PluginScanner { } // tslint:disable-next-line:no-any - private readConfiguration(rawConfiguration: any, pluginPath: string): any { - return { - type: rawConfiguration.type, - title: rawConfiguration.title, - properties: rawConfiguration.properties - }; + private readConfiguration(rawConfiguration: RecursivePartial, pluginPath: string): PreferenceSchema | undefined { + return PreferenceSchema.is(rawConfiguration) ? rawConfiguration : undefined; } private readKeybinding(rawKeybinding: PluginPackageKeybinding): Keybinding { @@ -254,18 +253,16 @@ export class TheiaPluginScanner implements PluginScanner { }; } - private readViewsContainers(rawViewsContainers: PluginPackageViewContainer[], pluginPath: string): ViewContainer[] { - return rawViewsContainers.map(rawViewContainer => this.readViewContainer(rawViewContainer, pluginPath)); + private readViewsContainers(rawViewsContainers: PluginPackageViewContainer[], pck: PluginPackage): ViewContainer[] { + return rawViewsContainers.map(rawViewContainer => this.readViewContainer(rawViewContainer, pck)); } - private readViewContainer(rawViewContainer: PluginPackageViewContainer, pluginPath: string): ViewContainer { - const result: ViewContainer = { + private readViewContainer(rawViewContainer: PluginPackageViewContainer, pck: PluginPackage): ViewContainer { + return { id: rawViewContainer.id, title: rawViewContainer.title, - icon: rawViewContainer.icon + iconUrl: this.toPluginUrl(pck, rawViewContainer.icon) }; - - return result; } private readViews(rawViews: PluginPackageView[]): View[] { diff --git a/packages/plugin-ext/src/main/browser/debug/debug-main.ts b/packages/plugin-ext/src/main/browser/debug/debug-main.ts index 5346030da84a9..83df89de42b7a 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -115,6 +115,7 @@ export class DebugMainImpl implements DebugMain { async $registerDebuggerContribution(description: DebuggerDescription): Promise { const disposable = new DisposableCollection(); this.toDispose.set(description.type, disposable); + const terminalOptionsExt = await this.debugExt.$getTerminalCreationOptions(description.type); const debugSessionFactory = new PluginDebugSessionFactory( this.terminalService, @@ -128,7 +129,8 @@ export class DebugMainImpl implements DebugMain { const connection = await this.connectionMain.ensureConnection(sessionId); return new PluginWebSocketChannel(connection); }, - this.fileSystem + this.fileSystem, + terminalOptionsExt ); disposable.pushAll([ diff --git a/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts b/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts index 7dbfb48ebb82e..3ea8661d5b2d5 100644 --- a/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts +++ b/packages/plugin-ext/src/main/browser/debug/plugin-debug-session-factory.ts @@ -27,6 +27,30 @@ import { DebugSession } from '@theia/debug/lib/browser/debug-session'; import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection'; import { IWebSocket } from 'vscode-ws-jsonrpc/lib/socket/socket'; import { FileSystem } from '@theia/filesystem/lib/common'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { TerminalWidgetOptions } from '@theia/terminal/lib/browser/base/terminal-widget'; +import { TerminalOptionsExt } from '../../../api/plugin-api'; + +export class PluginDebugSession extends DebugSession { + constructor( + readonly id: string, + readonly options: DebugSessionOptions, + protected readonly connection: DebugSessionConnection, + protected readonly terminalServer: TerminalService, + protected readonly editorManager: EditorManager, + protected readonly breakpoints: BreakpointManager, + protected readonly labelProvider: LabelProvider, + protected readonly messages: MessageClient, + protected readonly fileSystem: FileSystem, + protected readonly terminalOptionsExt: TerminalOptionsExt | undefined) { + super(id, options, connection, terminalServer, editorManager, breakpoints, labelProvider, messages, fileSystem); + } + + protected async doRunInTerminal(terminalWidgetOptions: TerminalWidgetOptions): Promise { + terminalWidgetOptions = Object.assign({}, terminalWidgetOptions, this.terminalOptionsExt); + return super.doRunInTerminal(terminalWidgetOptions); + } +} /** * Session factory for a client debug session that communicates with debug adapter contributed as plugin. @@ -42,7 +66,8 @@ export class PluginDebugSessionFactory extends DefaultDebugSessionFactory { protected readonly outputChannelManager: OutputChannelManager, protected readonly debugPreferences: DebugPreferences, protected readonly connectionFactory: (sessionId: string) => Promise, - protected readonly fileSystem: FileSystem + protected readonly fileSystem: FileSystem, + protected readonly terminalOptionsExt: TerminalOptionsExt | undefined ) { super(); } @@ -53,7 +78,7 @@ export class PluginDebugSessionFactory extends DefaultDebugSessionFactory { this.connectionFactory, this.getTraceOutputChannel()); - return new DebugSession( + return new PluginDebugSession( sessionId, options, connection, @@ -62,6 +87,7 @@ export class PluginDebugSessionFactory extends DefaultDebugSessionFactory { this.breakpoints, this.labelProvider, this.messages, - this.fileSystem); + this.fileSystem, + this.terminalOptionsExt); } } diff --git a/packages/plugin-ext/src/main/browser/editors-and-documents-main.ts b/packages/plugin-ext/src/main/browser/editors-and-documents-main.ts index ec7b87cc54f89..b089362763497 100644 --- a/packages/plugin-ext/src/main/browser/editors-and-documents-main.ts +++ b/packages/plugin-ext/src/main/browser/editors-and-documents-main.ts @@ -30,6 +30,7 @@ import { TextEditorsMainImpl } from './text-editors-main'; import { EditorManager } from '@theia/editor/lib/browser'; import { OpenerService } from '@theia/core/lib/browser/opener-service'; import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service'; +import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; export class EditorsAndDocumentsMain { private toDispose = new DisposableCollection(); @@ -57,11 +58,12 @@ export class EditorsAndDocumentsMain { const editorManager = container.get(EditorManager); const openerService = container.get(OpenerService); const bulkEditService = container.get(MonacoBulkEditService); + const monacoEditorService = container.get(MonacoEditorService); const documentsMain = new DocumentsMainImpl(this, this.modelService, rpc, editorManager, openerService); rpc.set(PLUGIN_RPC_CONTEXT.DOCUMENTS_MAIN, documentsMain); - const editorsMain = new TextEditorsMainImpl(this, rpc, bulkEditService); + const editorsMain = new TextEditorsMainImpl(this, rpc, bulkEditService, monacoEditorService); rpc.set(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN, editorsMain); this.stateComputer = new EditorAndDocumentStateComputer(d => this.onDelta(d), editorService, this.modelService); diff --git a/packages/plugin-ext/src/main/browser/languages-main.ts b/packages/plugin-ext/src/main/browser/languages-main.ts index 75127997662c9..f150dce0996b8 100644 --- a/packages/plugin-ext/src/main/browser/languages-main.ts +++ b/packages/plugin-ext/src/main/browser/languages-main.ts @@ -112,7 +112,7 @@ export class LanguagesMainImpl implements LanguagesMain { this.disposables.set(handle, disposable); } - $registeReferenceProvider(handle: number, selector: SerializedDocumentFilter[]): void { + $registerReferenceProvider(handle: number, selector: SerializedDocumentFilter[]): void { const languageSelector = fromLanguageSelector(selector); const referenceProvider = this.createReferenceProvider(handle, languageSelector); const disposable = new DisposableCollection(); diff --git a/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts b/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts index 9a91a2a328622..c296a3cdde16b 100644 --- a/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts +++ b/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts @@ -27,6 +27,7 @@ import { VIEW_ITEM_CONTEXT_MENU } from '../view/tree-views-main'; import { PluginContribution, Menu } from '../../../common'; import { DebugStackFramesWidget } from '@theia/debug/lib/browser/view/debug-stack-frames-widget'; import { DebugThreadsWidget } from '@theia/debug/lib/browser/view/debug-threads-widget'; +import { MetadataSelection } from '../metadata-selection'; @injectable() export class MenusContributionPointHandler { @@ -115,6 +116,12 @@ export class MenusContributionPointHandler { const command: Command = { id: commandId }; const selectedResource = () => { const selection = this.selectionService.selection; + + const metadata = MetadataSelection.getMetadata(selection); + if (metadata) { + return metadata; + } + const uri = UriSelection.getUri(selection); return uri ? uri['codeUri'] : (typeof selection !== 'object' && typeof selection !== 'function') ? selection : undefined; }; diff --git a/packages/plugin-ext/src/main/browser/metadata-selection.ts b/packages/plugin-ext/src/main/browser/metadata-selection.ts new file mode 100644 index 0000000000000..45ae12a7e89bb --- /dev/null +++ b/packages/plugin-ext/src/main/browser/metadata-selection.ts @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. 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 + ********************************************************************************/ + +export interface MetadataSelection { + // tslint:disable-next-line:no-any + readonly metadata: any +} + +export namespace MetadataSelection { + + export function is(arg: Object | undefined): arg is MetadataSelection { + // tslint:disable-next-line:no-any + return typeof arg === 'object' && ('metadata' in arg); + } + + // tslint:disable-next-line:no-any + export function getMetadata(selection: Object | undefined): any | undefined { + if (is(selection)) { + return selection.metadata; + } + if (Array.isArray(selection) && is(selection[0])) { + return selection[0].metadata; + } + return undefined; + } + +} diff --git a/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts b/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts index b8a2a272e1fe6..22f1eaa396261 100644 --- a/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts +++ b/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts @@ -87,7 +87,7 @@ export class DeployQuickOpenItem extends QuickOpenItem { protected readonly pluginServer: PluginServer, protected readonly hostedPluginSupport: HostedPluginSupport, protected readonly pluginWidget: PluginWidget, - protected readonly description?: string, + protected readonly description?: string ) { super(); } diff --git a/packages/plugin-ext/src/main/browser/plugin-ext-widget.tsx b/packages/plugin-ext/src/main/browser/plugin-ext-widget.tsx index 980f2de9fd4ed..76945cc5ebb80 100644 --- a/packages/plugin-ext/src/main/browser/plugin-ext-widget.tsx +++ b/packages/plugin-ext/src/main/browser/plugin-ext-widget.tsx @@ -40,6 +40,7 @@ export class PluginWidget extends ReactWidget { this.title.label = 'Plugins'; this.title.caption = 'Plugins'; this.title.iconClass = 'fa plugins-tab-icon'; + this.title.closable = true; this.addClass('theia-plugins'); this.update(); diff --git a/packages/plugin-ext/src/main/browser/plugin-frontend-view-contribution.ts b/packages/plugin-ext/src/main/browser/plugin-frontend-view-contribution.ts index 21ea9429f3bbc..1153427bebc5b 100644 --- a/packages/plugin-ext/src/main/browser/plugin-frontend-view-contribution.ts +++ b/packages/plugin-ext/src/main/browser/plugin-frontend-view-contribution.ts @@ -29,7 +29,7 @@ export class PluginFrontendViewContribution extends AbstractViewContribution(); constructor(private readonly editorsAndDocuments: EditorsAndDocumentsMain, - rpc: RPCProtocol, - private readonly bulkEditService: MonacoBulkEditService) { + rpc: RPCProtocol, + private readonly bulkEditService: MonacoBulkEditService, + private readonly monacoEditorService: MonacoEditorService) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.TEXT_EDITORS_EXT); this.toDispose.push(editorsAndDocuments.onTextEditorAdd(editors => editors.forEach(this.onTextEditorAdd, this))); this.toDispose.push(editorsAndDocuments.onTextEditorRemove(editors => editors.forEach(this.onTextEditorRemove, this))); @@ -108,9 +110,9 @@ export class TextEditorsMainImpl implements TextEditorsMain { } $tryApplyWorkspaceEdit(dto: WorkspaceEditDto): Promise { - const edits = reviveWorkspaceEditDto(dto); + const edits = reviveWorkspaceEditDto(dto); return new Promise(resolve => { - this.bulkEditService.apply( edits).then(() => resolve(true), err => resolve(false)); + this.bulkEditService.apply(edits).then(() => resolve(true), err => resolve(false)); }); } @@ -122,11 +124,11 @@ export class TextEditorsMainImpl implements TextEditorsMain { } $registerTextEditorDecorationType(key: string, options: DecorationRenderOptions): void { - monaco.services.StaticServices.codeEditorService.get().registerDecorationType(key, options); + this.monacoEditorService.registerDecorationType(key, options); } $removeTextEditorDecorationType(key: string): void { - monaco.services.StaticServices.codeEditorService.get().removeDecorationType(key); + this.monacoEditorService.removeDecorationType(key); } $trySetDecorations(id: string, key: string, ranges: DecorationOptions[]): Promise { diff --git a/packages/plugin-ext/src/main/browser/view/tree-views-main.tsx b/packages/plugin-ext/src/main/browser/view/tree-views-main.tsx index 8b6d4b43ca894..a977801f2cc83 100644 --- a/packages/plugin-ext/src/main/browser/view/tree-views-main.tsx +++ b/packages/plugin-ext/src/main/browser/view/tree-views-main.tsx @@ -40,6 +40,7 @@ import { MenuPath } from '@theia/core/lib/common/menu'; import * as ReactDOM from 'react-dom'; import * as React from 'react'; import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service'; +import { SelectionService } from '@theia/core/lib/common'; export const TREE_NODE_HYPERLINK = 'theia-TreeNodeHyperlink'; export const VIEW_ITEM_CONTEXT_MENU: MenuPath = ['view-item-context-menu']; @@ -104,7 +105,8 @@ export class TreeViewsMainImpl implements TreeViewsMain { createTreeViewContainer(dataProvider: TreeViewDataProviderMain): Container { const child = createTreeContainer(this.container, { - contextMenuPath: VIEW_ITEM_CONTEXT_MENU + contextMenuPath: VIEW_ITEM_CONTEXT_MENU, + globalSelection: true }); child.bind(TreeViewDataProviderMain).toConstantValue(dataProvider); @@ -140,10 +142,15 @@ export class TreeViewsMainImpl implements TreeViewsMain { } -export interface TreeViewFolderNode extends SelectableTreeNode, ExpandableTreeNode, CompositeTreeNode { +export interface DescriptiveMetadata { + // tslint:disable-next-line:no-any + readonly metadata?: any +} + +export interface TreeViewFolderNode extends SelectableTreeNode, ExpandableTreeNode, CompositeTreeNode, DescriptiveMetadata { } -export interface TreeViewFileNode extends SelectableTreeNode { +export interface TreeViewFileNode extends SelectableTreeNode, DescriptiveMetadata { } export class TreeViewDataProviderMain { @@ -166,7 +173,8 @@ export class TreeViewDataProviderMain { visible: true, selected: false, expanded, - children: [] + children: [], + metadata: item.metadata }; } @@ -179,6 +187,7 @@ export class TreeViewDataProviderMain { parent: undefined, visible: true, selected: false, + metadata: item.metadata }; } @@ -218,8 +227,8 @@ export class TreeViewWidget extends TreeWidget { @inject(TreeProps) readonly treeProps: TreeProps, @inject(TreeModel) readonly model: TreeModel, @inject(ContextMenuRenderer) readonly contextMenuRenderer: ContextMenuRenderer, - @inject(TreeViewDataProviderMain) readonly dataProvider: TreeViewDataProviderMain) { - + @inject(TreeViewDataProviderMain) readonly dataProvider: TreeViewDataProviderMain, + @inject(SelectionService) readonly selectionService: SelectionService) { super(treeProps, model, contextMenuRenderer); } diff --git a/packages/plugin-ext/src/main/browser/view/view-registry.ts b/packages/plugin-ext/src/main/browser/view/view-registry.ts index 8821a3750faab..357b00d5f1dc1 100644 --- a/packages/plugin-ext/src/main/browser/view/view-registry.ts +++ b/packages/plugin-ext/src/main/browser/view/view-registry.ts @@ -23,6 +23,7 @@ import { } from '@theia/core/lib/browser/frontend-application-state'; import { ViewsContainerWidget } from './views-container-widget'; import { TreeViewWidget } from './tree-views-main'; +import { PluginSharedStyle } from '../plugin-shared-style'; const READY: FrontendApplicationState = 'ready'; const DEFAULT_LOCATION: ApplicationShell.Area = 'left'; @@ -36,6 +37,9 @@ export class ViewRegistry { @inject(FrontendApplicationStateService) protected applicationStateService: FrontendApplicationStateService; + @inject(PluginSharedStyle) + protected readonly style: PluginSharedStyle; + private treeViewWidgets: Map = new Map(); private containerWidgets: Map = new Map(); private updateContainerOnApplicationReady: Promise; @@ -49,7 +53,14 @@ export class ViewRegistry { if (this.containerWidgets.has(viewsContainer.id)) { return; } + const iconClass = 'plugin-view-container-icon-' + viewsContainer.id; + this.style.insertRule('.' + iconClass, () => ` + mask: : url('${viewsContainer.iconUrl}') no-repeat 50% 50%; + -webkit-mask: url('${viewsContainer.iconUrl}') no-repeat 50% 50%; + `); + const containerWidget = new ViewsContainerWidget(viewsContainer, containerViews); + containerWidget.title.iconClass = iconClass; this.containerWidgets.set(viewsContainer.id, containerWidget); // add to the promise chain diff --git a/packages/plugin-ext/src/main/browser/view/views-container-widget.ts b/packages/plugin-ext/src/main/browser/view/views-container-widget.ts index 153a33a978757..73f634a2390a8 100644 --- a/packages/plugin-ext/src/main/browser/view/views-container-widget.ts +++ b/packages/plugin-ext/src/main/browser/view/views-container-widget.ts @@ -31,8 +31,6 @@ export class ViewsContainerWidget extends Widget { private sections: Map = new Map(); private childrenId: string[] = []; - sectionTitle: HTMLElement; - constructor(protected viewContainer: ViewContainer, protected views: View[]) { super(); @@ -42,11 +40,6 @@ export class ViewsContainerWidget extends Widget { this.addClass('theia-views-container'); - // create container title - this.sectionTitle = createElement('theia-views-container-title'); - this.sectionTitle.innerText = viewContainer.title; - this.node.appendChild(this.sectionTitle); - views.forEach((view: View) => { if (this.hasView(view.id)) { return; @@ -80,7 +73,6 @@ export class ViewsContainerWidget extends Widget { public updateDimensions() { let visibleSections = 0; let availableHeight = this.node.offsetHeight; - availableHeight -= this.sectionTitle.offsetHeight; // Determine available space for sections and how much sections are opened this.sections.forEach((section: ViewContainerSection) => { availableHeight -= section.header.offsetHeight; diff --git a/packages/plugin-ext/src/main/browser/webview/webview.ts b/packages/plugin-ext/src/main/browser/webview/webview.ts index 96f653dbd50fa..c40334db4121c 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview.ts @@ -13,7 +13,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { BaseWidget } from '@theia/core/lib/browser/widgets/widget'; +import { BaseWidget, Message } from '@theia/core/lib/browser/widgets/widget'; import { IdGenerator } from '../../../common/id-generator'; import { Disposable, DisposableCollection } from '@theia/core'; @@ -38,6 +38,7 @@ export class WebviewWidget extends BaseWidget { constructor(title: string, private options: WebviewWidgetOptions, private eventDelegate: WebviewEvents) { super(); + this.node.tabIndex = 0; this.id = WebviewWidget.ID.nextId(); this.title.closable = true; this.title.label = title; @@ -132,15 +133,16 @@ export class WebviewWidget extends BaseWidget { this.loadTimeout = undefined; onLoad(e.target, newFrame.contentWindow); } - }); + }, { once: true }); newFrame.contentDocument!.write(newDocument!.documentElement!.innerHTML); newFrame.contentDocument!.close(); this.updateSandboxAttribute(newFrame); } - focus() { - this.iframe.contentWindow!.focus(); + protected onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + this.node.focus(); } private reloadFrame() { diff --git a/packages/plugin-ext/src/main/browser/webviews-main.ts b/packages/plugin-ext/src/main/browser/webviews-main.ts index 0140e77ac3bf6..2db2002c573aa 100644 --- a/packages/plugin-ext/src/main/browser/webviews-main.ts +++ b/packages/plugin-ext/src/main/browser/webviews-main.ts @@ -85,10 +85,19 @@ export class WebviewsMainImpl implements WebviewsMain { this.onCloseView(viewId); }); this.views.set(viewId, view); - this.shell.addWidget(view, { area: showOptions.area ? showOptions.area : 'main' }); - this.shell.activateWidget(view.id); + const widgetOptions: ApplicationShell.WidgetOptions = { area: showOptions.area ? showOptions.area : 'main' }; + // FIXME translate all view columns properly + if (showOptions.viewColumn === -2) { + const ref = this.shell.currentWidget; + if (ref && this.shell.getAreaFor(ref) === widgetOptions.area) { + Object.assign(widgetOptions, { ref, mode: 'open-to-right' }); + } + } + this.shell.addWidget(view, widgetOptions); if (showOptions.preserveFocus) { - view.focus(); + this.shell.revealWidget(view.id); + } else { + this.shell.activateWidget(view.id); } } $disposeWebview(handle: string): void { @@ -98,7 +107,16 @@ export class WebviewsMainImpl implements WebviewsMain { } } $reveal(handle: string, showOptions: WebviewPanelShowOptions): void { - throw new Error('Method not implemented.'); + const webview = this.getWebview(handle); + if (webview.isDisposed) { + return; + } + // FIXME handle view column here too! + if (showOptions.preserveFocus) { + this.shell.revealWidget(webview.id); + } else { + this.shell.activateWidget(webview.id); + } } $setTitle(handle: string, value: string): void { const webview = this.getWebview(handle); diff --git a/packages/plugin-ext/src/main/browser/workspace-main.ts b/packages/plugin-ext/src/main/browser/workspace-main.ts index ed18eac945715..54a045dfa6111 100644 --- a/packages/plugin-ext/src/main/browser/workspace-main.ts +++ b/packages/plugin-ext/src/main/browser/workspace-main.ts @@ -32,6 +32,7 @@ import { FileWatcherSubscriberOptions } from '../../api/model'; import { InPluginFileSystemWatcherManager } from './in-plugin-filesystem-watcher-manager'; import { StoragePathService } from './storage-path-service'; import { PluginServer } from '../../common/plugin-protocol'; +import { FileSystemPreferences } from '@theia/filesystem/lib/browser'; export class WorkspaceMainImpl implements WorkspaceMain { @@ -55,6 +56,8 @@ export class WorkspaceMainImpl implements WorkspaceMain { private storagePathService: StoragePathService; + private fsPreferences: FileSystemPreferences; + constructor(rpc: RPCProtocol, container: interfaces.Container) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WORKSPACE_EXT); this.storageProxy = rpc.getProxy(MAIN_RPC_CONTEXT.STORAGE_EXT); @@ -64,6 +67,7 @@ export class WorkspaceMainImpl implements WorkspaceMain { this.pluginServer = container.get(PluginServer); this.workspaceService = container.get(WorkspaceService); this.storagePathService = container.get(StoragePathService); + this.fsPreferences = container.get(FileSystemPreferences); this.inPluginFileSystemWatcherManager = new InPluginFileSystemWatcherManager(this.proxy, container); @@ -154,16 +158,31 @@ export class WorkspaceMainImpl implements WorkspaceMain { async $startFileSearch(includePattern: string, includeFolderUri: string | undefined, excludePatternOrDisregardExcludes?: string | false, maxResults?: number): Promise { + const roots: FileSearchService.RootOptions = {}; const rootUris = includeFolderUri ? [includeFolderUri] : this.roots.map(r => r.uri); - const opts: FileSearchService.Options = { rootUris }; + for (const rootUri of rootUris) { + roots[rootUri] = {}; + } + const opts: FileSearchService.Options = { rootOptions: roots }; if (includePattern) { opts.includePatterns = [includePattern]; } if (typeof excludePatternOrDisregardExcludes === 'string') { - if (excludePatternOrDisregardExcludes === '') { // default excludes - opts.defaultIgnorePatterns = []; - } else { - opts.defaultIgnorePatterns = [excludePatternOrDisregardExcludes]; + opts.excludePatterns = [excludePatternOrDisregardExcludes]; + } + if (excludePatternOrDisregardExcludes !== false) { + for (const rootUri of rootUris) { + const filesExclude = this.fsPreferences.get('files.exclude', undefined, rootUri); + if (filesExclude) { + for (const excludePattern in filesExclude) { + if (filesExclude[excludePattern]) { + const rootOptions = roots[rootUri]; + const rootExcludePatterns = rootOptions.excludePatterns || []; + rootExcludePatterns.push(excludePattern); + rootOptions.excludePatterns = rootExcludePatterns; + } + } + } } } if (typeof maxResults === 'number') { @@ -194,6 +213,10 @@ export class WorkspaceMainImpl implements WorkspaceMain { this.resourceResolver.onContentChange(uri, content); } + async $updateWorkspaceFolders(start: number, deleteCount?: number, ...rootsToAdd: string[]): Promise { + await this.workspaceService.spliceRoots(start, deleteCount, ...rootsToAdd.map(root => new URI(root))); + } + } /** @@ -289,7 +312,7 @@ export class TextContentResource implements Resource { } } - return Promise.reject(`Unable to get content for '${this.uri.toString()}'`); + return Promise.reject(new Error(`Unable to get content for '${this.uri.toString()}'`)); } dispose() { diff --git a/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts b/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts index 250dd33bff65e..04db06161a7a5 100644 --- a/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts +++ b/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts @@ -37,7 +37,7 @@ export class PluginDeployerImpl implements PluginDeployer { protected readonly logger: ILogger; @inject(PluginDeployerHandler) - protected readonly hostedPluginServer: PluginDeployerHandler; + protected readonly pluginDeployerHandler: PluginDeployerHandler; @inject(PluginCliContribution) protected readonly cliContribution: PluginCliContribution; @@ -112,9 +112,7 @@ export class PluginDeployerImpl implements PluginDeployer { } public async deploy(pluginEntry: string): Promise { - const entries: string[] = []; - entries.push(pluginEntry); - await this.deployMultipleEntries(entries); + await this.deployMultipleEntries([pluginEntry]); return Promise.resolve(); } @@ -156,8 +154,8 @@ export class PluginDeployerImpl implements PluginDeployer { await Promise.all([ // start the backend plugins - this.hostedPluginServer.deployBackendPlugins(acceptedBackendPlugins), - this.hostedPluginServer.deployFrontendPlugins(acceptedFrontendPlugins) + this.pluginDeployerHandler.deployBackendPlugins(acceptedBackendPlugins), + this.pluginDeployerHandler.deployFrontendPlugins(acceptedFrontendPlugins) ]); } diff --git a/packages/plugin-ext/src/main/node/plugin-github-resolver.ts b/packages/plugin-ext/src/main/node/plugin-github-resolver.ts index 86308998220f2..4a4a7e47f8360 100644 --- a/packages/plugin-ext/src/main/node/plugin-github-resolver.ts +++ b/packages/plugin-ext/src/main/node/plugin-github-resolver.ts @@ -53,7 +53,7 @@ export class GithubPluginDeployerResolver implements PluginDeployerResolver { const extracted = /^github:(.*)\/(.*)\/(.*)$/gm.exec(pluginResolverContext.getOriginId()); if (!extracted || extracted === null || extracted.length !== 4) { - reject('Invalid extension' + pluginResolverContext.getOriginId()); + reject(new Error('Invalid extension' + pluginResolverContext.getOriginId())); return; } @@ -87,14 +87,14 @@ export class GithubPluginDeployerResolver implements PluginDeployerResolver { if (response.statusCode === 302) { const redirectLocation = response.headers.location; if (!redirectLocation) { - reject('Invalid github link with latest not being found'); + reject(new Error('Invalid github link with latest not being found')); return; } // parse redirect link const taggedValueArray = /^https:\/\/.*tag\/(.*)/gm.exec(redirectLocation); if (!taggedValueArray || taggedValueArray.length !== 2) { - reject('The redirect link for latest is invalid ' + redirectLocation); + reject(new Error('The redirect link for latest is invalid ' + redirectLocation)); return; } diff --git a/packages/plugin-ext/src/main/node/plugin-http-resolver.ts b/packages/plugin-ext/src/main/node/plugin-http-resolver.ts index 227ebfbaa1d8f..4fe923ef3560e 100644 --- a/packages/plugin-ext/src/main/node/plugin-http-resolver.ts +++ b/packages/plugin-ext/src/main/node/plugin-http-resolver.ts @@ -52,7 +52,7 @@ export class HttpPluginDeployerResolver implements PluginDeployerResolver { const urlPath = pluginResolverContext.getOriginId(); const link = url.parse(urlPath); if (!link.pathname) { - reject('invalid link URI' + urlPath); + reject(new Error('invalid link URI' + urlPath)); return; } diff --git a/packages/plugin-ext/src/plugin/command-registry.ts b/packages/plugin-ext/src/plugin/command-registry.ts index 2ef2b2428687f..8467f4883e2bb 100644 --- a/packages/plugin-ext/src/plugin/command-registry.ts +++ b/packages/plugin-ext/src/plugin/command-registry.ts @@ -75,7 +75,7 @@ export class CommandRegistryImpl implements CommandRegistryExt { if (this.handlers.has(id)) { return this.executeLocalCommand(id, ...args); } else { - return Promise.reject(`Command: ${id} does not exist.`); + return Promise.reject(new Error(`Command: ${id} does not exist.`)); } } diff --git a/packages/plugin-ext/src/plugin/dialogs.ts b/packages/plugin-ext/src/plugin/dialogs.ts index 066858b06bcd7..68ca3a406886d 100644 --- a/packages/plugin-ext/src/plugin/dialogs.ts +++ b/packages/plugin-ext/src/plugin/dialogs.ts @@ -29,8 +29,8 @@ export class DialogsExtImpl { const optionsMain = { openLabel: options.openLabel, defaultUri: options.defaultUri ? options.defaultUri.path : undefined, - canSelectFiles: options.canSelectFiles, - canSelectFolders: options.canSelectFolders, + canSelectFiles: options.canSelectFiles ? options.canSelectFiles : true, + canSelectFolders: options.canSelectFolders ? options.canSelectFolders : false, canSelectMany: options.canSelectMany, filters: options.filters } as OpenDialogOptionsMain; diff --git a/packages/plugin-ext/src/plugin/languages.ts b/packages/plugin-ext/src/plugin/languages.ts index 79dc178d99112..6e650d8c81bb6 100644 --- a/packages/plugin-ext/src/plugin/languages.ts +++ b/packages/plugin-ext/src/plugin/languages.ts @@ -429,7 +429,7 @@ export class LanguagesExtImpl implements LanguagesExt { registerReferenceProvider(selector: theia.DocumentSelector, provider: theia.ReferenceProvider): theia.Disposable { const callId = this.addNewAdapter(new ReferenceAdapter(provider, this.documents)); - this.proxy.$registeReferenceProvider(callId, this.transformDocumentSelector(selector)); + this.proxy.$registerReferenceProvider(callId, this.transformDocumentSelector(selector)); return this.createDisposable(callId); } // ### Code Reference Provider end diff --git a/packages/plugin-ext/src/plugin/node/debug/debug.ts b/packages/plugin-ext/src/plugin/node/debug/debug.ts index 40fcfd07543df..3cf349b63d2db 100644 --- a/packages/plugin-ext/src/plugin/node/debug/debug.ts +++ b/packages/plugin-ext/src/plugin/node/debug/debug.ts @@ -20,7 +20,8 @@ import { RPCProtocol } from '../../../api/rpc-protocol'; import { PLUGIN_RPC_CONTEXT as Ext, DebugMain, - DebugExt + DebugExt, + TerminalOptionsExt } from '../../../api/plugin-api'; import * as theia from '@theia/plugin'; import uuid = require('uuid'); @@ -227,6 +228,14 @@ export class DebugExtImpl implements DebugExt { return contribution && contribution.configurationSnippets || []; } + async $getTerminalCreationOptions(debugType: string): Promise { + return this.doGetTerminalCreationOptions(debugType); + } + + async doGetTerminalCreationOptions(debugType: string): Promise { + return undefined; + } + async $provideDebugConfigurations(debugType: string, workspaceFolderUri: string | undefined): Promise { let result: theia.DebugConfiguration[] = []; diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 3097fc37110fd..bed7853400fd9 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -131,13 +131,13 @@ export function createAPIFactory( debugExt: DebugExtImpl, preferenceRegistryExt: PreferenceRegistryExtImpl, editorsAndDocumentsExt: EditorsAndDocumentsExtImpl, - workspaceExt: WorkspaceExtImpl + workspaceExt: WorkspaceExtImpl, + messageRegistryExt: MessageRegistryExt ): PluginAPIFactory { const commandRegistry = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc)); const quickOpenExt = rpc.set(MAIN_RPC_CONTEXT.QUICK_OPEN_EXT, new QuickOpenExtImpl(rpc)); const dialogsExt = new DialogsExtImpl(rpc); - const messageRegistryExt = new MessageRegistryExt(rpc); const windowStateExt = rpc.set(MAIN_RPC_CONTEXT.WINDOW_STATE_EXT, new WindowStateExtImpl()); const notificationExt = rpc.set(MAIN_RPC_CONTEXT.NOTIFICATION_EXT, new NotificationExtImpl(rpc)); const statusBarExt = new StatusBarExtImpl(rpc); @@ -396,7 +396,7 @@ export function createAPIFactory( uri = await documents.createDocumentData(options); } else { - return Promise.reject('illegal argument - uriOrFileNameOrOptions'); + return Promise.reject(new Error('illegal argument - uriOrFileNameOrOptions')); } const data = await documents.openDocument(uri); @@ -429,6 +429,9 @@ export function createAPIFactory( asRelativePath(pathOrUri: theia.Uri | string, includeWorkspace?: boolean): string | undefined { return workspaceExt.getRelativePath(pathOrUri, includeWorkspace); }, + updateWorkspaceFolders: (index, deleteCount, ...workspaceFoldersToAdd) => + workspaceExt.updateWorkspaceFolders(index, deleteCount || 0, ...workspaceFoldersToAdd) + , registerTaskProvider(type: string, provider: theia.TaskProvider): theia.Disposable { return tasks.registerTaskProvider(type, provider); }, diff --git a/packages/plugin-ext/src/plugin/preference-registry.ts b/packages/plugin-ext/src/plugin/preference-registry.ts index fa74a71b15418..0e4385f51ef7f 100644 --- a/packages/plugin-ext/src/plugin/preference-registry.ts +++ b/packages/plugin-ext/src/plugin/preference-registry.ts @@ -80,6 +80,7 @@ export class PreferenceRegistryExtImpl implements PreferenceRegistryExt { } init(data: PreferenceData): void { + data[PreferenceScope.Default]['files.associations'] = {}; this._preferences = this.parse(data); } diff --git a/packages/plugin-ext/src/plugin/tasks/task-provider.ts b/packages/plugin-ext/src/plugin/tasks/task-provider.ts index 5e56fd8705fb0..3d5d81cb473e2 100644 --- a/packages/plugin-ext/src/plugin/tasks/task-provider.ts +++ b/packages/plugin-ext/src/plugin/tasks/task-provider.ts @@ -52,7 +52,8 @@ export class TaskProviderAdapter { return Promise.resolve(undefined); } const id = ObjectIdentifier.of(task); - const item = this.cache.get(id); + const cached = this.cache.get(id); + const item = cached ? cached : Converter.toTask(task); if (!item) { return Promise.resolve(undefined); } diff --git a/packages/plugin-ext/src/plugin/tree/tree-views.ts b/packages/plugin-ext/src/plugin/tree/tree-views.ts index fb539aa0314f8..bbe8681086626 100644 --- a/packages/plugin-ext/src/plugin/tree/tree-views.ts +++ b/packages/plugin-ext/src/plugin/tree/tree-views.ts @@ -215,7 +215,8 @@ class TreeViewExtImpl extends Disposable { label: label, icon, tooltip: treeItem.tooltip, - collapsibleState: treeItem.collapsibleState + collapsibleState: treeItem.collapsibleState, + metadata: value } as TreeViewItem; treeItems.push(treeViewItem); diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 3f34d573258a4..2191a1d3fecfd 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -950,6 +950,10 @@ export class CodeActionKind { public contains(other: CodeActionKind): boolean { return this.value === other.value || startsWithIgnoreCase(other.value, this.value + CodeActionKind.sep); } + + public intersects(other: CodeActionKind): boolean { + return this.contains(other) || other.contains(this); + } } export enum TextDocumentSaveReason { @@ -1679,17 +1683,16 @@ export class Task { } private updateDefinitionBasedOnExecution(): void { - this.taskDefinition = undefined; if (this.taskExecution instanceof ProcessExecution) { - this.taskDefinition = { + Object.assign(this.taskDefinition, { type: 'process', id: this.taskExecution.computeId() - }; + }); } else if (this.taskExecution instanceof ShellExecution) { - this.taskDefinition = { + Object.assign(this.taskDefinition, { type: 'shell', id: this.taskExecution.computeId() - }; + }); } } } diff --git a/packages/plugin-ext/src/plugin/webviews.ts b/packages/plugin-ext/src/plugin/webviews.ts index 2b4da44fb2f0f..3d5ff196daf71 100644 --- a/packages/plugin-ext/src/plugin/webviews.ts +++ b/packages/plugin-ext/src/plugin/webviews.ts @@ -298,12 +298,28 @@ export class WebviewPanelImpl implements theia.WebviewPanel { this._visible = value; } - reveal(area?: WebviewPanelTargetArea, viewColumn?: theia.ViewColumn, preserveFocus?: boolean): void { + reveal(arg0?: theia.ViewColumn | WebviewPanelTargetArea, arg1?: theia.ViewColumn | boolean, arg2?: boolean): void { + let area: WebviewPanelTargetArea | undefined = undefined; + let viewColumn: theia.ViewColumn | undefined = undefined; + let preserveFocus: boolean | undefined = undefined; + if (typeof arg0 === 'number') { + viewColumn = arg0; + } else { + area = arg0; + } + if (typeof arg1 === 'number') { + viewColumn = arg1; + } else { + preserveFocus = arg1; + } + if (typeof arg2 === 'boolean') { + preserveFocus = arg2; + } this.checkIsDisposed(); this.proxy.$reveal(this.viewId, { - area: area, + area, viewColumn: viewColumn ? fromViewColumn(viewColumn) : undefined, - preserveFocus: !!preserveFocus + preserveFocus }); } diff --git a/packages/plugin-ext/src/plugin/workspace.ts b/packages/plugin-ext/src/plugin/workspace.ts index 3d929b60a8e0a..6a8ee119ce613 100644 --- a/packages/plugin-ext/src/plugin/workspace.ts +++ b/packages/plugin-ext/src/plugin/workspace.ts @@ -13,6 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// copied and modified from https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/services/workspace/node/workspaceEditingService.ts import * as paths from 'path'; import * as theia from '@theia/plugin'; @@ -22,7 +27,8 @@ import { WorkspaceExt, WorkspaceFolderPickOptionsMain, WorkspaceMain, - PLUGIN_RPC_CONTEXT as Ext + PLUGIN_RPC_CONTEXT as Ext, + MainMessageType } from '../api/plugin-api'; import { Path } from '@theia/core/lib/common/path'; import { RPCProtocol } from '../api/rpc-protocol'; @@ -35,6 +41,7 @@ import { normalize } from '../common/paths'; import { relative } from '../common/paths-util'; import { Schemes } from '../common/uri-components'; import { toWorkspaceFolder } from './type-converters'; +import { MessageRegistryExt } from './message-registry'; export class WorkspaceExtImpl implements WorkspaceExt { @@ -47,7 +54,9 @@ export class WorkspaceExtImpl implements WorkspaceExt { private folders: theia.WorkspaceFolder[] | undefined; private documentContentProviders = new Map(); - constructor(rpc: RPCProtocol, private editorsAndDocuments: EditorsAndDocumentsExtImpl) { + constructor(rpc: RPCProtocol, + private editorsAndDocuments: EditorsAndDocumentsExtImpl, + private messageService: MessageRegistryExt) { this.proxy = rpc.getProxy(Ext.WORKSPACE_MAIN); this.fileSystemWatcherManager = new InPluginFileSystemWatcherProxy(this.proxy); } @@ -72,15 +81,20 @@ export class WorkspaceExtImpl implements WorkspaceExt { $onWorkspaceFoldersChanged(event: WorkspaceRootsChangeEvent): void { const newRoots = event.roots || []; const newFolders = newRoots.map((root, index) => this.toWorkspaceFolder(root, index)); - const added = this.foldersDiff(newFolders, this.folders); - const removed = this.foldersDiff(this.folders, newFolders); + const delta = this.deltaFolders(this.folders, newFolders); this.folders = newFolders; - this.workspaceFoldersChangedEmitter.fire({ - added: added, - removed: removed - }); + this.workspaceFoldersChangedEmitter.fire(delta); + } + + private deltaFolders(currentFolders: theia.WorkspaceFolder[] = [], newFolders: theia.WorkspaceFolder[] = []): { + added: theia.WorkspaceFolder[] + removed: theia.WorkspaceFolder[] + } { + const added = this.foldersDiff(newFolders, currentFolders); + const removed = this.foldersDiff(currentFolders, newFolders); + return { added, removed }; } private foldersDiff(folder1: theia.WorkspaceFolder[] = [], folder2: theia.WorkspaceFolder[] = []): theia.WorkspaceFolder[] { @@ -282,6 +296,54 @@ export class WorkspaceExtImpl implements WorkspaceExt { return normalize(result, true); } + updateWorkspaceFolders(start: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: theia.Uri, name?: string }[]): boolean { + const rootsToAdd = new Set(); + if (Array.isArray(workspaceFoldersToAdd)) { + workspaceFoldersToAdd.forEach(folderToAdd => { + const uri = URI.isUri(folderToAdd.uri) && folderToAdd.uri.toString(); + if (uri && !rootsToAdd.has(uri)) { + rootsToAdd.add(uri); + } + }); + } + + if ([start, deleteCount].some(i => typeof i !== 'number' || i < 0)) { + return false; // validate numbers + } + + if (deleteCount === 0 && rootsToAdd.size === 0) { + return false; // nothing to delete or add + } + + const currentWorkspaceFolders = this.workspaceFolders || []; + if (start + deleteCount > currentWorkspaceFolders.length) { + return false; // cannot delete more than we have + } + + // Simulate the updateWorkspaceFolders method on our data to do more validation + const newWorkspaceFolders = currentWorkspaceFolders.slice(0); + newWorkspaceFolders.splice(start, deleteCount, ...[...rootsToAdd].map(uri => ({ uri: URI.parse(uri), name: undefined!, index: undefined! }))); + + for (let i = 0; i < newWorkspaceFolders.length; i++) { + const folder = newWorkspaceFolders[i]; + if (newWorkspaceFolders.some((otherFolder, index) => index !== i && folder.uri.toString() === otherFolder.uri.toString())) { + return false; // cannot add the same folder multiple times + } + } + + const { added, removed } = this.deltaFolders(currentWorkspaceFolders, newWorkspaceFolders); + if (added.length === 0 && removed.length === 0) { + return false; // nothing actually changed + } + + // Trigger on main side + this.proxy.$updateWorkspaceFolders(start, deleteCount, ...rootsToAdd).then(undefined, error => + this.messageService.showMessage(MainMessageType.Error, `Failed to update workspace folders: ${error}`) + ); + + return true; + } + // Experimental API https://github.com/theia-ide/theia/issues/4167 private workspaceWillRenameFileEmitter = new Emitter(); private workspaceDidRenameFileEmitter = new Emitter(); diff --git a/packages/plugin/package.json b/packages/plugin/package.json index b0666ae2c0082..0437820523f6a 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@theia/plugin", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Plugin API", "types": "./src/theia.d.ts", "publishConfig": { @@ -23,11 +23,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "rimraf docs && typedoc --module commonjs --target es6 --mode file --hideGenerator --name 'Theia Plug-in API' --excludeExternals --includeDeclarations --out ./docs/api src/theia.d.ts" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 8ea86351aa1b6..7820b12c13c42 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -2663,6 +2663,17 @@ declare module '@theia/plugin' { */ readonly onDidDispose: Event; + /** + * Show the webview panel in a given column. + * + * A webview panel may only show in a single column at a time. If it is already showing, this + * method moves it to a new column. + * + * @param viewColumn View column to show the panel in. Shows in the current `viewColumn` if undefined. + * @param preserveFocus When `true`, the webview will not take focus. + */ + reveal(viewColumn?: ViewColumn, preserveFocus?: boolean): void; + /** * Show the webview panel according to a given options. * @@ -3670,48 +3681,48 @@ declare module '@theia/plugin' { export class FileSystemError extends Error { /** - * Create an error to signal that a file or folder wasn't found. - * @param messageOrUri Message or uri. - */ + * Create an error to signal that a file or folder wasn't found. + * @param messageOrUri Message or uri. + */ static FileNotFound(messageOrUri?: string | Uri): FileSystemError; /** - * Create an error to signal that a file or folder already exists, e.g. when - * creating but not overwriting a file. - * @param messageOrUri Message or uri. - */ + * Create an error to signal that a file or folder already exists, e.g. when + * creating but not overwriting a file. + * @param messageOrUri Message or uri. + */ static FileExists(messageOrUri?: string | Uri): FileSystemError; /** - * Create an error to signal that a file is not a folder. - * @param messageOrUri Message or uri. - */ + * Create an error to signal that a file is not a folder. + * @param messageOrUri Message or uri. + */ static FileNotADirectory(messageOrUri?: string | Uri): FileSystemError; /** - * Create an error to signal that a file is a folder. - * @param messageOrUri Message or uri. - */ + * Create an error to signal that a file is a folder. + * @param messageOrUri Message or uri. + */ static FileIsADirectory(messageOrUri?: string | Uri): FileSystemError; /** - * Create an error to signal that an operation lacks required permissions. - * @param messageOrUri Message or uri. - */ + * Create an error to signal that an operation lacks required permissions. + * @param messageOrUri Message or uri. + */ static NoPermissions(messageOrUri?: string | Uri): FileSystemError; /** - * Create an error to signal that the file system is unavailable or too busy to - * complete a request. - * @param messageOrUri Message or uri. - */ + * Create an error to signal that the file system is unavailable or too busy to + * complete a request. + * @param messageOrUri Message or uri. + */ static Unavailable(messageOrUri?: string | Uri): FileSystemError; /** - * Creates a new filesystem error. - * - * @param messageOrUri Message or uri. - */ + * Creates a new filesystem error. + * + * @param messageOrUri Message or uri. + */ constructor(messageOrUri?: string | Uri); } @@ -4147,6 +4158,49 @@ declare module '@theia/plugin' { */ export function asRelativePath(pathOrUri: string | Uri, includeWorkspaceFolder?: boolean): string | undefined; + /** + * This method replaces `deleteCount` [workspace folders](#workspace.workspaceFolders) starting at index `start` + * by an optional set of `workspaceFoldersToAdd` on the `theia.workspace.workspaceFolders` array. This "splice" + * behavior can be used to add, remove and change workspace folders in a single operation. + * + * If the first workspace folder is added, removed or changed, the currently executing extensions (including the + * one that called this method) will be terminated and restarted so that the (deprecated) `rootPath` property is + * updated to point to the first workspace folder. + * + * Use the [`onDidChangeWorkspaceFolders()`](#onDidChangeWorkspaceFolders) event to get notified when the + * workspace folders have been updated. + * + * **Example:** adding a new workspace folder at the end of workspace folders + * ```typescript + * workspace.updateWorkspaceFolders(workspace.workspaceFolders ? workspace.workspaceFolders.length : 0, null, { uri: ...}); + * ``` + * + * **Example:** removing the first workspace folder + * ```typescript + * workspace.updateWorkspaceFolders(0, 1); + * ``` + * + * **Example:** replacing an existing workspace folder with a new one + * ```typescript + * workspace.updateWorkspaceFolders(0, 1, { uri: ...}); + * ``` + * + * It is valid to remove an existing workspace folder and add it again with a different name + * to rename that folder. + * + * **Note:** it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times + * without waiting for the [`onDidChangeWorkspaceFolders()`](#onDidChangeWorkspaceFolders) to fire. + * + * @param start the zero-based location in the list of currently opened [workspace folders](#WorkspaceFolder) + * from which to start deleting workspace folders. + * @param deleteCount the optional number of workspace folders to remove. + * @param workspaceFoldersToAdd the optional variable set of workspace folders to add in place of the deleted ones. + * Each workspace is identified with a mandatory URI and an optional name. + * @return true if the operation was successfully started and false otherwise if arguments were used that would result + * in invalid workspace folder state (e.g. 2 folders with the same URI). + */ + export function updateWorkspaceFolders(start: number, deleteCount: number | undefined | null, ...workspaceFoldersToAdd: { uri: Uri, name?: string }[]): boolean; + /** * ~~Register a task provider.~~ * @@ -5716,6 +5770,15 @@ declare module '@theia/plugin' { * @param other Kind to check. */ contains(other: CodeActionKind): boolean; + + /** + * Check if this code action kind intersects `other`. + * The kind "refactor.extract" for example intersects refactor, "refactor.extract" and + * `"refactor.extract.function", but not "unicorn.refactor.extract", or "refactor.extractAll". + * + * @param other Kind to check. + */ + intersects(other: CodeActionKind): boolean; } /** diff --git a/packages/preferences/package.json b/packages/preferences/package.json index 3505105cea40c..fb8ee99e5e451 100644 --- a/packages/preferences/package.json +++ b/packages/preferences/package.json @@ -1,15 +1,15 @@ { "name": "@theia/preferences", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Preferences Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/json": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/userstorage": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/json": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/userstorage": "^0.5.0", + "@theia/workspace": "^0.5.0", "@types/fs-extra": "^4.0.2", "fs-extra": "^4.0.2", "jsonc-parser": "^2.0.2" @@ -43,11 +43,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/preferences/src/browser/abstract-resource-preference-provider.ts b/packages/preferences/src/browser/abstract-resource-preference-provider.ts index 05de310a9835b..aff488ced08bb 100644 --- a/packages/preferences/src/browser/abstract-resource-preference-provider.ts +++ b/packages/preferences/src/browser/abstract-resource-preference-provider.ts @@ -118,8 +118,7 @@ export abstract class AbstractResourcePreferenceProvider extends PreferenceProvi // tslint:disable-next-line:no-any protected async getParsedContent(content: string): Promise<{ [key: string]: any }> { - const strippedContent = jsoncparser.stripComments(content); - const jsonData = jsoncparser.parse(strippedContent); + const jsonData = this.parse(content); // tslint:disable-next-line:no-any const preferences: { [key: string]: any } = {}; if (typeof jsonData !== 'object') { @@ -146,6 +145,12 @@ export abstract class AbstractResourcePreferenceProvider extends PreferenceProvi return preferences; } + // tslint:disable-next-line:no-any + protected parse(content: string): any { + const strippedContent = jsoncparser.stripComments(content); + return jsoncparser.parse(strippedContent); + } + // tslint:disable-next-line:no-any protected async handlePreferenceChanges(newPrefs: { [key: string]: any }): Promise { const oldPrefs = Object.assign({}, this.preferences); diff --git a/packages/preferences/src/browser/preference-service.spec.ts b/packages/preferences/src/browser/preference-service.spec.ts index eb6dde85c0a11..c85cfbfc49983 100644 --- a/packages/preferences/src/browser/preference-service.spec.ts +++ b/packages/preferences/src/browser/preference-service.spec.ts @@ -29,7 +29,7 @@ import * as temp from 'temp'; import { Emitter } from '@theia/core/lib/common'; import { PreferenceService, PreferenceScope, PreferenceProviderDataChanges, - PreferenceSchemaProvider, PreferenceProviderProvider, PreferenceServiceImpl, bindPreferenceSchemaProvider, PreferenceChange + PreferenceSchemaProvider, PreferenceProviderProvider, PreferenceServiceImpl, bindPreferenceSchemaProvider, PreferenceChange, PreferenceSchema } from '@theia/core/lib/browser/preferences'; import { FileSystem, FileShouldOverwrite, FileStat } from '@theia/filesystem/lib/common/'; import { FileSystemWatcher } from '@theia/filesystem/lib/browser/filesystem-watcher'; @@ -623,14 +623,59 @@ describe('Preference Service', () => { }))); }); - function prepareServices() { + it('defaultOverrides [go].editor.formatOnSave', () => { + const { preferences, schema } = prepareServices({ + schema: { + properties: { + 'editor.insertSpaces': { + type: 'boolean', + default: true, + overridable: true + }, + 'editor.formatOnSave': { + type: 'boolean', + default: false, + overridable: true + } + } + } + }); + + assert.equal(true, preferences.get('editor.insertSpaces')); + assert.equal(undefined, preferences.get('[go].editor.insertSpaces')); + assert.equal(false, preferences.get('editor.formatOnSave')); + assert.equal(undefined, preferences.get('[go].editor.formatOnSave')); + + schema.registerOverrideIdentifier('go'); + schema.setSchema({ + id: 'defaultOverrides', + title: 'Default Configuration Overrides', + properties: { + '[go]': { + type: 'object', + default: { + 'editor.insertSpaces': false, + 'editor.formatOnSave': true + }, + description: 'Configure editor settings to be overridden for go language.' + } + } + }); + + assert.equal(true, preferences.get('editor.insertSpaces')); + assert.equal(false, preferences.get('[go].editor.insertSpaces')); + assert.equal(false, preferences.get('editor.formatOnSave')); + assert.equal(true, preferences.get('[go].editor.formatOnSave')); + }); + + function prepareServices(options?: { schema: PreferenceSchema }) { const container = new Container(); bindPreferenceSchemaProvider(container.bind.bind(container)); container.bind(PreferenceProviderProvider).toFactory(() => () => new MockPreferenceProvider()); container.bind(PreferenceServiceImpl).toSelf().inSingletonScope(); const schema = container.get(PreferenceSchemaProvider); - schema.setSchema({ + schema.setSchema(options && options.schema || { properties: { 'editor.tabSize': { type: 'number', diff --git a/packages/preferences/src/browser/workspace-preference-provider.ts b/packages/preferences/src/browser/workspace-preference-provider.ts index c45c49a206074..be78e5e1d9561 100644 --- a/packages/preferences/src/browser/workspace-preference-provider.ts +++ b/packages/preferences/src/browser/workspace-preference-provider.ts @@ -62,16 +62,14 @@ export class WorkspacePreferenceProvider extends AbstractResourcePreferenceProvi } // tslint:disable-next-line:no-any - protected async getParsedContent(content: string): Promise<{ [key: string]: any }> { - const data = await super.getParsedContent(content); + protected parse(content: string): any { + const data = super.parse(content); if (this.workspaceService.saved) { if (WorkspaceData.is(data)) { return data.settings || {}; } - } else { - return data || {}; } - return {}; + return data; } protected getPath(preferenceName: string): string[] { diff --git a/packages/preview/package.json b/packages/preview/package.json index 6183f78b786ae..b313f40815722 100644 --- a/packages/preview/package.json +++ b/packages/preview/package.json @@ -1,12 +1,12 @@ { "name": "@theia/preview", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Preview Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/mini-browser": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/mini-browser": "^0.5.0", "@types/highlight.js": "^9.12.2", "@types/markdown-it": "^0.0.4", "@types/markdown-it-anchor": "^4.0.1", @@ -43,11 +43,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/preview/src/browser/markdown/markdown-preview-handler.ts b/packages/preview/src/browser/markdown/markdown-preview-handler.ts index 832df374fa8cd..62357d0588209 100644 --- a/packages/preview/src/browser/markdown/markdown-preview-handler.ts +++ b/packages/preview/src/browser/markdown/markdown-preview-handler.ts @@ -106,7 +106,7 @@ export class MarkdownPreviewHandler implements PreviewHandler { if (!elementToReveal) { return; } - elementToReveal.scrollIntoView({ behavior: 'instant' }); + elementToReveal.scrollIntoView(); } findElementForFragment(content: HTMLElement, link: string): HTMLElement | undefined { diff --git a/packages/preview/src/browser/preview-contribution.ts b/packages/preview/src/browser/preview-contribution.ts index 2ae65dfa03773..cf1cc36efed64 100644 --- a/packages/preview/src/browser/preview-contribution.ts +++ b/packages/preview/src/browser/preview-contribution.ts @@ -36,10 +36,12 @@ export namespace PreviewCommands { */ export const OPEN: Command = { id: 'preview:open', - label: 'Open Preview' + label: 'Open Preview', + iconClass: 'theia-open-preview-icon' }; export const OPEN_SOURCE: Command = { - id: 'preview.open.source' + id: 'preview.open.source', + iconClass: 'theia-open-file-icon' }; } @@ -196,13 +198,11 @@ export class PreviewContribution extends NavigatableWidgetOpenHandler { this.preventScrollNotification = false; }, 50); @@ -225,7 +225,7 @@ export class PreviewWidget extends BaseWidget implements Navigatable { const elementToReveal = this.previewHandler.findElementForSourceLine(this.node, sourceLine); if (elementToReveal) { this.preventScrollNotification = true; - elementToReveal.scrollIntoView({ behavior: 'instant' }); + elementToReveal.scrollIntoView(); window.setTimeout(() => { this.preventScrollNotification = false; }, 50); diff --git a/packages/process/package.json b/packages/process/package.json index 44e0dae296c00..f61e9d14ca327 100644 --- a/packages/process/package.json +++ b/packages/process/package.json @@ -1,9 +1,9 @@ { "name": "@theia/process", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia process support.", "dependencies": { - "@theia/core": "^0.4.0", + "@theia/core": "^0.5.0", "@theia/node-pty": "0.7.8-theia004", "string-argv": "^0.1.1" }, @@ -36,11 +36,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/process/src/node/raw-process.ts b/packages/process/src/node/raw-process.ts index 4c24c17e79f71..e32f3793da3b0 100644 --- a/packages/process/src/node/raw-process.ts +++ b/packages/process/src/node/raw-process.ts @@ -119,9 +119,9 @@ export class RawProcess extends Process { ); }); - this.output = this.process.stdout; - this.input = this.process.stdin; - this.errorOutput = this.process.stderr; + this.output = this.process.stdout || new DevNullStream(); + this.input = this.process.stdin || new DevNullStream(); + this.errorOutput = this.process.stderr || new DevNullStream(); if (this.process.pid !== undefined) { process.nextTick(() => { diff --git a/packages/python/package.json b/packages/python/package.json index 24edea2008c4d..818a5cdc4ab96 100644 --- a/packages/python/package.json +++ b/packages/python/package.json @@ -1,12 +1,12 @@ { "name": "@theia/python", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Python Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/languages": "^0.4.0", - "@theia/monaco": "^0.4.0", - "@theia/process": "^0.4.0" + "@theia/core": "^0.5.0", + "@theia/languages": "^0.5.0", + "@theia/monaco": "^0.5.0", + "@theia/process": "^0.5.0" }, "publishConfig": { "access": "public" @@ -39,11 +39,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/search-in-workspace/package.json b/packages/search-in-workspace/package.json index e7e5b449d9ff6..f39dfd6c98691 100644 --- a/packages/search-in-workspace/package.json +++ b/packages/search-in-workspace/package.json @@ -1,14 +1,14 @@ { "name": "@theia/search-in-workspace", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Search in workspace", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/navigator": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/navigator": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/workspace": "^0.5.0", "vscode-ripgrep": "^1.2.4" }, "publishConfig": { @@ -41,10 +41,9 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" } } diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts index ab420767eb567..e8ae6c4f95fc1 100644 --- a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts +++ b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts @@ -18,12 +18,14 @@ import { AbstractViewContribution, KeybindingRegistry, LabelProvider, CommonMenu import { SearchInWorkspaceWidget } from './search-in-workspace-widget'; import { injectable, inject, postConstruct } from 'inversify'; import { CommandRegistry, MenuModelRegistry, SelectionService, Command } from '@theia/core'; +import { Widget } from '@theia/core/lib/browser/widgets'; import { NavigatorContextMenu } from '@theia/navigator/lib/browser/navigator-contribution'; import { UriCommandHandler, UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler'; import URI from '@theia/core/lib/common/uri'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { FileSystem } from '@theia/filesystem/lib/common'; import { SearchInWorkspaceContextKeyService } from './search-in-workspace-context-key-service'; +import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; export namespace SearchInWorkspaceCommands { const SEARCH_CATEGORY = 'Search'; @@ -41,10 +43,28 @@ export namespace SearchInWorkspaceCommands { category: SEARCH_CATEGORY, label: 'Find in Folder' }; + export const REFRESH_RESULTS: Command = { + id: 'search-in-workspace.refresh', + category: SEARCH_CATEGORY, + label: 'Refresh', + iconClass: 'refresh' + }; + export const COLLAPSE_ALL: Command = { + id: 'search-in-workspace.collapse-all', + category: SEARCH_CATEGORY, + label: 'Collapse All', + iconClass: 'collapse-all' + }; + export const CLEAR_ALL: Command = { + id: 'search-in-workspace.clear-all', + category: SEARCH_CATEGORY, + label: 'Clear All', + iconClass: 'clear-all' + }; } @injectable() -export class SearchInWorkspaceFrontendContribution extends AbstractViewContribution implements FrontendApplicationContribution { +export class SearchInWorkspaceFrontendContribution extends AbstractViewContribution implements FrontendApplicationContribution, TabBarToolbarContribution { @inject(SelectionService) protected readonly selectionService: SelectionService; @inject(LabelProvider) protected readonly labelProvider: LabelProvider; @@ -60,7 +80,7 @@ export class SearchInWorkspaceFrontendContribution extends AbstractViewContribut widgetName: SearchInWorkspaceWidget.LABEL, defaultWidgetOptions: { area: 'left', - rank: 300 + rank: 200 }, toggleCommandId: SearchInWorkspaceCommands.TOGGLE_SIW_WIDGET.id }); @@ -78,7 +98,7 @@ export class SearchInWorkspaceFrontendContribution extends AbstractViewContribut await this.openView({ activate: false }); } - registerCommands(commands: CommandRegistry): void { + async registerCommands(commands: CommandRegistry): Promise { super.registerCommands(commands); commands.registerCommand(SearchInWorkspaceCommands.OPEN_SIW_WIDGET, { isEnabled: () => this.workspaceService.tryGetRoots().length > 0, @@ -104,10 +124,33 @@ export class SearchInWorkspaceFrontendContribution extends AbstractViewContribut } } }); - const widget: SearchInWorkspaceWidget = await this.openView({ activate: true }); + const widget = await this.openView({ activate: true }); widget.findInFolder(resources); } })); + + commands.registerCommand(SearchInWorkspaceCommands.REFRESH_RESULTS, { + execute: w => this.withWidget(w, widget => widget.refresh()), + isEnabled: w => this.withWidget(w, widget => (widget.hasResultList() || widget.hasSearchTerm()) && this.workspaceService.tryGetRoots().length > 0), + isVisible: w => this.withWidget(w, () => true) + }); + commands.registerCommand(SearchInWorkspaceCommands.COLLAPSE_ALL, { + execute: w => this.withWidget(w, widget => widget.collapseAll()), + isEnabled: w => this.withWidget(w, widget => widget.hasResultList()), + isVisible: w => this.withWidget(w, () => true) + }); + commands.registerCommand(SearchInWorkspaceCommands.CLEAR_ALL, { + execute: w => this.withWidget(w, widget => widget.clear()), + isEnabled: w => this.withWidget(w, widget => widget.hasResultList()), + isVisible: w => this.withWidget(w, () => true) + }); + } + + protected withWidget(widget: Widget | undefined = this.tryGetWidget(), fn: (widget: SearchInWorkspaceWidget) => T): T | false { + if (widget instanceof SearchInWorkspaceWidget && widget.id === SearchInWorkspaceWidget.ID) { + return fn(widget); + } + return false; } registerKeybindings(keybindings: KeybindingRegistry): void { @@ -128,6 +171,32 @@ export class SearchInWorkspaceFrontendContribution extends AbstractViewContribut }); } + async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise { + const widget = await this.widget; + const onDidChange = widget.onDidUpdate; + toolbarRegistry.registerItem({ + id: SearchInWorkspaceCommands.REFRESH_RESULTS.id, + command: SearchInWorkspaceCommands.REFRESH_RESULTS.id, + tooltip: SearchInWorkspaceCommands.REFRESH_RESULTS.label, + priority: 0, + onDidChange + }); + toolbarRegistry.registerItem({ + id: SearchInWorkspaceCommands.CLEAR_ALL.id, + command: SearchInWorkspaceCommands.CLEAR_ALL.id, + tooltip: SearchInWorkspaceCommands.CLEAR_ALL.label, + priority: 1, + onDidChange + }); + toolbarRegistry.registerItem({ + id: SearchInWorkspaceCommands.COLLAPSE_ALL.id, + command: SearchInWorkspaceCommands.COLLAPSE_ALL.id, + tooltip: SearchInWorkspaceCommands.COLLAPSE_ALL.label, + priority: 2, + onDidChange + }); + } + protected newUriAwareCommandHandler(handler: UriCommandHandler): UriAwareCommandHandler { return new UriAwareCommandHandler(this.selectionService, handler); } 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 33fa6f6a28ccd..8faa063529446 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 @@ -26,6 +26,8 @@ import { SearchInWorkspaceResultTreeWidget } from './search-in-workspace-result- import { SearchInWorkspaceFrontendContribution } from './search-in-workspace-frontend-contribution'; import { InMemoryTextResourceResolver } from './in-memory-text-resource'; import { SearchInWorkspaceContextKeyService } from './search-in-workspace-context-key-service'; +import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; +import { bindSearchInWorkspacePreferences } from './search-in-workspace-preferences'; export default new ContainerModule(bind => { bind(SearchInWorkspaceContextKeyService).toSelf().inSingletonScope(); @@ -39,6 +41,7 @@ export default new ContainerModule(bind => { bindViewContribution(bind, SearchInWorkspaceFrontendContribution); bind(FrontendApplicationContribution).toService(SearchInWorkspaceFrontendContribution); + bind(TabBarToolbarContribution).toService(SearchInWorkspaceFrontendContribution); // The object that gets notified of search results. bind(SearchInWorkspaceClientImpl).toSelf().inSingletonScope(); @@ -53,6 +56,8 @@ export default new ContainerModule(bind => { bind(InMemoryTextResourceResolver).toSelf().inSingletonScope(); bind(ResourceResolver).toService(InMemoryTextResourceResolver); + + bindSearchInWorkspacePreferences(bind); }); export function createSearchTreeWidget(parent: interfaces.Container): SearchInWorkspaceResultTreeWidget { diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-preferences.ts b/packages/search-in-workspace/src/browser/search-in-workspace-preferences.ts new file mode 100644 index 0000000000000..8d423676ccf8c --- /dev/null +++ b/packages/search-in-workspace/src/browser/search-in-workspace-preferences.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * 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 { PreferenceSchema, PreferenceProxy, PreferenceService, createPreferenceProxy, PreferenceContribution } from '@theia/core/lib/browser/preferences'; +import { interfaces } from 'inversify'; + +export const searchInWorkspacePreferencesSchema: PreferenceSchema = { + type: 'object', + properties: { + 'search.lineNumbers': { + description: 'Controls whether to show line numbers for search results.', + default: false, + type: 'boolean', + } + } +}; + +export class SearchInWorkspaceConfiguration { + 'search.lineNumbers': boolean; +} + +export const SearchInWorkspacePreferences = Symbol('SearchInWorkspacePreferences'); +export type SearchInWorkspacePreferences = PreferenceProxy; + +export function createSearchInWorkspacePreferences(preferences: PreferenceService): SearchInWorkspacePreferences { + return createPreferenceProxy(preferences, searchInWorkspacePreferencesSchema); +} + +export function bindSearchInWorkspacePreferences(bind: interfaces.Bind): void { + bind(SearchInWorkspacePreferences).toDynamicValue(ctx => { + const preferences = ctx.container.get(PreferenceService); + return createSearchInWorkspacePreferences(preferences); + }).inSingletonScope(); + bind(PreferenceContribution).toConstantValue({ schema: searchInWorkspacePreferencesSchema }); +} diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.tsx b/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.tsx index e3314c464ff28..6d35165f8ede9 100644 --- a/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.tsx +++ b/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.tsx @@ -42,6 +42,7 @@ import { SearchInWorkspaceService } from './search-in-workspace-service'; import { MEMORY_TEXT } from './in-memory-text-resource'; import URI from '@theia/core/lib/common/uri'; import * as React from 'react'; +import { SearchInWorkspacePreferences } from './search-in-workspace-preferences'; const ROOT_ID = 'ResultTree'; @@ -115,6 +116,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget { @inject(LabelProvider) protected readonly labelProvider: LabelProvider; @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @inject(TreeExpansionService) protected readonly expansionService: TreeExpansionService; + @inject(SearchInWorkspacePreferences) protected readonly searchInWorkspacePreferences: SearchInWorkspacePreferences; constructor( @inject(TreeProps) readonly props: TreeProps, @@ -153,6 +155,10 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget { this.toDispose.push(this.editorManager.onActiveEditorChanged(() => { this.updateCurrentEditorDecorations(); })); + + this.toDispose.push(this.searchInWorkspacePreferences.onPreferenceChanged(() => { + this.update(); + })); } get fileNumber(): number { @@ -588,6 +594,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget { protected renderResultLineNode(node: SearchInWorkspaceResultLineNode): React.ReactNode { const prefix = node.character > 26 ? '... ' : ''; return
+ {this.searchInWorkspacePreferences['search.lineNumbers'] && {node.line}} {prefix + node.lineText.substr(0, node.character - 1).substr(-25)} diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-widget.tsx b/packages/search-in-workspace/src/browser/search-in-workspace-widget.tsx index 2fe771015766e..ba3aee37167d8 100644 --- a/packages/search-in-workspace/src/browser/search-in-workspace-widget.tsx +++ b/packages/search-in-workspace/src/browser/search-in-workspace-widget.tsx @@ -20,7 +20,7 @@ import { SearchInWorkspaceResultTreeWidget } from './search-in-workspace-result- import { SearchInWorkspaceOptions } from '../common/search-in-workspace-interface'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { Disposable } from '@theia/core/lib/common'; +import { Event, Emitter, Disposable } from '@theia/core/lib/common'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { SearchInWorkspaceContextKeyService } from './search-in-workspace-context-key-service'; @@ -72,6 +72,9 @@ export class SearchInWorkspaceWidget extends BaseWidget implements StatefulWidge protected searchFormContainer: HTMLElement; protected resultContainer: HTMLElement; + protected readonly onDidUpdateEmitter = new Emitter(); + readonly onDidUpdate: Event = this.onDidUpdateEmitter.event; + @inject(SearchInWorkspaceResultTreeWidget) protected readonly resultTreeWidget: SearchInWorkspaceResultTreeWidget; @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @@ -83,8 +86,8 @@ export class SearchInWorkspaceWidget extends BaseWidget implements StatefulWidge this.id = SearchInWorkspaceWidget.ID; this.title.label = SearchInWorkspaceWidget.LABEL; this.title.caption = SearchInWorkspaceWidget.LABEL; - this.title.iconClass = 'fa search-in-workspace-tab-icon'; - + this.title.iconClass = 'search-in-workspace-tab-icon'; + this.title.closable = true; this.contentNode = document.createElement('div'); this.contentNode.classList.add('t-siw-search-container'); this.searchFormContainer = document.createElement('div'); @@ -180,6 +183,47 @@ export class SearchInWorkspaceWidget extends BaseWidget implements StatefulWidge this.update(); } + hasResultList(): boolean { + return this.hasResults; + } + + hasSearchTerm(): boolean { + return this.searchTerm !== ''; + } + + refresh(): void { + this.resultTreeWidget.search(this.searchTerm, this.searchInWorkspaceOptions); + this.update(); + } + + collapseAll(): void { + this.resultTreeWidget.collapseAll(); + this.update(); + } + + clear(): void { + this.searchTerm = ''; + this.replaceTerm = ''; + this.searchInWorkspaceOptions.include = []; + this.searchInWorkspaceOptions.exclude = []; + this.includeIgnoredState.enabled = false; + this.matchCaseState.enabled = false; + this.wholeWordState.enabled = false; + this.regExpState.enabled = false; + const search = document.getElementById('search-input-field'); + const replace = document.getElementById('replace-input-field'); + const include = document.getElementById('include-glob-field'); + const exclude = document.getElementById('exclude-glob-field'); + if (search && replace && include && exclude) { + (search as HTMLInputElement).value = ''; + (replace as HTMLInputElement).value = ''; + (include as HTMLInputElement).value = ''; + (exclude as HTMLInputElement).value = ''; + } + this.resultTreeWidget.search(this.searchTerm, this.searchInWorkspaceOptions); + this.update(); + } + protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); ReactDOM.render({this.renderSearchHeader()}{this.renderSearchInfo()}, this.searchFormContainer); @@ -192,6 +236,7 @@ export class SearchInWorkspaceWidget extends BaseWidget implements StatefulWidge protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); ReactDOM.render({this.renderSearchHeader()}{this.renderSearchInfo()}, this.searchFormContainer); + this.onDidUpdateEmitter.fire(undefined); } protected onResize(msg: Widget.ResizeMessage): void { @@ -224,55 +269,9 @@ export class SearchInWorkspaceWidget extends BaseWidget implements StatefulWidge } protected renderSearchHeader(): React.ReactNode { - const controlButtons = this.renderControlButtons(); const searchAndReplaceContainer = this.renderSearchAndReplace(); const searchDetails = this.renderSearchDetails(); - return
{controlButtons}{searchAndReplaceContainer}{searchDetails}
; - } - - protected refresh = () => { - this.resultTreeWidget.search(this.searchTerm, this.searchInWorkspaceOptions); - this.update(); - } - - protected collapseAll = () => { - this.resultTreeWidget.collapseAll(); - this.update(); - } - - protected clear = () => { - this.searchTerm = ''; - this.replaceTerm = ''; - this.searchInWorkspaceOptions.include = []; - this.searchInWorkspaceOptions.exclude = []; - this.includeIgnoredState.enabled = false; - this.matchCaseState.enabled = false; - this.wholeWordState.enabled = false; - this.regExpState.enabled = false; - const search = document.getElementById('search-input-field'); - const replace = document.getElementById('replace-input-field'); - const include = document.getElementById('include-glob-field'); - const exclude = document.getElementById('exclude-glob-field'); - if (search && replace && include && exclude) { - (search as HTMLInputElement).value = ''; - (replace as HTMLInputElement).value = ''; - (include as HTMLInputElement).value = ''; - (exclude as HTMLInputElement).value = ''; - } - this.resultTreeWidget.search(this.searchTerm, this.searchInWorkspaceOptions); - this.update(); - } - - protected renderControlButtons(): React.ReactNode { - const refreshButton = this.renderControlButton(`refresh${(this.hasResults || this.searchTerm !== '') && this.workspaceService.tryGetRoots().length > 0 - ? ' enabled' : ''}`, 'Refresh', this.refresh); - const collapseAllButton = this.renderControlButton(`collapse-all${this.hasResults ? ' enabled' : ''}`, 'Collapse All', this.collapseAll); - const clearButton = this.renderControlButton(`clear-all${this.hasResults ? ' enabled' : ''}`, 'Clear', this.clear); - return
{refreshButton}{collapseAllButton}{clearButton}
; - } - - protected renderControlButton(btnClass: string, title: string, clickHandler: () => void): React.ReactNode { - return ; + return
{searchAndReplaceContainer}{searchDetails}
; } protected renderSearchAndReplace(): React.ReactNode { diff --git a/packages/search-in-workspace/src/browser/styles/index.css b/packages/search-in-workspace/src/browser/styles/index.css index 855d7c193c5c9..0162a42b5feac 100644 --- a/packages/search-in-workspace/src/browser/styles/index.css +++ b/packages/search-in-workspace/src/browser/styles/index.css @@ -21,13 +21,18 @@ .t-siw-search-container { color: var(--theia-ui-font-color1); - padding: 5px; + padding: 1px 5px; display: flex; flex-direction: column; height: 100%; box-sizing: border-box; } +.t-siw-search-container .theia-ExpansionToggle { + padding-right: 4px; + min-width: 6px; +} + .t-siw-search-container input[type="text"] { flex: 1; line-height: var(--theia-content-line-height); @@ -59,16 +64,16 @@ margin-bottom: 5px; } -.t-siw-search-container .searchHeader .controls .refresh { - background: var(--theia-icon-refresh); +.p-TabBar-toolbar .item .refresh { + background: var(--theia-icon-refresh) no-repeat; } -.t-siw-search-container .searchHeader .controls .collapse-all { - background: var(--theia-icon-collapse-all); +.p-TabBar-toolbar .item .collapse-all { + background: var(--theia-icon-collapse-all) no-repeat; } -.t-siw-search-container .searchHeader .controls .clear-all { - background: var(--theia-icon-clear); +.p-TabBar-toolbar .item .clear-all { + background: var(--theia-icon-clear) no-repeat; } .t-siw-search-container .searchHeader .search-field-container { @@ -181,26 +186,10 @@ justify-content: center; } -.t-siw-search-container .searchHeader .controls .btn{ - margin-left: 3px; - opacity: 0.25; - width: 18px -} - -.t-siw-search-container .searchHeader .controls .btn.enabled{ - opacity: 0.7; - cursor: pointer; -} - -.t-siw-search-container .searchHeader .controls .btn.enabled:hover{ - opacity: 1; -} - .t-siw-search-container .searchHeader .search-details .button-container { height: 5px; } - .t-siw-search-container .searchHeader .search-details .button-container .btn{ cursor: pointer; } @@ -224,6 +213,7 @@ .t-siw-search-container .resultContainer { height: 100%; + margin-left: 13px; } .t-siw-search-container .result { @@ -344,6 +334,11 @@ box-sizing: border-box; } +.theia-side-panel .replace-toggle { + width: 13px; + min-width: 13px; +} + .replace-toggle:hover { background: var(--theia-layout-color2); } @@ -388,8 +383,9 @@ opacity: 0.5; } -.search-in-workspace-tab-icon::before { - content: "\f002" +.search-in-workspace-tab-icon { + -webkit-mask: url('search.svg'); + mask: url('search.svg'); } .highlighted-count-container { @@ -401,3 +397,8 @@ color: var(--theia-ui-font-color2); margin-left: 17px; } + +.theia-siw-lineNumber { + color: var(--theia-ui-font-color2); + padding-right: 4px; +} diff --git a/packages/search-in-workspace/src/browser/styles/search.svg b/packages/search-in-workspace/src/browser/styles/search.svg new file mode 100644 index 0000000000000..5b8c2af051798 --- /dev/null +++ b/packages/search-in-workspace/src/browser/styles/search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/task/package.json b/packages/task/package.json index 68fd3afdc98fb..77254e4e987d5 100644 --- a/packages/task/package.json +++ b/packages/task/package.json @@ -1,15 +1,15 @@ { "name": "@theia/task", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Task extension. This extension adds support for executing raw or terminal processes in the backend.", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/markers": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/terminal": "^0.4.0", - "@theia/variable-resolver": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/markers": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/terminal": "^0.5.0", + "@theia/variable-resolver": "^0.5.0", + "@theia/workspace": "^0.5.0", "jsonc-parser": "^2.0.2" }, "publishConfig": { @@ -42,11 +42,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/task/src/browser/provided-task-configurations.spec.ts b/packages/task/src/browser/provided-task-configurations.spec.ts new file mode 100644 index 0000000000000..6849cc8257c5b --- /dev/null +++ b/packages/task/src/browser/provided-task-configurations.spec.ts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. 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 { assert } from 'chai'; +import { Container } from 'inversify'; +import { ProvidedTaskConfigurations } from './provided-task-configurations'; +import { TaskProviderRegistry } from './task-contribution'; +import { TaskConfiguration } from '../common'; + +describe('provided-task-configurations', () => { + let container: Container; + beforeEach(() => { + container = new Container(); + container.bind(ProvidedTaskConfigurations).toSelf().inSingletonScope(); + container.bind(TaskProviderRegistry).toSelf().inSingletonScope(); + }); + + it('provided-task-search', async () => { + const providerRegistry = container.get(TaskProviderRegistry); + providerRegistry.register('test', { + provideTasks(): Promise { + return Promise.resolve([{ type: 'test', label: 'task from test', _source: 'test', _scope: 'test' } as TaskConfiguration]); + } + }); + + const task = await container.get(ProvidedTaskConfigurations).getTask('test', 'task from 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 336dd5da0d3c9..d904e595f0175 100644 --- a/packages/task/src/browser/provided-task-configurations.ts +++ b/packages/task/src/browser/provided-task-configurations.ts @@ -42,7 +42,21 @@ export class ProvidedTaskConfigurations { } /** returns the task configuration for a given source and label or undefined if none */ - getTask(source: string, taskLabel: string): TaskConfiguration | undefined { + async getTask(source: string, taskLabel: string): Promise { + const task = this.getCachedTask(source, taskLabel); + if (task) { + return task; + } else { + const provider = this.taskProviderRegistry.getProvider(source); + if (provider) { + const tasks = await provider.provideTasks(); + this.cacheTasks(tasks); + return this.getCachedTask(source, taskLabel); + } + } + } + + protected getCachedTask(source: string, taskLabel: string): TaskConfiguration | undefined { const labelConfigMap = this.tasksMap.get(source); if (labelConfigMap) { return labelConfigMap.get(taskLabel); diff --git a/packages/task/src/browser/quick-open-task.ts b/packages/task/src/browser/quick-open-task.ts index 60d0ed4f8b490..ba034f9c1e21a 100644 --- a/packages/task/src/browser/quick-open-task.ts +++ b/packages/task/src/browser/quick-open-task.ts @@ -15,16 +15,22 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenGroupItem, QuickOpenMode, QuickOpenHandler, QuickOpenOptions } from '@theia/core/lib/browser/quick-open/'; +import { + QuickOpenService, QuickOpenModel, QuickOpenItem, + QuickOpenGroupItem, QuickOpenMode, QuickOpenHandler, QuickOpenOptions, QuickOpenActionProvider +} from '@theia/core/lib/browser/quick-open/'; import { TaskService } from './task-service'; import { TaskInfo, TaskConfiguration } from '../common/task-protocol'; import { TaskConfigurations } from './task-configurations'; import URI from '@theia/core/lib/common/uri'; +import { TaskActionProvider } from './task-action-provider'; +import { LabelProvider } from '@theia/core/lib/browser'; @injectable() export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { protected items: QuickOpenItem[]; + protected actionProvider: QuickOpenActionProvider | undefined; readonly prefix: string = 'task '; @@ -36,6 +42,12 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService; + @inject(TaskActionProvider) + protected readonly taskActionProvider: TaskActionProvider; + + @inject(LabelProvider) + protected readonly labelProvider: LabelProvider; + /** * @deprecated To be removed in 0.5.0 */ @@ -47,11 +59,21 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { const configuredTasks = this.taskService.getConfiguredTasks(); const providedTasks = await this.taskService.getProvidedTasks(); + const filteredProvidedTasks: TaskConfiguration[] = []; + providedTasks.forEach(provided => { + if (!configuredTasks.some(configured => configured.label === provided.label)) { + filteredProvidedTasks.push(provided); + } + }); + this.items = []; this.items.push( - ...configuredTasks.map((t, ind) => new TaskRunQuickOpenItem(t, this.taskService, true, ind === 0 ? 'configured' : undefined)), - ...providedTasks.map((t, ind) => new TaskRunQuickOpenItem(t, this.taskService, false, ind === 0 ? 'provided' : undefined)) + ...configuredTasks.map((t, ind) => new TaskRunQuickOpenItem(t, this.taskService, true, ind === 0 ? 'configured tasks' : undefined)), + ...filteredProvidedTasks.map((t, ind) => new TaskRunQuickOpenItem(t, this.taskService, false, ind === 0 ? 'detected tasks' : undefined)) ); + + this.actionProvider = this.items.length ? this.taskActionProvider : undefined; + if (!this.items.length) { this.items.push(new QuickOpenItem({ label: 'No tasks found', @@ -63,7 +85,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { async open(): Promise { await this.init(); this.quickOpenService.open(this, { - placeholder: 'Type the name of a task you want to execute', + placeholder: 'Select the task to run', fuzzyMatchLabel: true, fuzzySort: false }); @@ -82,6 +104,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { attach(): void { this.items = []; + this.actionProvider = undefined; this.taskService.getRunningTasks().then(tasks => { if (!tasks.length) { @@ -110,8 +133,31 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { }); } - onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): void { - acceptor(this.items); + async configure(): Promise { + this.items = []; + this.actionProvider = undefined; + + const providedTasks = await this.taskService.getProvidedTasks(); + if (!providedTasks.length) { + this.items.push(new QuickOpenItem({ + label: 'No tasks found', + run: (_mode: QuickOpenMode): boolean => false + })); + } + + providedTasks.forEach(task => { + this.items.push(new TaskConfigureQuickOpenItem(task, this.taskService, this.labelProvider)); + }); + + this.quickOpenService.open(this, { + placeholder: 'Select a task to configure', + fuzzyMatchLabel: true, + fuzzySort: true + }); + } + + onType(lookFor: string, acceptor: (items: QuickOpenItem[], actionProvider?: QuickOpenActionProvider) => void): void { + acceptor(this.items, this.actionProvider); } protected getRunningTaskLabel(task: TaskInfo): string { @@ -130,6 +176,10 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem { super(); } + getTask(): TaskConfiguration { + return this.task; + } + getLabel(): string { if (this.isConfigured) { return `${this.task.type}: ${this.task.label}`; @@ -155,7 +205,12 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem { if (mode !== QuickOpenMode.OPEN) { return false; } - this.taskService.run(this.task._source, this.task.label); + + if (this.isConfigured) { + this.taskService.runConfiguredTask(this.task._source, this.task.label); + } else { + this.taskService.run(this.task._source, this.task.label); + } return true; } @@ -185,3 +240,33 @@ export class TaskAttachQuickOpenItem extends QuickOpenItem { return true; } } +export class TaskConfigureQuickOpenItem extends QuickOpenGroupItem { + + constructor( + protected readonly task: TaskConfiguration, + protected readonly taskService: TaskService, + protected readonly labelProvider: LabelProvider + ) { + super(); + } + + getLabel(): string { + return `${this.task._source}: ${this.task.label}`; + } + + getDescription(): string { + if (this.task._scope) { + return this.labelProvider.getLongName(new URI(this.task._scope)); + } + return this.task._source; + } + + run(mode: QuickOpenMode): boolean { + if (mode !== QuickOpenMode.OPEN) { + return false; + } + this.taskService.configure(this.task); + + return true; + } +} diff --git a/packages/task/src/browser/style/configure-inverse.svg b/packages/task/src/browser/style/configure-inverse.svg new file mode 100644 index 0000000000000..692b34b2c1647 --- /dev/null +++ b/packages/task/src/browser/style/configure-inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/task/src/browser/style/configure.svg b/packages/task/src/browser/style/configure.svg new file mode 100644 index 0000000000000..cdd6588df5c12 --- /dev/null +++ b/packages/task/src/browser/style/configure.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/task/src/browser/style/index.css b/packages/task/src/browser/style/index.css new file mode 100644 index 0000000000000..84168e26ab32e --- /dev/null +++ b/packages/task/src/browser/style/index.css @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. 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 + ********************************************************************************/ + + .quick-open-task-configure-dark { + background-image: url('configure-inverse.svg'); + background-position-y: -2px; +} + +.quick-open-task-configure-bright { + background-image: url('configure.svg'); + background-position-y: -2px; +} diff --git a/packages/task/src/browser/task-action-provider.ts b/packages/task/src/browser/task-action-provider.ts new file mode 100644 index 0000000000000..736330452f3fe --- /dev/null +++ b/packages/task/src/browser/task-action-provider.ts @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. 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, inject } from 'inversify'; +import { TaskService } from './task-service'; +import { TaskRunQuickOpenItem } from './quick-open-task'; +import { QuickOpenBaseAction, QuickOpenItem, QuickOpenActionProvider, QuickOpenAction } from '@theia/core/lib/browser/quick-open'; +import { ThemeService } from '@theia/core/lib/browser/theming'; + +@injectable() +export class ConfigureTaskAction extends QuickOpenBaseAction { + + @inject(TaskService) + protected readonly taskService: TaskService; + + constructor() { + super({ id: 'configure:task' }); + + this.updateTheme(); + + ThemeService.get().onThemeChange(() => this.updateTheme()); + } + + async run(item?: QuickOpenItem): Promise { + if (item instanceof TaskRunQuickOpenItem) { + this.taskService.configure(item.getTask()); + } + } + + protected updateTheme(): void { + const theme = ThemeService.get().getCurrentTheme().id; + if (theme === 'dark') { + this.class = 'quick-open-task-configure-dark'; + } else if (theme === 'light') { + this.class = 'quick-open-task-configure-bright'; + } + } +} + +@injectable() +export class TaskActionProvider implements QuickOpenActionProvider { + + @inject(ConfigureTaskAction) + protected configureTaskAction: ConfigureTaskAction; + + hasActions(): boolean { + return true; + } + + async getActions(): Promise { + return [this.configureTaskAction]; + } +} diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index 825f6eebd7c80..c84bbbd0c5dae 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -16,13 +16,16 @@ import { inject, injectable } from 'inversify'; import { TaskConfiguration } from '../common/task-protocol'; -import { Disposable, DisposableCollection } from '@theia/core/lib/common'; +import { Disposable, DisposableCollection, ResourceProvider } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; import { FileSystemWatcher, FileChangeEvent } from '@theia/filesystem/lib/browser/filesystem-watcher'; import { FileChange, FileChangeType } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; import { FileSystem } from '@theia/filesystem/lib/common'; import * as jsoncparser from 'jsonc-parser'; import { ParseError } from 'jsonc-parser'; +import { WorkspaceService } from '@theia/workspace/lib/browser'; +import { open, OpenerService } from '@theia/core/lib/browser'; +import { Resource } from '@theia/core'; export interface TaskConfigurationClient { /** @@ -54,6 +57,15 @@ export class TaskConfigurations implements Disposable { protected client: TaskConfigurationClient | undefined = undefined; + @inject(WorkspaceService) + protected readonly workspaceService: WorkspaceService; + + @inject(ResourceProvider) + protected readonly resourceProvider: ResourceProvider; + + @inject(OpenerService) + protected readonly openerService: OpenerService; + constructor( @inject(FileSystemWatcher) protected readonly watcherServer: FileSystemWatcher, @inject(FileSystem) protected readonly fileSystem: FileSystem @@ -230,6 +242,49 @@ export class TaskConfigurations implements Disposable { } } + /** Adds given task to a config file and opens the file to provide ability to edit task configuration. */ + async configure(task: TaskConfiguration): Promise { + const workspace = this.workspaceService.workspace; + if (!workspace) { + return; + } + + const configFileUri = this.getConfigFileUri(workspace.uri); + if (!this.getTasks().some(t => t.label === task.label)) { + await this.saveTask(configFileUri, task); + } + + try { + await open(this.openerService, new URI(configFileUri)); + } catch (e) { + console.error(`Error occurred while opening: ${this.TASKFILE}.`, e); + } + } + + /** Writes the task to a config file. Creates a config file if this one does not exist */ + async saveTask(configFileUri: string, task: TaskConfiguration): Promise { + if (configFileUri && !await this.fileSystem.exists(configFileUri)) { + await this.fileSystem.createFile(configFileUri); + } + + const { _source, $ident, ...preparedTask } = task; + try { + const response = await this.fileSystem.resolveContent(configFileUri); + const content = response.content; + + const formattingOptions = { tabSize: 4, insertSpaces: true, eol: '' }; + const edits = jsoncparser.modify(content, ['tasks', -1], preparedTask, { formattingOptions }); + const result = jsoncparser.applyEdits(content, edits); + + const resource = await this.resourceProvider(new URI(configFileUri)); + Resource.save(resource, { content: result }); + } catch (e) { + const message = `Failed to save task configuration for ${task.label} task.`; + console.error(`${message} ${e.toString()}`); + return; + } + } + protected filterDuplicates(tasks: TaskConfiguration[]): TaskConfiguration[] { const filteredTasks: TaskConfiguration[] = []; for (const task of tasks) { diff --git a/packages/task/src/browser/task-frontend-contribution.ts b/packages/task/src/browser/task-frontend-contribution.ts index ea81f8af09fb5..2cbd3adb70f36 100644 --- a/packages/task/src/browser/task-frontend-contribution.ts +++ b/packages/task/src/browser/task-frontend-contribution.ts @@ -47,6 +47,18 @@ export namespace TaskCommands { category: TASK_CATEGORY, label: 'Attach Task...' }; + + export const TASK_RUN_TEXT: Command = { + id: 'task:run:text', + category: TASK_CATEGORY, + label: 'Run Selected Text' + }; + + export const TASK_CONFIGURE: Command = { + id: 'task:configure', + category: TASK_CATEGORY, + label: 'Configure Tasks...' + }; } @injectable() @@ -119,6 +131,20 @@ export class TaskFrontendContribution implements CommandContribution, MenuContri execute: () => this.taskService.runLastTask() } ); + registry.registerCommand( + TaskCommands.TASK_RUN_TEXT, + { + isEnabled: () => true, + execute: () => this.taskService.runSelectedText() + } + ); + + registry.registerCommand( + TaskCommands.TASK_CONFIGURE, + { + execute: () => this.quickOpenTask.configure() + } + ); } registerMenus(menus: MenuModelRegistry): void { @@ -136,6 +162,16 @@ export class TaskFrontendContribution implements CommandContribution, MenuContri commandId: TaskCommands.TASK_ATTACH.id, order: '2' }); + + menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, { + commandId: TaskCommands.TASK_RUN_TEXT.id, + order: '3' + }); + + menus.registerMenuAction(TerminalMenus.TERMINAL_TASKS, { + commandId: TaskCommands.TASK_CONFIGURE.id, + order: '4' + }); } registerQuickOpenHandlers(handlers: QuickOpenHandlerRegistry): void { diff --git a/packages/task/src/browser/task-frontend-module.ts b/packages/task/src/browser/task-frontend-module.ts index 00d84180ada1d..8e95eba0efddc 100644 --- a/packages/task/src/browser/task-frontend-module.ts +++ b/packages/task/src/browser/task-frontend-module.ts @@ -29,10 +29,14 @@ import { TaskServer, taskPath } from '../common/task-protocol'; import { TaskWatcher } from '../common/task-watcher'; import { bindProcessTaskModule } from './process/process-task-frontend-module'; import { TaskSchemaUpdater } from './task-schema-updater'; +import { TaskActionProvider, ConfigureTaskAction } from './task-action-provider'; +import '../../src/browser/style/index.css'; export default new ContainerModule(bind => { bind(TaskFrontendContribution).toSelf().inSingletonScope(); bind(TaskService).toSelf().inSingletonScope(); + bind(TaskActionProvider).toSelf().inSingletonScope(); + bind(ConfigureTaskAction).toSelf().inSingletonScope(); for (const identifier of [FrontendApplicationContribution, CommandContribution, KeybindingContribution, MenuContribution, QuickOpenContribution]) { bind(identifier).toService(TaskFrontendContribution); diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index 41e1ba774d3dc..a0e7d2108e544 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -15,10 +15,12 @@ ********************************************************************************/ import { inject, injectable, named, postConstruct } from 'inversify'; +import { EditorManager } from '@theia/editor/lib/browser'; import { ILogger } from '@theia/core/lib/common'; import { FrontendApplication, ApplicationShell } from '@theia/core/lib/browser'; import { TaskResolverRegistry, TaskProviderRegistry } from './task-contribution'; import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions } from '@theia/terminal/lib/browser/terminal-widget-impl'; +import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; import { WidgetManager } from '@theia/core/lib/browser/widget-manager'; import { MessageService } from '@theia/core/lib/common/message-service'; @@ -28,6 +30,7 @@ import { VariableResolverService } from '@theia/variable-resolver/lib/browser'; import { TaskWatcher } from '../common/task-watcher'; import { TaskConfigurationClient, TaskConfigurations } from './task-configurations'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; +import { Range } from 'vscode-languageserver-types'; import URI from '@theia/core/lib/common/uri'; @injectable() @@ -79,6 +82,12 @@ export class TaskService implements TaskConfigurationClient { @inject(TaskResolverRegistry) protected readonly taskResolverRegistry: TaskResolverRegistry; + @inject(TerminalService) + protected readonly terminalService: TerminalService; + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + /** * @deprecated To be removed in 0.5.0 */ @@ -148,7 +157,7 @@ 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. */ - getProvidedTask(source: string, label: string): TaskConfiguration | undefined { + async getProvidedTask(source: string, label: string): Promise { return this.providedTaskConfigurations.getTask(source, label); } @@ -181,7 +190,8 @@ export class TaskService implements TaskConfigurationClient { this.logger.error(`Can't get task launch configuration for label: ${taskLabel}`); return; } - this.run(task._source, task.label); + + this.runTask(task); } /** @@ -200,7 +210,7 @@ export class TaskService implements TaskConfigurationClient { * It looks for configured and provided tasks. */ async run(source: string, taskLabel: string): Promise { - let task = this.getProvidedTask(source, taskLabel); + let task = await this.getProvidedTask(source, taskLabel); if (!task) { task = this.taskConfigurations.getTask(source, taskLabel); if (!task) { @@ -209,6 +219,13 @@ export class TaskService implements TaskConfigurationClient { } } + this.runTask(task); + } + + async runTask(task: TaskConfiguration): Promise { + const source = task._source; + const taskLabel = task.label; + const resolver = this.taskResolverRegistry.getResolver(task.type); let resolvedTask: TaskConfiguration; try { @@ -238,6 +255,30 @@ export class TaskService implements TaskConfigurationClient { } } + /** + * Run selected text in the last active terminal. + */ + async runSelectedText(): Promise { + if (!this.editorManager.currentEditor) { return; } + const startLine = this.editorManager.currentEditor.editor.selection.start.line; + const startCharacter = this.editorManager.currentEditor.editor.selection.start.character; + const endLine = this.editorManager.currentEditor.editor.selection.end.line; + const endCharacter = this.editorManager.currentEditor.editor.selection.end.character; + let selectedRange: Range = Range.create(startLine, startCharacter, endLine, endCharacter); + // if no text is selected, default to selecting entire line + if (startLine === endLine && startCharacter === endCharacter) { + selectedRange = Range.create(startLine, 0, endLine + 1, 0); + } + const selectedText: string = this.editorManager.currentEditor.editor.document.getText(selectedRange).trimRight() + '\n'; + let terminal = this.terminalService.currentTerminal; + if (!terminal) { + terminal = await this.terminalService.newTerminal({ created: new Date().toString() }); + await terminal.start(); + this.terminalService.activateTerminal(terminal); + } + terminal.sendText(selectedText); + } + async attach(terminalId: number, taskId: number): Promise { // create terminal widget to display an execution output of a Task that was launched as a command inside a shell const widget = await this.widgetManager.getOrCreateWidget( @@ -255,6 +296,10 @@ export class TaskService implements TaskConfigurationClient { widget.start(terminalId); } + async configure(task: TaskConfiguration): Promise { + await this.taskConfigurations.configure(task); + } + protected isEventForThisClient(context: string | undefined): boolean { if (context === this.getContext()) { return true; diff --git a/packages/task/src/node/task-server.slow-spec.ts b/packages/task/src/node/task-server.slow-spec.ts index b2dd0caf5dc27..054ce333bfbf4 100644 --- a/packages/task/src/node/task-server.slow-spec.ts +++ b/packages/task/src/node/task-server.slow-spec.ts @@ -87,14 +87,14 @@ describe('Task server / back-end', function () { await new Promise((resolve, reject) => { const channel = new TestWebSocketChannel({ server, path: `${terminalsPath}/${terminalId}` }); channel.onError(reject); - channel.onClose((code, reason) => reject(`channel is closed with '${code}' code and '${reason}' reason`)); + channel.onClose((code, reason) => reject(new Error(`channel is closed with '${code}' code and '${reason}' reason`))); channel.onMessage(msg => { // check output of task on terminal is what we expect const expected = `tasking... ${someString}`; if (msg.toString().indexOf(expected) !== -1) { resolve(); } else { - reject(`expected sub-string not found in terminal output. Expected: "${expected}" vs Actual: "${msg.toString()}"`); + reject(new Error(`expected sub-string not found in terminal output. Expected: "${expected}" vs Actual: "${msg.toString()}"`)); } channel.close(); }); @@ -114,7 +114,7 @@ describe('Task server / back-end', function () { if (taskInfo.terminalId === undefined) { resolve(); } else { - reject(`terminal id was expected to be undefined, actual: ${taskInfo.terminalId}`); + reject(new Error(`terminal id was expected to be undefined, actual: ${taskInfo.terminalId}`)); } toDispose.dispose(); } @@ -272,14 +272,14 @@ describe('Task server / back-end', function () { if (runningTasksAll.length === 6) { resolve(); } else { - reject(`Error: unexpected total number of running tasks for all contexts: expected: 6, actual: ${runningTasksCtx1.length}`); + reject(new Error(`Error: unexpected total number of running tasks for all contexts: expected: 6, actual: ${runningTasksCtx1.length}`)); } } else { - reject(`Error: unexpected number of running tasks for context 2: expected: 2, actual: ${runningTasksCtx1.length}`); + reject(new Error(`Error: unexpected number of running tasks for context 2: expected: 2, actual: ${runningTasksCtx1.length}`)); } } else { - reject(`Error: unexpected number of running tasks for context 1: expected: 4, actual: ${runningTasksCtx1.length}`); + reject(new Error(`Error: unexpected number of running tasks for context 1: expected: 4, actual: ${runningTasksCtx1.length}`)); } }); @@ -320,11 +320,11 @@ describe('Task server / back-end', function () { if (numRunningTasksAfterKilled.length === 0) { resolve(); } else { - reject(`Error: remaining running tasks, after all killed: expected: 0, actual: ${numRunningTasksAfterKilled.length}`); + reject(new Error(`Error: remaining running tasks, after all killed: expected: 0, actual: ${numRunningTasksAfterKilled.length}`)); } } else { - reject(`Error: unexpected number of running tasks: expected: ${numTasks}, actual: ${numRunningTasksAfterCreated.length}`); + reject(new Error(`Error: unexpected number of running tasks: expected: ${numTasks}, actual: ${numRunningTasksAfterCreated.length}`)); } }); diff --git a/packages/task/src/node/task-server.ts b/packages/task/src/node/task-server.ts index 13abfe82ae29f..6f615b21c8302 100644 --- a/packages/task/src/node/task-server.ts +++ b/packages/task/src/node/task-server.ts @@ -95,7 +95,7 @@ export class TaskServerImpl implements TaskServer { return taskToKill.kill(); } else { this.logger.info(`Could not find task to kill, task id ${id}. Already terminated?`); - return Promise.reject(`Could not find task to kill, task id ${id}. Already terminated?`); + return Promise.reject(new Error(`Could not find task to kill, task id ${id}. Already terminated?`)); } } diff --git a/packages/terminal/package.json b/packages/terminal/package.json index 80545ba73ea4f..acfb46c4060cf 100644 --- a/packages/terminal/package.json +++ b/packages/terminal/package.json @@ -1,13 +1,13 @@ { "name": "@theia/terminal", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Terminal Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/filesystem": "^0.4.0", - "@theia/process": "^0.4.0", - "@theia/workspace": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/filesystem": "^0.5.0", + "@theia/process": "^0.5.0", + "@theia/workspace": "^0.5.0", "xterm": "3.9.2" }, "publishConfig": { @@ -40,11 +40,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/terminal/src/browser/terminal-preferences.ts b/packages/terminal/src/browser/terminal-preferences.ts index 4155588369852..d829d307e9f4e 100644 --- a/packages/terminal/src/browser/terminal-preferences.ts +++ b/packages/terminal/src/browser/terminal-preferences.ts @@ -39,6 +39,7 @@ export const TerminalConfigSchema: PreferenceSchema = { 'terminal.integrated.fontSize': { type: 'number', description: 'Controls the font size in pixels of the terminal.', + minimum: 6, default: EDITOR_FONT_DEFAULTS.fontSize }, 'terminal.integrated.fontWeight': { @@ -61,6 +62,7 @@ export const TerminalConfigSchema: PreferenceSchema = { 'terminal.integrated.lineHeight': { description: 'Controls the line height of the terminal, this number is multiplied by the terminal font size to get the actual line-height in pixels.', type: 'number', + minimum: 1, default: 1 }, } diff --git a/packages/terminal/src/node/terminal-backend-contribution.slow-spec.ts b/packages/terminal/src/node/terminal-backend-contribution.slow-spec.ts index 82e230dfd00fc..064f1b828db37 100644 --- a/packages/terminal/src/node/terminal-backend-contribution.slow-spec.ts +++ b/packages/terminal/src/node/terminal-backend-contribution.slow-spec.ts @@ -47,7 +47,7 @@ describe('Terminal Backend Contribution', function () { await new Promise((resolve, reject) => { const channel = new TestWebSocketChannel({ server, path: `${terminalsPath}/${terminalId}` }); channel.onError(reject); - channel.onClose((code, reason) => reject(`channel is closed with '${code}' code and '${reason}' reason`)); + channel.onClose((code, reason) => reject(new Error(`channel is closed with '${code}' code and '${reason}' reason`))); channel.onOpen(() => { resolve(); channel.close(); diff --git a/packages/textmate-grammars/package.json b/packages/textmate-grammars/package.json index 9148160e2be2c..ef21f6d689635 100644 --- a/packages/textmate-grammars/package.json +++ b/packages/textmate-grammars/package.json @@ -1,9 +1,9 @@ { "name": "@theia/textmate-grammars", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Textmate Grammars", "dependencies": { - "@theia/monaco": "^0.4.0" + "@theia/monaco": "^0.5.0" }, "publishConfig": { "access": "public" @@ -35,11 +35,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/textmate-grammars/src/browser/perl.ts b/packages/textmate-grammars/src/browser/perl.ts index 741a97ab56013..536d9b92de6e7 100644 --- a/packages/textmate-grammars/src/browser/perl.ts +++ b/packages/textmate-grammars/src/browser/perl.ts @@ -22,6 +22,32 @@ export class PerlContribution implements LanguageGrammarDefinitionContribution { readonly id = 'perl'; readonly scopeName = 'source.perl'; + readonly config: monaco.languages.LanguageConfiguration = { + comments: { + lineComment: '#' + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' }, + { open: '`', close: '`' } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' }, + { open: '`', close: '`' } + ] + }; registerTextmateLanguage(registry: TextmateRegistry) { monaco.languages.register({ @@ -30,6 +56,9 @@ export class PerlContribution implements LanguageGrammarDefinitionContribution { extensions: ['.pl', '.pm', '.pod', '.t', '.PL', '.psgi'], firstLine: '^#!.*\\bperl\\b' }); + + monaco.languages.setLanguageConfiguration(this.id, this.config); + const grammar = require('../../data/perl.tmLanguage.json'); registry.registerTextmateGrammarScope(this.scopeName, { async getGrammarDefinition() { diff --git a/packages/tslint/package.json b/packages/tslint/package.json index 27acc78a698f6..7ec8d91373718 100644 --- a/packages/tslint/package.json +++ b/packages/tslint/package.json @@ -1,6 +1,6 @@ { "name": "@theia/tslint", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - TSLint Extension", "publishConfig": { "access": "public" @@ -30,11 +30,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/typehierarchy/package.json b/packages/typehierarchy/package.json index d15a74a2cae23..db66651728868 100644 --- a/packages/typehierarchy/package.json +++ b/packages/typehierarchy/package.json @@ -1,11 +1,11 @@ { "name": "@theia/typehierarchy", - "version": "0.4.0", + "version": "0.5.0", "description": "Theia - Type Hierarchy Extension", "dependencies": { - "@theia/core": "^0.4.0", - "@theia/editor": "^0.4.0", - "@theia/languages": "^0.4.0", + "@theia/core": "^0.5.0", + "@theia/editor": "^0.5.0", + "@theia/languages": "^0.5.0", "@types/uuid": "^3.4.3", "uuid": "^3.2.1" }, @@ -38,11 +38,10 @@ "clean": "theiaext clean", "build": "theiaext build", "watch": "theiaext watch", - "test": "theiaext test", - "docs": "theiaext docs" + "test": "theiaext test" }, "devDependencies": { - "@theia/ext-scripts": "^0.4.0" + "@theia/ext-scripts": "^0.5.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts b/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts index 6a511a984c50c..11c2ac019ea63 100644 --- a/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts +++ b/packages/typehierarchy/src/browser/tree/typehierarchy-tree.ts @@ -131,6 +131,10 @@ export namespace TypeHierarchyTree { export function create(item: TypeHierarchyItem, direction: TypeHierarchyDirection, resolved: boolean = true): Node { const items = TypeHierarchyDirection.Children === direction ? item.children : item.parents; + if (items && items.length > 0) { + // If the server sent more levels than requested, use them. + resolved = true; + } const node = { id: v4(), name: item.name, diff --git a/packages/typehierarchy/src/browser/typehierarchy-contribution.ts b/packages/typehierarchy/src/browser/typehierarchy-contribution.ts index 8038606b19715..74ee7ae797648 100644 --- a/packages/typehierarchy/src/browser/typehierarchy-contribution.ts +++ b/packages/typehierarchy/src/browser/typehierarchy-contribution.ts @@ -44,7 +44,7 @@ export class TypeHierarchyContribution extends AbstractViewContribution boolean = require('valid-filename'); @@ -136,6 +137,15 @@ export class FileMenuContribution implements MenuContribution { registry.registerMenuAction(CommonMenus.FILE_NEW, { commandId: WorkspaceCommands.NEW_FOLDER.id }); + const downloadUploadMenu = [...CommonMenus.FILE, '4_downloadupload']; + registry.registerMenuAction(downloadUploadMenu, { + commandId: FileDownloadCommands.UPLOAD.id, + order: 'a' + }); + registry.registerMenuAction(downloadUploadMenu, { + commandId: FileDownloadCommands.DOWNLOAD.id, + order: 'b' + }); } } @@ -172,11 +182,13 @@ export class WorkspaceCommandContribution implements CommandContribution { const parentUri = new URI(parent.uri); const { fileName, fileExtension } = this.getDefaultFileConfig(); const vacantChildUri = FileSystemUtils.generateUniqueResourceURI(parentUri, parent, fileName, fileExtension); + const dialog = new SingleTextInputDialog({ title: 'New File', initialValue: vacantChildUri.path.base, - validate: name => this.validateFileName(name, parent) + validate: name => this.validateFileName(name, parent, true) }); + dialog.open().then(name => { if (name) { const fileUri = parentUri.resolve(name); @@ -196,7 +208,7 @@ export class WorkspaceCommandContribution implements CommandContribution { const dialog = new SingleTextInputDialog({ title: 'New Folder', initialValue: vacantChildUri.path.base, - validate: name => this.validateFileName(name, parent) + validate: name => this.validateFileName(name, parent, true) }); dialog.open().then(name => { if (name) { @@ -230,12 +242,12 @@ export class WorkspaceCommandContribution implements CommandContribution { if (initialValue === name && mode === 'preview') { return false; } - return this.validateFileName(name, parent); + return this.validateFileName(name, parent, false); } }); dialog.open().then(name => { if (name) { - this.fileSystem.move(uri.toString(), uri.parent.resolve(name).toString()); + this.fileSystem.move(uri.toString(), uri.parent.resolve(name).toString(), { overwrite: true }); } }); } @@ -298,21 +310,40 @@ export class WorkspaceCommandContribution implements CommandContribution { * * @param name the simple file name of the file to validate. * @param parent the parent directory's file stat. + * @param recursive allow file or folder creation using recursive path */ - protected validateFileName(name: string, parent: FileStat): string { - if (!validFilename(name)) { - return 'Invalid name, try other'; + protected async validateFileName(name: string, parent: FileStat, recursive: boolean = false): Promise { + if (!name) { + return ''; } - if (parent.children) { - for (const child of parent.children) { - if (new URI(child.uri).path.base === name) { - return 'A file with this name already exists.'; - } - } + // do not allow recursive rename + if (!recursive && !validFilename(name)) { + return 'Invalid file or folder name'; + } + if (name.startsWith('/')) { + return 'Absolute paths or names that starts with / are not allowed'; + } else if (name.startsWith(' ') || name.endsWith(' ')) { + return 'Names with leading or trailing whitespaces are not allowed'; + } + // check and validate each sub-paths + if (name.split(/[\\/]/).some(file => !file || !validFilename(file) || /^\s+$/.test(file))) { + return `The name ${this.trimFileName(name)} is not a valid file or folder name.`; + } + const childUri = new URI(parent.uri).resolve(name).toString(); + const exists = await this.fileSystem.exists(childUri); + if (exists) { + return `A file or folder ${this.trimFileName(name)} already exists at this location.`; } return ''; } + protected trimFileName(name: string): string { + if (name && name.length > 30) { + return `${name.substr(0, 30)}...`; + } + return name; + } + protected async getDirectory(candidate: URI): Promise { const stat = await this.fileSystem.getFileStat(candidate.toString()); if (stat && stat.isDirectory) { @@ -390,7 +421,6 @@ export class WorkspaceCommandContribution implements CommandContribution { } return false; } - } export class WorkspaceRootUriAwareCommandHandler extends UriAwareCommandHandler { diff --git a/packages/workspace/src/browser/workspace-service.spec.ts b/packages/workspace/src/browser/workspace-service.spec.ts index 140752f856278..5d7af9aa37509 100644 --- a/packages/workspace/src/browser/workspace-service.spec.ts +++ b/packages/workspace/src/browser/workspace-service.spec.ts @@ -33,6 +33,7 @@ import { createMockPreferenceProxy } from '@theia/core/lib/browser/preferences/t import * as jsoncparser from 'jsonc-parser'; import * as sinon from 'sinon'; import * as chai from 'chai'; +import * as assert from 'assert'; import URI from '@theia/core/lib/common/uri'; const expect = chai.expect; @@ -483,43 +484,6 @@ describe('WorkspaceService', () => { await wsService.addRoot(new URI(folderB.uri)); expect(spyWriteFile.calledWith(workspaceFileStat, { folders: [{ path: folderA.uri }, { path: folderB.uri }] })).to.be.true; }); - - [true, false].forEach(existTemporaryWorkspaceFile => { - it('should write workspace data into a temporary file when theia currently uses a folder as the workspace ' + - `and the temporary file ${existTemporaryWorkspaceFile ? 'exists' : 'does not exist'}`, async () => { - const stubSave = sinon.stub(wsService, 'save').callsFake(() => { }); - const stubWriteWorkspaceFile = sinon.stub(wsService, 'writeWorkspaceFile').callsFake(() => { }); - toRestore.push(...[stubSave, stubWriteWorkspaceFile]); - wsService['_workspace'] = folderA; - wsService['_roots'] = [folderA]; - const homeStat = { - uri: 'file:///home/user', - lastModification: 0, - isDirectory: true - }; - const untitledStat = { - uri: 'file:///home/user/.theia/Untitled.theia-workspace', - lastModification: 0, - isDirectory: true - }; - (mockFilesystem.getCurrentUserHome).resolves(homeStat); - const stubGetFileStat = mockFilesystem.getFileStat; - stubGetFileStat.onCall(0).resolves(folderB); - (mockFilesystem.exists).resolves(existTemporaryWorkspaceFile); - const stubCreateFile = mockFilesystem.createFile; - stubCreateFile.resolves(untitledStat); - if (existTemporaryWorkspaceFile) { - stubGetFileStat.onCall(1).resolves(untitledStat); - } - wsService['_workspace'] = folderA; - wsService['_roots'] = [folderA]; - - await wsService.addRoot(new URI(folderB.uri)); - expect(stubCreateFile.calledWith(untitledStat.uri)).to.eq(!existTemporaryWorkspaceFile); - expect(stubSave.calledWith(untitledStat)).to.be.true; - expect(stubWriteWorkspaceFile.called).to.be.true; - }); - }); }); describe('save() function', () => { @@ -725,6 +689,59 @@ describe('WorkspaceService', () => { }); }); + describe('spliceRoots', () => { + const workspace = { uri: 'file:///workspace.theia-workspace', isDirectory: false }; + const fooDir = { uri: 'file:///foo', isDirectory: true }; + const workspaceService: WorkspaceService = new WorkspaceService(); + workspaceService['getUntitledWorkspace'] = async () => new URI('file:///untitled.theia-workspace'); + workspaceService['save'] = async () => { }; + workspaceService['getWorkspaceDataFromFile'] = async () => ({ folders: [] }); + workspaceService['writeWorkspaceFile'] = async (_, data) => { + workspaceService['_roots'] = data.folders.map(({ path }) => { uri: path }); + return undefined; + }; + const assertRemoved = (removed: URI[], ...roots: string[]) => + assert.deepEqual(removed.map(uri => uri.toString()), roots); + const assertRoots = (...roots: string[]) => + assert.deepEqual(workspaceService['_roots'].map(root => root.uri), roots); + + beforeEach(() => { + workspaceService['_workspace'] = workspace; + workspaceService['_roots'] = [fooDir]; + }); + + it('skip', async () => { + assertRemoved(await workspaceService.spliceRoots(0, 0)); + assertRoots('file:///foo'); + }); + + it('add', async () => { + assertRemoved(await workspaceService.spliceRoots(1, 0, new URI('file:///bar'))); + assertRoots('file:///foo', 'file:///bar'); + }); + + it('add dups', async () => { + assertRemoved(await workspaceService.spliceRoots(1, 0, new URI('file:///bar'), new URI('file:///baz'), new URI('file:///bar'))); + assertRoots('file:///foo', 'file:///bar', 'file:///baz'); + }); + + it('remove', async () => { + assertRemoved(await workspaceService.spliceRoots(0, 1), 'file:///foo'); + assertRoots(); + }); + + it('update', async () => { + assertRemoved(await workspaceService.spliceRoots(0, 1, new URI('file:///bar')), 'file:///foo'); + assertRoots('file:///bar'); + }); + + it('add untitled', async () => { + workspaceService['_workspace'] = fooDir; + assertRemoved(await workspaceService.spliceRoots(1, 0, new URI('file:///bar'))); + assertRoots('file:///foo', 'file:///bar'); + }); + }); + describe('getWorkspaceRootUri() function', () => { it('should return undefined if no uri is passed into the function', () => { expect(wsService.getWorkspaceRootUri(undefined)).to.be.undefined; diff --git a/packages/workspace/src/browser/workspace-service.ts b/packages/workspace/src/browser/workspace-service.ts index 697bca3a11eec..1eb008c301cb9 100644 --- a/packages/workspace/src/browser/workspace-service.ts +++ b/packages/workspace/src/browser/workspace-service.ts @@ -301,27 +301,7 @@ export class WorkspaceService implements FrontendApplicationContribution { * @param uri URI of the root folder being added */ async addRoot(uri: URI): Promise { - await this.roots; - - if (!this.opened) { - throw new Error('Folder cannot be added as there is no active workspace or opened folder.'); - } - const valid = await this.toValidRoot(uri); - if (!valid) { - throw new Error(`Invalid workspace root URI. Expected an existing directory location. URI: ${uri.toString()}.`); - } - - if (this._workspace && !this._roots.find(r => r.uri === valid.uri)) { - if (this._workspace.isDirectory) { // save the workspace data in a temporary file - const tempFile = await this.getTemporaryWorkspaceFile(); - if (tempFile) { - await this.save(tempFile); - } - } - const workspaceData = await this.getWorkspaceDataFromFile(); - this._workspace = await this.writeWorkspaceFile(this._workspace, - WorkspaceData.buildWorkspaceData([...this._roots, valid], workspaceData ? workspaceData.settings : undefined)); - } + await this.spliceRoots(this._roots.length, 0, uri); } /** @@ -342,6 +322,41 @@ export class WorkspaceService implements FrontendApplicationContribution { } } + async spliceRoots(start: number, deleteCount?: number, ...rootsToAdd: URI[]): Promise { + if (!this._workspace) { + throw new Error('There is not active workspace'); + } + const dedup = new Set(); + const roots = this._roots.map(root => (dedup.add(root.uri), root.uri)); + const toAdd: string[] = []; + for (const root of rootsToAdd) { + const uri = root.toString(); + if (!dedup.has(uri)) { + dedup.add(uri); + toAdd.push(uri); + } + } + const toRemove = roots.splice(start, deleteCount || 0, ...toAdd); + if (!toRemove.length && !toAdd.length) { + return []; + } + if (this._workspace.isDirectory) { + const utitledWorkspace = await this.getUntitledWorkspace(); + if (utitledWorkspace) { + await this.save(utitledWorkspace); + } + } + const currentData = await this.getWorkspaceDataFromFile(); + const newData = WorkspaceData.buildWorkspaceData(roots, currentData && currentData.settings); + await this.writeWorkspaceFile(this._workspace, newData); + return toRemove.map(root => new URI(root)); + } + + protected async getUntitledWorkspace(): Promise { + const home = await this.fileSystem.getCurrentUserHome(); + return home && getTemporaryWorkspaceFileUri(new URI(home.uri)); + } + private async writeWorkspaceFile(workspaceFile: FileStat | undefined, workspaceData: WorkspaceData): Promise { if (workspaceFile) { const data = JSON.stringify(WorkspaceData.transformToRelative(workspaceData, workspaceFile)); @@ -352,17 +367,6 @@ export class WorkspaceService implements FrontendApplicationContribution { } } - private async getTemporaryWorkspaceFile(): Promise { - const home = await this.fileSystem.getCurrentUserHome(); - if (home) { - const tempWorkspaceUri = getTemporaryWorkspaceFileUri(new URI(home.uri)); - if (!await this.fileSystem.exists(tempWorkspaceUri.toString())) { - return this.fileSystem.createFile(tempWorkspaceUri.toString()); - } - return this.toFileStat(tempWorkspaceUri); - } - } - /** * Clears current workspace root. */ diff --git a/tsconfig.json b/tsconfig.json index 9fd662f6241d0..3e2c0edd6da6a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -111,6 +111,15 @@ ], "@theia/getting-started/lib/*": [ "packages/getting-started/src/*" + ], + "@theia/file-search/lib/*": [ + "packages/file-search/src/*" + ], + "@theia/plugin-ext/lib/*": [ + "packages/plugin-ext/src/*" + ], + "@theia/plugin-ext-vscode/lib/*": [ + "packages/plugin-ext-vscode/src/*" ] }, "plugins": [ diff --git a/yarn.lock b/yarn.lock index 46a4b252fd7cc..73c0843a3c671 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,6 +121,7 @@ "@theia/node-pty@0.7.8-theia004": version "0.7.8-theia004" resolved "https://registry.yarnpkg.com/@theia/node-pty/-/node-pty-0.7.8-theia004.tgz#0fe31b958df9315352d5fbeea7075047cf69c935" + integrity sha512-GetaD2p1qVPq/xbNCHCwKYjIr9IWjksf9V2iiv/hV6f885cJ+ie0Osr4+C159PrwzGRYW2jQVUtXghBJoyOCLg== dependencies: nan "2.10.0" @@ -212,15 +213,24 @@ dependencies: "@types/node" "*" +"@types/formidable@^1.0.31": + version "1.0.31" + resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b" + integrity sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q== + dependencies: + "@types/events" "*" + "@types/node" "*" + "@types/fs-extra@^4.0.2": version "4.0.8" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.8.tgz#6957ddaf9173195199cb96da3db44c74700463d2" dependencies: "@types/node" "*" -"@types/fs-extra@^5.0.3": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" +"@types/fs-extra@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" + integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== dependencies: "@types/node" "*" @@ -232,10 +242,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/handlebars@^4.0.38": - version "4.0.39" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.39.tgz#961fb54db68030890942e6aeffe9f93a957807bd" - "@types/highlight.js@^9.12.2", "@types/highlight.js@^9.12.3": version "9.12.3" resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca" @@ -271,9 +277,10 @@ version "4.14.116" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9" -"@types/lodash@^4.14.110": - version "4.14.118" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.118.tgz#247bab39bfcc6d910d4927c6e06cbc70ec376f27" +"@types/lodash@^4.14.123": + version "4.14.123" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" + integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== "@types/markdown-it-anchor@^4.0.1": version "4.0.1" @@ -289,9 +296,10 @@ version "0.0.4" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.4.tgz#c5f67365916044b342dae8d702724788ba0b5b74" -"@types/marked@^0.4.0": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f" +"@types/marked@^0.6.0": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.6.4.tgz#7c1238662976dde4c74f89c0a94c573474edae02" + integrity sha512-rJe6ToeWAUmR6I7eY87gxb2CTwXKqfwfrkDAr9ododuha9D/d57DWWK0xdtKZ2C6yTdP60pAg5fJwC5PHguzyA== "@types/mime-types@^2.1.0": version "2.1.0" @@ -313,14 +321,15 @@ version "10.7.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.7.1.tgz#b704d7c259aa40ee052eec678758a68d07132a2e" -"@types/node@8.10.20": - version "8.10.20" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.20.tgz#fe674ea52e13950ab10954433a7824438aabbcac" - "@types/node@^8.0.24", "@types/node@^8.0.26": version "8.10.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.26.tgz#950e3d4e6b316ba6e1ae4e84d9155aba67f88c2f" +"@types/node@~10.3.6": + version "10.3.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.6.tgz#ea8aab9439b59f40d19ec5f13b44642344872b11" + integrity sha512-h7VDRFL8IhdPw1JjiNVvhr+WynfKW09q2BOflIOA0yeuXNeXBP1bIRuBrysSryH4keaJ5bYUNp63aIyQL9YpDQ== + "@types/p-debounce@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/p-debounce/-/p-debounce-1.0.0.tgz#c7fab3d61f9bc6454337c4aef0dec069456d00ee" @@ -395,9 +404,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/shelljs@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.0.tgz#0caa56b68baae4f68f44e0dd666ab30b098e3632" +"@types/shelljs@^0.8.3": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.4.tgz#b903e41ad5e5195b7d5e34d3cd33c94bdbd07895" + integrity sha512-UNACC6scKFVljWEvO3rHBkbbKXu3QkKPBOMCisxI7au9cnFK7tjOGPsKh5OjedAPLmtsKSarmk+YeehKTQSKtg== dependencies: "@types/glob" "*" "@types/node" "*" @@ -929,12 +939,19 @@ async@1.x, async@^1.4.0, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.0.0, async@^2.1.4, async@^2.5.0, async@^2.6.0: +async@^2.0.0, async@^2.5.0, async@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: lodash "^4.17.10" +async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" + integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== + dependencies: + lodash "^4.17.11" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1502,7 +1519,7 @@ babel-register@^6.26.0, babel-register@^6.9.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@~6.26.0: +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@~6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1554,6 +1571,13 @@ back@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/back/-/back-0.1.5.tgz#342b96b804657b03ec9a31f248a11f200608dcc2" +backbone@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" + integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== + dependencies: + underscore ">=1.8.3" + balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" @@ -1630,6 +1654,14 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493" + integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -1719,6 +1751,7 @@ browser-stdout@1.3.0: browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" @@ -2066,7 +2099,6 @@ check-error@^1.0.1: checksum@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9" - integrity sha1-3GUn1MkL6FYNvR7Uzs8yl9Uo6ek= dependencies: optimist "~0.3.5" @@ -2093,11 +2125,6 @@ chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" -chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== - chrome-trace-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" @@ -2363,13 +2390,14 @@ command-join@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/command-join/-/command-join-2.0.0.tgz#52e8b984f4872d952ff1bdc8b98397d27c7144cf" -commander@*, commander@^2.11.0, commander@^2.12.1, commander@^2.8.1, commander@^2.9.0: +commander@*, commander@^2.11.0, commander@^2.12.1, commander@^2.8.1: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" commander@2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== commander@2.6.0: version "2.6.0" @@ -2381,10 +2409,20 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" +commander@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" +commander@~2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + commander@~2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" @@ -2445,6 +2483,7 @@ concurrently@^3.5.0: conf@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/conf/-/conf-2.2.0.tgz#ee282efafc1450b61e205372041ad7d866802d9a" + integrity sha512-93Kz74FOMo6aWRVpAZsonOdl2I57jKtHrNmxhumehFQw4X8Sk37SohNY11PG7Q8Okta+UnrVaI006WLeyp8/XA== dependencies: dot-prop "^4.1.0" env-paths "^1.0.0" @@ -2822,6 +2861,7 @@ css-loader@~0.26.1: css-parse@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" + integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= dependencies: css "^2.0.0" @@ -2993,15 +3033,16 @@ debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.5. dependencies: ms "2.0.0" -debug@3.1.0, debug@^3.1.0: +debug@3.1.0, debug@^3.0.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -debug@^4.0.0: +debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" @@ -3272,6 +3313,7 @@ dot-prop@^3.0.0: dot-prop@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== dependencies: is-obj "^1.0.0" @@ -3304,6 +3346,7 @@ dugite-extra@0.1.11: dugite-no-gpl@1.69.0: version "1.69.0" resolved "https://registry.yarnpkg.com/dugite-no-gpl/-/dugite-no-gpl-1.69.0.tgz#bc9007cf5a595180f563ccc0e4f2cc80ebbaa52e" + integrity sha512-9NzPMyWW1uWEm+rEGivfQ0+zZ9soXrtk/zb6FIVpPa5CLoUdhMkLY4jHc0DDyayarxivJgrI/rHDdTUej4Zhrw== dependencies: checksum "^0.1.1" mkdirp "^0.5.1" @@ -3369,19 +3412,20 @@ ejs@~2.5.6: version "2.5.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5" -electron-download@^3.0.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-3.3.0.tgz#2cfd54d6966c019c4d49ad65fbe65cc9cdef68c8" +electron-download@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" + integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== dependencies: - debug "^2.2.0" - fs-extra "^0.30.0" - home-path "^1.0.1" + debug "^3.0.0" + env-paths "^1.0.0" + fs-extra "^4.0.1" minimist "^1.2.0" - nugget "^2.0.0" - path-exists "^2.1.0" - rc "^1.1.2" - semver "^5.3.0" - sumchecker "^1.2.0" + nugget "^2.0.1" + path-exists "^3.0.0" + rc "^1.2.1" + semver "^5.4.1" + sumchecker "^2.0.2" electron-mocha@~3.5.0: version "3.5.0" @@ -3411,6 +3455,7 @@ electron-rebuild@^1.5.11: electron-store@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-2.0.0.tgz#1035cca2a95409d1f54c7466606345852450d64a" + integrity sha512-1WCFYHsYvZBqDsoaS0Relnz0rd81ZkBAI0Fgx7Nq2UWU77rSNs1qxm4S6uH7TCZ0bV3LQpJFk7id/is/ZgoOPA== dependencies: conf "^2.0.0" @@ -3424,12 +3469,13 @@ electron-window@^0.8.0: dependencies: is-electron-renderer "^2.0.0" -electron@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/electron/-/electron-2.0.14.tgz#fad6766645e7c0cd10b4ae822d3167959735a870" +electron@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/electron/-/electron-3.1.7.tgz#2041031db272e88f167b2e5fe2de9eecabcf4632" + integrity sha512-rvmucnAsB4hQVdD0fOd1ad7+5u/BX1ak6emcSyPsLUk6rTqvVfOMk5ryC19h7Yd/5X8NWvCGkgYzSyQbgAJngA== dependencies: "@types/node" "^8.0.24" - electron-download "^3.0.1" + electron-download "^4.1.0" extract-zip "^1.0.3" elegant-spinner@^1.0.1: @@ -3468,9 +3514,10 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== dependencies: once "^1.4.0" @@ -3489,6 +3536,7 @@ entities@^1.1.1, entities@~1.1.1: env-paths@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" + integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= env-variable@0.0.x: version "0.0.4" @@ -3513,7 +3561,7 @@ error@^7.0.2: string-template "~0.2.1" xtend "~4.0.0" -es6-promise@^4.0.3, es6-promise@^4.0.5, es6-promise@^4.2.4: +es6-promise@^4.0.3, es6-promise@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" @@ -3888,9 +3936,12 @@ fecha@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" -fibers@~2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fibers/-/fibers-2.0.2.tgz#36db63ea61c543174e2264675fea8c2783371366" +fibers@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fibers/-/fibers-3.1.1.tgz#0238902ca938347bd779523692fbeefdf4f688ab" + integrity sha512-dl3Ukt08rHVQfY8xGD0ODwyjwrRALtaghuqGH2jByYX1wpY+nAnRQjJ6Dbqq0DnVgNVQ9yibObzbF4IlPyiwPw== + dependencies: + detect-libc "^1.0.3" figures@^1.7.0: version "1.7.0" @@ -3992,6 +4043,7 @@ find-git-exec@0.0.1-alpha.2, find-git-exec@^0.0.1-alpha.2: find-git-repositories@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/find-git-repositories/-/find-git-repositories-0.1.0.tgz#1ac886f0f54a11f5f073bca3bcdfddc03486305a" + integrity sha1-GsiG8PVKEfXwc7yjvN/dwDSGMFo= dependencies: nan "^2.0.0" @@ -4082,6 +4134,11 @@ formatio@1.2.0: dependencies: samsam "1.x" +formidable@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" + integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -4111,16 +4168,6 @@ fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" -fs-extra@^0.26.5: - version "0.26.7" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -4155,6 +4202,15 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -4531,6 +4587,7 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2 grapheme-splitter@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== grouped-queue@^0.3.3: version "0.3.3" @@ -4541,6 +4598,7 @@ grouped-queue@^0.3.3: growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== growl@1.9.2: version "1.9.2" @@ -4575,7 +4633,7 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handlebars@^4.0.1, handlebars@^4.0.11, handlebars@^4.0.2, handlebars@^4.0.6: +handlebars@^4.0.1, handlebars@^4.0.11, handlebars@^4.0.2: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: @@ -4585,6 +4643,17 @@ handlebars@^4.0.1, handlebars@^4.0.11, handlebars@^4.0.2, handlebars@^4.0.6: optionalDependencies: uglify-js "^2.6" +handlebars@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.1.tgz#6e4e41c18ebe7719ae4d38e5aca3d32fa3dd23d3" + integrity sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -4693,10 +4762,15 @@ he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" -highlight.js@^9.0.0, highlight.js@^9.12.0: +highlight.js@^9.12.0: version "9.12.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" +highlight.js@^9.13.1: + version "9.15.6" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4" + integrity sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ== + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4712,10 +4786,6 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -home-path@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.6.tgz#d549dc2465388a7f8667242c5b31588d29af29fc" - homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" @@ -4806,6 +4876,7 @@ https-proxy-agent@^2.2.1: humanize-duration@~3.15.0: version "3.15.3" resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.15.3.tgz#600a939bd9d9a16b696e907b3fc08d1a4f15e8c9" + integrity sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA== iconv-lite@0.4.19: version "0.4.19" @@ -5349,6 +5420,11 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +jquery@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02" + integrity sha1-LInWiJterFIqfuoywUUhVZxsvwI= + js-base64@^2.1.9: version "2.4.8" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.8.tgz#57a9b130888f956834aa40c5b165ba59c758f033" @@ -5505,6 +5581,7 @@ jsonc-parser@^2.0.0-next.1, jsonc-parser@^2.0.1: jsonc-parser@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d" + integrity sha512-TSU435K5tEKh3g7bam1AFf+uZrISheoDsLlpmAo6wWZYqjsnd09lHYK1Qo+moK4Ikifev1Gdpa69g4NELKnCrQ== jsonfile@^2.1.0: version "2.4.0" @@ -5667,6 +5744,7 @@ less-loader@~2.2.3: less@^3.0.3: version "3.9.0" resolved "https://registry.yarnpkg.com/less/-/less-3.9.0.tgz#b7511c43f37cf57dc87dffd9883ec121289b1474" + integrity sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w== dependencies: clone "^2.1.2" optionalDependencies: @@ -5975,6 +6053,11 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" +lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -6040,6 +6123,11 @@ lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +lunr@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.6.tgz#f278beee7ffd56ad86e6e478ce02ab2b98c78dd5" + integrity sha512-swStvEyDqQ85MGpABCMBclZcLI/pBIlu8FFDtmX197+oEgKloJ67QnB+Tidh0340HmLMs39c4GrkPY3cmkXp6Q== + make-dir@^1.0.0, make-dir@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -6088,9 +6176,10 @@ markdown-it@^8.4.0: mdurl "^1.0.1" uc.micro "^1.0.5" -marked@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66" +marked@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.1.tgz#a63addde477bca9613028de4b2bc3629e53a0562" + integrity sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA== math-expression-evaluator@^1.2.14: version "1.2.17" @@ -6269,6 +6358,7 @@ mime@1.4.1: mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mime@^2.0.3: version "2.3.1" @@ -6326,27 +6416,12 @@ minipass@^2.2.1, minipass@^2.3.3: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^2.3.4: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minizlib@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" dependencies: minipass "^2.2.1" -minizlib@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - mississippi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" @@ -6392,9 +6467,10 @@ mocha@^3.4.2: mkdirp "0.5.1" supports-color "3.1.2" -mocha@^5.0.0: +mocha@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== dependencies: browser-stdout "1.3.1" commander "2.15.1" @@ -6558,6 +6634,11 @@ neo-async@^2.5.0: version "2.5.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc" +neo-async@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== + nice-try@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" @@ -6572,15 +6653,10 @@ nise@^1.0.1: path-to-regexp "^1.7.0" text-encoding "^0.6.4" -node-abi@^2.0.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.3.tgz#43666b7b17e57863e572409edbb82115ac7af28b" - dependencies: - semver "^5.4.1" - -node-abi@^2.2.0: - version "2.4.4" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.4.4.tgz#410d8968809fe616dc078a181c44a370912f12fd" +node-abi@^2.0.0, node-abi@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.5.0.tgz#942e1a78bce764bc0c1672d5821e492b9d032052" + integrity sha512-9g2twBGSP6wIR5PW7tXvAWnEWKJDH/VskdXp168xsw9VVxpEGov8K4jsP4/VeoC7b2ZAyzckvMCuQuQlw44lXg== dependencies: semver "^5.4.1" @@ -6655,12 +6731,6 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -nodegit-promise@~4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/nodegit-promise/-/nodegit-promise-4.0.0.tgz#5722b184f2df7327161064a791d2e842c9167b34" - dependencies: - asap "~2.0.3" - nomnom@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7" @@ -6763,9 +6833,20 @@ npm-run-path@^2.0.0: gauge "~2.7.3" set-blocking "~2.0.0" -nugget@^2.0.0: +nsfw@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/nsfw/-/nsfw-1.2.2.tgz#95b79b6b0e311268aaa20c5c085b9f3b341b0769" + integrity sha512-YwoS39dkrp6loO0gvh61UbQPiOYwmbAiKqWSYuMeoSkpxxy8rbe/RVgxIJ1L+ua5usLGr0FPSo7NEQnDQOGyIw== + dependencies: + fs-extra "^7.0.0" + lodash.isinteger "^4.0.4" + lodash.isundefined "^3.0.1" + nan "^2.0.0" + +nugget@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" + integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= dependencies: debug "^2.1.3" minimist "^1.1.0" @@ -6929,7 +7010,6 @@ optimist@^0.6.1, optimist@~0.6.1: optimist@~0.3.5: version "0.3.7" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" - integrity sha1-yQlBrVnkJzMokjB00s8ufLxuwNk= dependencies: wordwrap "~0.0.2" @@ -7146,7 +7226,7 @@ path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" -path-exists@^2.0.0, path-exists@^2.1.0: +path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" dependencies: @@ -7271,6 +7351,7 @@ pkg-dir@^2.0.0: pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= dependencies: find-up "^2.1.0" @@ -7607,14 +7688,20 @@ progress-stream@^1.1.0: speedometer "~0.1.2" through2 "~0.2.3" -progress@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" +progress@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + prom-client@^10.2.0: version "10.2.3" resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-10.2.3.tgz#a51bf21c239c954a6c5be4b1361fdd380218bb41" @@ -7635,12 +7722,6 @@ promised-io@*: version "0.3.5" resolved "https://registry.yarnpkg.com/promised-io/-/promised-io-0.3.5.tgz#4ad217bb3658bcaae9946b17a8668ecd851e1356" -promisify-node@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/promisify-node/-/promisify-node-0.3.0.tgz#b4b55acf90faa7d2b8b90ca396899086c03060cf" - dependencies: - nodegit-promise "~4.0.0" - prop-types@^15.6.0: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" @@ -7806,7 +7887,7 @@ raw-body@2.3.3: iconv-lite "0.4.23" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.1, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" dependencies: @@ -7927,6 +8008,15 @@ readable-stream@1.0.x: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -8257,6 +8347,7 @@ ret@~0.1.10: rgb2hex@^0.1.9: version "0.1.9" resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.1.9.tgz#5d3e0e14b0177b568e6f0d5b43e34fbfdb670346" + integrity sha512-32iuQzhOjyT+cv9aAFRBJ19JgHwzQwbjUhH3Fj2sWW2EEGAW8fpFrDFP5ndoKDxJaLO06x1hE3kyuIFrUQtybQ== right-align@^0.1.1: version "0.1.3" @@ -8410,22 +8501,23 @@ seek-bzip@^1.0.5: commander "~2.8.1" selenium-standalone@^6.15.4: - version "6.15.4" - resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.15.4.tgz#9f9056f625bd7d2558483562b3e8be80947e9faf" + version "6.16.0" + resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.16.0.tgz#ffcf02665c58ff7a7472427ae819ba79c15967ac" + integrity sha512-tl7HFH2FOxJD1is7Pzzsl0pY4vuePSdSWiJdPn+6ETBkpeJDiuzou8hBjvWYWpD+eIVcOrmy3L0R3GzkdHLzDw== dependencies: - async "^2.1.4" - commander "^2.9.0" - cross-spawn "^6.0.0" - debug "^4.0.0" - lodash "^4.17.4" + async "^2.6.2" + commander "^2.19.0" + cross-spawn "^6.0.5" + debug "^4.1.1" + lodash "^4.17.11" minimist "^1.2.0" mkdirp "^0.5.1" - progress "2.0.1" + progress "2.0.3" request "2.88.0" - tar-stream "1.6.2" - urijs "^1.18.4" - which "^1.2.12" - yauzl "^2.5.0" + tar-stream "2.0.0" + urijs "^1.19.1" + which "^1.3.1" + yauzl "^2.10.0" "semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: version "5.5.1" @@ -8542,7 +8634,7 @@ shell-path@^2.0.0: dependencies: shell-env "^0.3.0" -shelljs@^0.8.0, shelljs@^0.8.2: +shelljs@^0.8.0: version "0.8.2" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.2.tgz#345b7df7763f4c2340d584abb532c5f752ca9e35" dependencies: @@ -8550,6 +8642,15 @@ shelljs@^0.8.0, shelljs@^0.8.2: interpret "^1.0.0" rechoir "^0.6.2" +shelljs@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" + integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + showdown@^1.7.4: version "1.8.6" resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.8.6.tgz#91ea4ee3b7a5448aaca6820a4e27e690c6ad771c" @@ -8921,6 +9022,13 @@ string_decoder@^1.0.0, string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -9005,12 +9113,12 @@ style-loader@~0.13.1: dependencies: loader-utils "^1.0.2" -sumchecker@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d" +sumchecker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" + integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= dependencies: debug "^2.2.0" - es6-promise "^4.0.5" supports-color@3.1.2: version "3.1.2" @@ -9021,6 +9129,7 @@ supports-color@3.1.2: supports-color@5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== dependencies: has-flag "^3.0.0" @@ -9087,17 +9196,16 @@ tar-fs@^1.13.0, tar-fs@^1.16.2: pump "^1.0.0" tar-stream "^1.1.2" -tar-stream@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" +tar-stream@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.0.0.tgz#8829bbf83067bc0288a9089db49c56be395b6aea" + integrity sha512-n2vtsWshZOVr/SY4KtslPoUlyNh06I2SGgAOCZmquCEjlbV/LjY2CY80rDtdQRHFOYXNlgBDo6Fr3ww2CWPOtA== dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" + bl "^2.2.0" + end-of-stream "^1.4.1" fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" tar-stream@^1.1.2, tar-stream@^1.5.0, tar-stream@^1.5.2: version "1.6.1" @@ -9119,7 +9227,7 @@ tar@^2.0.0: fstream "^1.0.2" inherits "2" -tar@^4, tar@^4.0.0: +tar@^4, tar@^4.0.0, tar@^4.0.2: version "4.4.6" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" dependencies: @@ -9131,19 +9239,6 @@ tar@^4, tar@^4.0.0: safe-buffer "^5.1.2" yallist "^3.0.2" -tar@^4.0.2: - version "4.4.8" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" - integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.4" - minizlib "^1.1.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - tasklist@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/tasklist/-/tasklist-3.1.1.tgz#84cb49f8359b9ed0451dd1d9b6111da18107dbd5" @@ -9292,7 +9387,7 @@ to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" -to-buffer@^1.1.0, to-buffer@^1.1.1: +to-buffer@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" @@ -9455,31 +9550,42 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typedoc-default-themes@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227" +typedoc-default-themes@^0.6.0-0: + version "0.6.0-0" + resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.6.0-0.tgz#a4867eaf91fb7888efd01680f1328b72e8a33640" + integrity sha512-O7hBMS1yBCozvVUntIIdlBk04WiqM+f6NOEc9p+LimJSFKJMF66cgzejeiybuTk6mgbMJW+olg42BNYC8E9x9Q== + dependencies: + backbone "^1.1.2" + jquery "^2.2.4" + lunr "^2.3.6" + underscore "^1.9.1" -typedoc@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.13.0.tgz#9efdf352bd54873955cd161bd4b75f24a8c59dde" +typedoc-plugin-external-module-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typedoc-plugin-external-module-map/-/typedoc-plugin-external-module-map-1.0.0.tgz#7021d0e2bc9a98b7266f4ea2eab593b7c63802ce" + integrity sha512-OtlTOmanX0yqRYUVLBuGSBjrffLLAjWNn8mqh6k6FkvfXAIIe3Yfg0kCeKZDN/65v4dt3MJ9AuGXTGLPue3Kqg== + +typedoc@^0.15.0-0: + version "0.15.0-0" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.15.0-0.tgz#4d0acd8697c22824fb51fff68766fd435b99163d" + integrity sha512-N43CSq6T22MVrP1kb0OYusgwnUniwuh9vGVmtgTCjvTSkuJjdMyMeJPMfnugmfRIWxuP9pO6wvNhdDRG32+EQA== dependencies: - "@types/fs-extra" "^5.0.3" - "@types/handlebars" "^4.0.38" + "@types/fs-extra" "^5.0.5" "@types/highlight.js" "^9.12.3" - "@types/lodash" "^4.14.110" - "@types/marked" "^0.4.0" + "@types/lodash" "^4.14.123" + "@types/marked" "^0.6.0" "@types/minimatch" "3.0.3" - "@types/shelljs" "^0.8.0" - fs-extra "^7.0.0" - handlebars "^4.0.6" - highlight.js "^9.0.0" - lodash "^4.17.10" - marked "^0.4.0" + "@types/shelljs" "^0.8.3" + fs-extra "^7.0.1" + handlebars "^4.1.0" + highlight.js "^9.13.1" + lodash "^4.17.11" + marked "^0.6.0" minimatch "^3.0.0" - progress "^2.0.0" - shelljs "^0.8.2" - typedoc-default-themes "^0.5.0" - typescript "3.1.x" + progress "^2.0.3" + shelljs "^0.8.3" + typedoc-default-themes "^0.6.0-0" + typescript "3.3.x" typescript-language-server@^0.3.7: version "0.3.7" @@ -9493,9 +9599,10 @@ typescript-language-server@^0.3.7: vscode-languageserver "^4.4.0" vscode-uri "^1.0.5" -typescript@3.1.x: - version "3.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" +typescript@3.3.x: + version "3.3.4000" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0" + integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA== typescript@^3.1.3: version "3.1.3" @@ -9529,6 +9636,14 @@ uglify-js@^2.6: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.1.4: + version "3.5.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.3.tgz#d490bb5347f23025f0c1bc0dee901d98e4d6b063" + integrity sha512-rIQPT2UMDnk4jRX+w4WO84/pebU2jiLsjgIyrCktYgSvx28enOE3iYQMr+BD1rHiitWnDmpu0cY/LfIEpKcjcw== + dependencies: + commander "~2.19.0" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -9561,6 +9676,11 @@ unbzip2-stream@^1.0.9: buffer "^3.0.1" through "^2.3.6" +underscore@>=1.8.3, underscore@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== + underscore@~1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" @@ -9640,9 +9760,10 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urijs@^1.18.4: +urijs@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.1.tgz#5b0ff530c0cbde8386f6342235ba5ca6e995d25a" + integrity sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg== urix@^0.1.0: version "0.1.0" @@ -9689,7 +9810,7 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -9850,16 +9971,6 @@ vscode-nls@^3.2.2, vscode-nls@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" -vscode-nsfw@^1.0.17: - version "1.0.17" - resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.0.17.tgz#da3820f26aea3a7e95cadc54bd9e5dae3d47e474" - dependencies: - fs-extra "^0.26.5" - lodash.isinteger "^4.0.4" - lodash.isundefined "^3.0.1" - nan "^2.0.0" - promisify-node "^0.3.0" - vscode-ripgrep@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.2.4.tgz#b3cfbe08ed13f6cf6b134147ea4d982970ab4f70" @@ -9910,17 +10021,19 @@ wdio-dot-reporter@~0.0.8: version "0.0.10" resolved "https://registry.yarnpkg.com/wdio-dot-reporter/-/wdio-dot-reporter-0.0.10.tgz#facfb7c9c5984149951f59cbc3cd0752101cf0e0" -wdio-mocha-framework@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/wdio-mocha-framework/-/wdio-mocha-framework-0.5.13.tgz#f4da119456cb673b8c058fb60936132ec752a9d4" +wdio-mocha-framework@0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/wdio-mocha-framework/-/wdio-mocha-framework-0.6.4.tgz#291b05b5f8735716023e1228e461f66ff2e7e1c9" + integrity sha512-GZsXwoW60/fkkfqZJR/ZAdiALaM+hW+BbnTT9x214qPR4Pe5XeyYxhJNEdyf0dNI9625cMdkyZYaWoFHN5zDcA== dependencies: babel-runtime "^6.23.0" - mocha "^5.0.0" - wdio-sync "0.7.1" + mocha "^5.2.0" + wdio-sync "0.7.3" wdio-selenium-standalone-service@0.0.12: version "0.0.12" resolved "https://registry.yarnpkg.com/wdio-selenium-standalone-service/-/wdio-selenium-standalone-service-0.0.12.tgz#f472d00d3a7800b2dbedb781bff0f5e726a21e9d" + integrity sha512-R8iUL30SkFfZictAG5wRofeCsHQ4bIucDtaArCQWZkUqS+DlGTStIk3TaIOCaX7dS7UW1YN/lJt9Vsn4Ekmoxg== dependencies: fs-extra "^0.30.0" selenium-standalone "^6.15.4" @@ -9928,22 +10041,25 @@ wdio-selenium-standalone-service@0.0.12: wdio-spec-reporter@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz#6d6f865deac6b36f96988c1204cc81099b75fc7e" + integrity sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg== dependencies: babel-runtime "~6.26.0" chalk "^2.3.0" humanize-duration "~3.15.0" -wdio-sync@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/wdio-sync/-/wdio-sync-0.7.1.tgz#00847fbbce16826c3225618f4259d28b60a42483" +wdio-sync@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/wdio-sync/-/wdio-sync-0.7.3.tgz#858c7439c18c0dbdcd2e25e29db8a0ea2f34bc04" + integrity sha512-ukASSHOQmOxaz5HTILR0jykqlHBtAPsBpMtwhpiG0aW9uc7SO7PF+E5LhVvTG4ypAh+UGmY3rTjohOsqDr39jw== dependencies: - babel-runtime "6.26.0" - fibers "~2.0.0" + babel-runtime "^6.26.0" + fibers "^3.0.0" object.assign "^4.0.3" webdriverio@4.14.1: version "4.14.1" resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-4.14.1.tgz#50fdb010d37233c77c48e5f0497a63ab875cdfc1" + integrity sha512-Gjb5ft6JtO7WdoZifedeM6U941UZi03IlG0t3Xq9M9SxSm6FuyqMEmNZ4HI3UcBRkSbWxdOWGAvpFShYxVr7iA== dependencies: archiver "~2.1.0" babel-runtime "^6.26.0" @@ -10106,7 +10222,7 @@ which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" -which@1, which@^1.1.1, which@^1.2.12, which@^1.2.14, which@^1.2.8, which@^1.2.9, which@^1.3.0: +which@1, which@^1.1.1, which@^1.2.14, which@^1.2.8, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: @@ -10256,6 +10372,7 @@ xtend@~2.1.1: xterm@3.9.2: version "3.9.2" resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.9.2.tgz#e94bfbb84217b19bc1c16ed43d303b8245c9313d" + integrity sha512-fpQJQFTosY97EK4eB7UOrlFAwwqv1rSqlXgttEVD0S1v4MlevsUkRwrM/ew5X73jQXc+vdglRtccIhcXg5wtGg== y18n@^3.2.1: version "3.2.1" @@ -10388,9 +10505,10 @@ yauzl@2.4.1: dependencies: fd-slicer "~1.0.1" -yauzl@^2.4.2, yauzl@^2.5.0: +yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0"