diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 71fd646776f..c0154336692 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -3,36 +3,1329 @@ "kind": 1, "language": "markdown", "value": "#### Config", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"February 2021\"", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "### Finalization", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repo $milestone label:api-finalization", - "editable": true + "editable": true, + "outputs": [ + { + "mime": "text/markdown", + "value": "- [#88309](https://github.com/microsoft/vscode/issues/88309 \"Authentication Provider API\") Authentication Provider API [api-finalization, authentication, feature-request, settings-sync]- [@RMacfarlane](https://github.com/RMacfarlane \"Issue 88309 is assigned to RMacfarlane\")\n\n" + }, + { + "mime": "x-application/github-issues", + "value": [ + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/88309", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/88309/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/88309/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/88309/events", + "html_url": "https://github.com/microsoft/vscode/issues/88309", + "id": 547141160, + "node_id": "MDU6SXNzdWU1NDcxNDExNjA=", + "number": 88309, + "title": "Authentication Provider API", + "user": { + "login": "RMacfarlane", + "id": 3672607, + "node_id": "MDQ6VXNlcjM2NzI2MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/3672607?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RMacfarlane", + "html_url": "https://github.com/RMacfarlane", + "followers_url": "https://api.github.com/users/RMacfarlane/followers", + "following_url": "https://api.github.com/users/RMacfarlane/following{/other_user}", + "gists_url": "https://api.github.com/users/RMacfarlane/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RMacfarlane/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RMacfarlane/subscriptions", + "organizations_url": "https://api.github.com/users/RMacfarlane/orgs", + "repos_url": "https://api.github.com/users/RMacfarlane/repos", + "events_url": "https://api.github.com/users/RMacfarlane/events{/privacy}", + "received_events_url": "https://api.github.com/users/RMacfarlane/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 974714207, + "node_id": "MDU6TGFiZWw5NzQ3MTQyMDc=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-finalization", + "name": "api-finalization", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 1702048079, + "node_id": "MDU6TGFiZWwxNzAyMDQ4MDc5", + "url": "https://api.github.com/repos/microsoft/vscode/labels/authentication", + "name": "authentication", + "color": "c5def5", + "default": false, + "description": "Authentication issues" + }, + { + "id": 272689392, + "node_id": "MDU6TGFiZWwyNzI2ODkzOTI=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/feature-request", + "name": "feature-request", + "color": "dcdcdc", + "default": false, + "description": "Request for new features or functionality" + }, + { + "id": 1684021718, + "node_id": "MDU6TGFiZWwxNjg0MDIxNzE4", + "url": "https://api.github.com/repos/microsoft/vscode/labels/settings-sync", + "name": "settings-sync", + "color": "1d76db", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "RMacfarlane", + "id": 3672607, + "node_id": "MDQ6VXNlcjM2NzI2MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/3672607?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RMacfarlane", + "html_url": "https://github.com/RMacfarlane", + "followers_url": "https://api.github.com/users/RMacfarlane/followers", + "following_url": "https://api.github.com/users/RMacfarlane/following{/other_user}", + "gists_url": "https://api.github.com/users/RMacfarlane/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RMacfarlane/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RMacfarlane/subscriptions", + "organizations_url": "https://api.github.com/users/RMacfarlane/orgs", + "repos_url": "https://api.github.com/users/RMacfarlane/repos", + "events_url": "https://api.github.com/users/RMacfarlane/events{/privacy}", + "received_events_url": "https://api.github.com/users/RMacfarlane/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "RMacfarlane", + "id": 3672607, + "node_id": "MDQ6VXNlcjM2NzI2MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/3672607?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RMacfarlane", + "html_url": "https://github.com/RMacfarlane", + "followers_url": "https://api.github.com/users/RMacfarlane/followers", + "following_url": "https://api.github.com/users/RMacfarlane/following{/other_user}", + "gists_url": "https://api.github.com/users/RMacfarlane/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RMacfarlane/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RMacfarlane/subscriptions", + "organizations_url": "https://api.github.com/users/RMacfarlane/orgs", + "repos_url": "https://api.github.com/users/RMacfarlane/repos", + "events_url": "https://api.github.com/users/RMacfarlane/events{/privacy}", + "received_events_url": "https://api.github.com/users/RMacfarlane/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 23, + "created_at": "2020-01-08T22:31:35Z", + "updated_at": "2021-02-10T20:41:11Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "### Problem\r\n\r\nThere are currently some extensions that attempt to provide authentication abilities that can be reused by other extensions. (An example being the Azure Account extension). Now that we've begun working on login for settings sync, it's worth revisiting if authentication should be a first-class concept in VS Code. By exposing an API to contribute an authentication flow\r\n\r\n- the core of VSCode can potentially leverage authentication\r\n- other extensions can leverage authentication\r\n- UI for account management could be centralized\r\n\r\n### Proposal\r\n\r\nI propose introducing a concept of an \"AuthenticationProvider\". Such a provider implements methods for logging in and logging out of a specified account, and exposes a list of accounts that are currently available with an event listener for changes to these. This abstracts away refreshing tokens from consumers - the AuthenticationProvider extension can manage refreshing in the background and fire an event when the accessToken has been changed.\r\n\r\n```ts\r\nexport interface Account {\r\n\treadonly id: string;\r\n\treadonly accessToken: string;\r\n\treadonly displayName: string;\r\n}\r\n\r\nexport interface AuthenticationProvider {\r\n\treadonly id: string; // perhaps \"type\"? Would be something like \"GitHub\", \"MSA\", etc.\r\n\treadonly displayName: string;\r\n\r\n\taccounts: ReadonlyArray;\r\n\tonDidChangeAccounts: Event>;\r\n\r\n\tlogin(): Promise;\r\n\tlogout(accountId: string): Promise;\r\n}\r\n\r\nexport namespace authentication {\r\n\texport function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable;\r\n\texport const authenticationProviders: ReadonlyArray;\r\n}\r\n```\r\n\r\nConsumers would need to know the id of the provider they're looking for. For example, the settings sync code would look for an \"MSA\" provider since this is what the setting sync backend currently needs.\r\n\r\nSince the authentication provider extension would be activated in each VS Code window, the extension would be responsible for synchronizing state across instances. By default, such extensions would have [\"ui\", \"workspace\"] extensionKind, so that they can store and read credentials on the local machine in both the desktop and web case.", + "performed_via_github_app": null, + "score": 1 + } + ] + } + ] }, { "kind": 1, "language": "markdown", "value": "### Proposals", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repo $milestone is:open label:api-proposal ", - "editable": true + "editable": true, + "outputs": [ + { + "mime": "text/markdown", + "value": "- [#115631](https://github.com/microsoft/vscode/issues/115631 \"Provide a way for custom editors to process untitled files without relying on textDocument\") Provide a way for custom editors to process untitled files without relying on textDocument [api-proposal, custom-editors, notebook]- [@lramos15](https://github.com/lramos15 \"Issue 115631 is assigned to lramos15\")\n\n- [#115626](https://github.com/microsoft/vscode/issues/115626 \"Microsoft Auth Provider should support overriding client id and tenant id\") Microsoft Auth Provider should support overriding client id and tenant id [api-proposal, authentication]- [@TylerLeonhardt](https://github.com/TylerLeonhardt \"Issue 115626 is assigned to TylerLeonhardt\")\n\n- [#115616](https://github.com/microsoft/vscode/issues/115616 \"Provide extension API to exclude ports from forwarding\") Provide extension API to exclude ports from forwarding [api, api-proposal, feature-request, remote-explorer]\n- [#114123](https://github.com/microsoft/vscode/issues/114123 \"Resolve the conflict run button in editor context menu\") Resolve the conflict run button in editor context menu [api-proposal, feature-request, menus]- [@jrieken](https://github.com/jrieken \"Issue 114123 is assigned to jrieken\")\n\n- [#109277](https://github.com/microsoft/vscode/issues/109277 \"Let extensions hook into url opening\") Let extensions hook into url opening [api, api-proposal, under-discussion]- [@mjbvz](https://github.com/mjbvz \"Issue 109277 is assigned to mjbvz\")\n\n- [#107467](https://github.com/microsoft/vscode/issues/107467 \"Testing in VS Code\") Testing in VS Code [api-proposal, plan-item, under-discussion]- [@connor4312](https://github.com/connor4312 \"Issue 107467 is assigned to connor4312\")\n\n- [#105690](https://github.com/microsoft/vscode/issues/105690 \"Extension API for Inline Values\") Extension API for Inline Values [api, api-proposal, debug, feature-request]- [@weinand](https://github.com/weinand \"Issue 105690 is assigned to weinand\")\n\n" + }, + { + "mime": "x-application/github-issues", + "value": [ + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/115631", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/115631/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/115631/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/115631/events", + "html_url": "https://github.com/microsoft/vscode/issues/115631", + "id": 799606785, + "node_id": "MDU6SXNzdWU3OTk2MDY3ODU=", + "number": 115631, + "title": "Provide a way for custom editors to process untitled files without relying on textDocument", + "user": { + "login": "lramos15", + "id": 4544166, + "node_id": "MDQ6VXNlcjQ1NDQxNjY=", + "avatar_url": "https://avatars.githubusercontent.com/u/4544166?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/lramos15", + "html_url": "https://github.com/lramos15", + "followers_url": "https://api.github.com/users/lramos15/followers", + "following_url": "https://api.github.com/users/lramos15/following{/other_user}", + "gists_url": "https://api.github.com/users/lramos15/gists{/gist_id}", + "starred_url": "https://api.github.com/users/lramos15/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/lramos15/subscriptions", + "organizations_url": "https://api.github.com/users/lramos15/orgs", + "repos_url": "https://api.github.com/users/lramos15/repos", + "events_url": "https://api.github.com/users/lramos15/events{/privacy}", + "received_events_url": "https://api.github.com/users/lramos15/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 1713330180, + "node_id": "MDU6TGFiZWwxNzEzMzMwMTgw", + "url": "https://api.github.com/repos/microsoft/vscode/labels/custom-editors", + "name": "custom-editors", + "color": "c5def5", + "default": false, + "description": "Custom editor API (webview based editors)" + }, + { + "id": 1839857516, + "node_id": "MDU6TGFiZWwxODM5ODU3NTE2", + "url": "https://api.github.com/repos/microsoft/vscode/labels/notebook", + "name": "notebook", + "color": "c5def5", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "lramos15", + "id": 4544166, + "node_id": "MDQ6VXNlcjQ1NDQxNjY=", + "avatar_url": "https://avatars.githubusercontent.com/u/4544166?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/lramos15", + "html_url": "https://github.com/lramos15", + "followers_url": "https://api.github.com/users/lramos15/followers", + "following_url": "https://api.github.com/users/lramos15/following{/other_user}", + "gists_url": "https://api.github.com/users/lramos15/gists{/gist_id}", + "starred_url": "https://api.github.com/users/lramos15/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/lramos15/subscriptions", + "organizations_url": "https://api.github.com/users/lramos15/orgs", + "repos_url": "https://api.github.com/users/lramos15/repos", + "events_url": "https://api.github.com/users/lramos15/events{/privacy}", + "received_events_url": "https://api.github.com/users/lramos15/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "lramos15", + "id": 4544166, + "node_id": "MDQ6VXNlcjQ1NDQxNjY=", + "avatar_url": "https://avatars.githubusercontent.com/u/4544166?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/lramos15", + "html_url": "https://github.com/lramos15", + "followers_url": "https://api.github.com/users/lramos15/followers", + "following_url": "https://api.github.com/users/lramos15/following{/other_user}", + "gists_url": "https://api.github.com/users/lramos15/gists{/gist_id}", + "starred_url": "https://api.github.com/users/lramos15/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/lramos15/subscriptions", + "organizations_url": "https://api.github.com/users/lramos15/orgs", + "repos_url": "https://api.github.com/users/lramos15/repos", + "events_url": "https://api.github.com/users/lramos15/events{/privacy}", + "received_events_url": "https://api.github.com/users/lramos15/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "mjbvz", + "id": 12821956, + "node_id": "MDQ6VXNlcjEyODIxOTU2", + "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mjbvz", + "html_url": "https://github.com/mjbvz", + "followers_url": "https://api.github.com/users/mjbvz/followers", + "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", + "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", + "organizations_url": "https://api.github.com/users/mjbvz/orgs", + "repos_url": "https://api.github.com/users/mjbvz/repos", + "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", + "received_events_url": "https://api.github.com/users/mjbvz/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 1, + "created_at": "2021-02-02T19:29:05Z", + "updated_at": "2021-02-02T21:58:36Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "Currently the \"Reopen with\" experience for untitled files and custom binary editors needs better support. See #114711. After discussion in the API call the best proposal seems to be placing the untitled file data in the OpenEditor / OpenNotebook context. There interface would be modified as shown:\r\n```ts\r\n\t/**\r\n\t * Additional information about the opening custom document.\r\n\t */\r\n\tinterface CustomDocumentOpenContext {\r\n\t\t/**\r\n\t\t * The id of the backup to restore the document from or `undefined` if there is no backup.\r\n\t\t *\r\n\t\t * If this is provided, your extension should restore the editor from the backup instead of reading the file\r\n\t\t * from the user's workspace.\r\n\t\t */\r\n\t\treadonly backupId?: string;\r\n\t\t/**\r\n\t\t * If the URI is an untitled file, this will be populated with the byte data of that file\r\n\t\t *\r\n\t\t * If this is provided, your extension should utilize this byte data rather than executing fs APIs on the URI passed in\r\n\t\t */\r\n\t\treadonly untitledDocumentData?: Uint8Array;\r\n\t}\r\n\r\n\tinterface NotebookDocumentOpenContext {\r\n\t\treadonly backupId?: string;\r\n\t\treadonly untitledDocumentData?: Uint8Array;\r\n\t}\r\n```\r\nThe extension other would then not be required to resolve the URI to a text document (which would have been disposed of). ", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/115626", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/115626/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/115626/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/115626/events", + "html_url": "https://github.com/microsoft/vscode/issues/115626", + "id": 799566516, + "node_id": "MDU6SXNzdWU3OTk1NjY1MTY=", + "number": 115626, + "title": "Microsoft Auth Provider should support overriding client id and tenant id", + "user": { + "login": "TylerLeonhardt", + "id": 2644648, + "node_id": "MDQ6VXNlcjI2NDQ2NDg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TylerLeonhardt", + "html_url": "https://github.com/TylerLeonhardt", + "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", + "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", + "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", + "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", + "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", + "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", + "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 1702048079, + "node_id": "MDU6TGFiZWwxNzAyMDQ4MDc5", + "url": "https://api.github.com/repos/microsoft/vscode/labels/authentication", + "name": "authentication", + "color": "c5def5", + "default": false, + "description": "Authentication issues" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "TylerLeonhardt", + "id": 2644648, + "node_id": "MDQ6VXNlcjI2NDQ2NDg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TylerLeonhardt", + "html_url": "https://github.com/TylerLeonhardt", + "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", + "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", + "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", + "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", + "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", + "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", + "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "TylerLeonhardt", + "id": 2644648, + "node_id": "MDQ6VXNlcjI2NDQ2NDg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2644648?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TylerLeonhardt", + "html_url": "https://github.com/TylerLeonhardt", + "followers_url": "https://api.github.com/users/TylerLeonhardt/followers", + "following_url": "https://api.github.com/users/TylerLeonhardt/following{/other_user}", + "gists_url": "https://api.github.com/users/TylerLeonhardt/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TylerLeonhardt/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TylerLeonhardt/subscriptions", + "organizations_url": "https://api.github.com/users/TylerLeonhardt/orgs", + "repos_url": "https://api.github.com/users/TylerLeonhardt/repos", + "events_url": "https://api.github.com/users/TylerLeonhardt/events{/privacy}", + "received_events_url": "https://api.github.com/users/TylerLeonhardt/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "RMacfarlane", + "id": 3672607, + "node_id": "MDQ6VXNlcjM2NzI2MDc=", + "avatar_url": "https://avatars.githubusercontent.com/u/3672607?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RMacfarlane", + "html_url": "https://github.com/RMacfarlane", + "followers_url": "https://api.github.com/users/RMacfarlane/followers", + "following_url": "https://api.github.com/users/RMacfarlane/following{/other_user}", + "gists_url": "https://api.github.com/users/RMacfarlane/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RMacfarlane/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RMacfarlane/subscriptions", + "organizations_url": "https://api.github.com/users/RMacfarlane/orgs", + "repos_url": "https://api.github.com/users/RMacfarlane/repos", + "events_url": "https://api.github.com/users/RMacfarlane/events{/privacy}", + "received_events_url": "https://api.github.com/users/RMacfarlane/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 0, + "created_at": "2021-02-02T18:42:12Z", + "updated_at": "2021-02-02T18:58:50Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "\r\n\r\n\r\n\r\n\r\n\r\nThe Microsoft Auth Provider uses a specific AAD application with client id hardcoded here:\r\nhttps://github.com/microsoft/vscode/blob/582ea371c2bf785d88458dab95828387ad94a63d/extensions/microsoft-authentication/src/AADHelper.ts#L25-L26\r\n\r\nHowever, this application only has access to a handful of scopes, and to add _allowed_ scopes to this client id is a manual process (which for an external extension author means opening an issue here and then having one of us add that scope to the _allowed_ scopes for the application)\r\n\r\nAs an extension author, I should easily be able to create my own AAD application (in the Azure Portal for example) and use that client id instead of the one vscode uses so that I can have control over the scopes I care about and, if this exists, I can get telemetry when my client id is used.\r\n\r\nSince we have abstracted auth providers, I think it's fitting to be able to pass additional auth provider specific options down to an auth provider. For example, the Microsoft auth provider would take a client id and tenant that would replace the hard coded string above.\r\n\r\nProposal:\r\n\r\n```ts\r\n /**\r\n\t * Options to be used when getting an [AuthenticationSession](#AuthenticationSession) from an [AuthenticationProvider](#AuthenticationProvider).\r\n\t */\r\n\texport interface AuthenticationGetSessionOptions {\r\n\t\t/**\r\n\t\t * Whether login should be performed if there is no matching session.\r\n\t\t *\r\n\t\t * If true, a modal dialog will be shown asking the user to sign in. If false, a numbered badge will be shown\r\n\t\t * on the accounts activity bar icon. An entry for the extension will be added under the menu to sign in. This\r\n\t\t * allows quietly prompting the user to sign in.\r\n\t\t *\r\n\t\t * Defaults to false.\r\n\t\t */\r\n\t\tcreateIfNone?: boolean;\r\n\r\n\t\t/**\r\n\t\t * Whether the existing user session preference should be cleared.\r\n\t\t *\r\n\t\t * For authentication providers that support being signed into multiple accounts at once, the user will be\r\n\t\t * prompted to select an account to use when [getSession](#authentication.getSession) is called. This preference\r\n\t\t * is remembered until [getSession](#authentication.getSession) is called with this flag.\r\n\t\t *\r\n\t\t * Defaults to false.\r\n\t\t */\r\n\t\tclearSessionPreference?: boolean;\r\n\r\n\t\t/*************/\r\n\t\t/*** NEW ***/\r\n\t\t/*************/\r\n /**\r\n * Provider specific options for getting this session (i.e. client id, tenant)\r\n */\r\n\t\tproviderOptions?: { [key: string]: any; }\r\n\t}\r\n```\r\n\r\nThe Auth Provider would then need to be responsible for deciding if it already has created a session with these options or if it needs to create a new session based on these options.", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/115616", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/115616/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/115616/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/115616/events", + "html_url": "https://github.com/microsoft/vscode/issues/115616", + "id": 799392757, + "node_id": "MDU6SXNzdWU3OTkzOTI3NTc=", + "number": 115616, + "title": "Provide extension API to exclude ports from forwarding", + "user": { + "login": "alexr00", + "id": 38270282, + "node_id": "MDQ6VXNlcjM4MjcwMjgy", + "avatar_url": "https://avatars.githubusercontent.com/u/38270282?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/alexr00", + "html_url": "https://github.com/alexr00", + "followers_url": "https://api.github.com/users/alexr00/followers", + "following_url": "https://api.github.com/users/alexr00/following{/other_user}", + "gists_url": "https://api.github.com/users/alexr00/gists{/gist_id}", + "starred_url": "https://api.github.com/users/alexr00/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/alexr00/subscriptions", + "organizations_url": "https://api.github.com/users/alexr00/orgs", + "repos_url": "https://api.github.com/users/alexr00/repos", + "events_url": "https://api.github.com/users/alexr00/events{/privacy}", + "received_events_url": "https://api.github.com/users/alexr00/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 290465400, + "node_id": "MDU6TGFiZWwyOTA0NjU0MDA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api", + "name": "api", + "color": "1d76db", + "default": false, + "description": "" + }, + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 272689392, + "node_id": "MDU6TGFiZWwyNzI2ODkzOTI=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/feature-request", + "name": "feature-request", + "color": "dcdcdc", + "default": false, + "description": "Request for new features or functionality" + }, + { + "id": 1772775110, + "node_id": "MDU6TGFiZWwxNzcyNzc1MTEw", + "url": "https://api.github.com/repos/microsoft/vscode/labels/remote-explorer", + "name": "remote-explorer", + "color": "1d76db", + "default": false, + "description": "Remote explorer view" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + { + "login": "alexr00", + "id": 38270282, + "node_id": "MDQ6VXNlcjM4MjcwMjgy", + "avatar_url": "https://avatars.githubusercontent.com/u/38270282?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/alexr00", + "html_url": "https://github.com/alexr00", + "followers_url": "https://api.github.com/users/alexr00/followers", + "following_url": "https://api.github.com/users/alexr00/following{/other_user}", + "gists_url": "https://api.github.com/users/alexr00/gists{/gist_id}", + "starred_url": "https://api.github.com/users/alexr00/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/alexr00/subscriptions", + "organizations_url": "https://api.github.com/users/alexr00/orgs", + "repos_url": "https://api.github.com/users/alexr00/repos", + "events_url": "https://api.github.com/users/alexr00/events{/privacy}", + "received_events_url": "https://api.github.com/users/alexr00/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 15, + "created_at": "2021-02-02T15:37:45Z", + "updated_at": "2021-02-12T16:08:10Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "From @weinand:\r\nToday the tunneling service blindly forwards all communication ports.\r\nThis includes ports that are used for debugging (even if our remote debugging feature does not need these ports to be forwarded).\r\nThis is confusing for users because they see ports that they are not really interested in.\r\n\r\nI propose to add extension API so that individual ports or port ranges can be excluded from forwarding.\r\nDebug extensions could use this API.\r\n\r\n", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/114123", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/114123/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/114123/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/114123/events", + "html_url": "https://github.com/microsoft/vscode/issues/114123", + "id": 783094648, + "node_id": "MDU6SXNzdWU3ODMwOTQ2NDg=", + "number": 114123, + "title": "Resolve the conflict run button in editor context menu", + "user": { + "login": "jdneo", + "id": 6193897, + "node_id": "MDQ6VXNlcjYxOTM4OTc=", + "avatar_url": "https://avatars.githubusercontent.com/u/6193897?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jdneo", + "html_url": "https://github.com/jdneo", + "followers_url": "https://api.github.com/users/jdneo/followers", + "following_url": "https://api.github.com/users/jdneo/following{/other_user}", + "gists_url": "https://api.github.com/users/jdneo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdneo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdneo/subscriptions", + "organizations_url": "https://api.github.com/users/jdneo/orgs", + "repos_url": "https://api.github.com/users/jdneo/repos", + "events_url": "https://api.github.com/users/jdneo/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdneo/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 272689392, + "node_id": "MDU6TGFiZWwyNzI2ODkzOTI=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/feature-request", + "name": "feature-request", + "color": "dcdcdc", + "default": false, + "description": "Request for new features or functionality" + }, + { + "id": 795791582, + "node_id": "MDU6TGFiZWw3OTU3OTE1ODI=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/menus", + "name": "menus", + "color": "1d76db", + "default": false, + "description": "Menu items and widget issues" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "jrieken", + "id": 1794099, + "node_id": "MDQ6VXNlcjE3OTQwOTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1794099?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jrieken", + "html_url": "https://github.com/jrieken", + "followers_url": "https://api.github.com/users/jrieken/followers", + "following_url": "https://api.github.com/users/jrieken/following{/other_user}", + "gists_url": "https://api.github.com/users/jrieken/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jrieken/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jrieken/subscriptions", + "organizations_url": "https://api.github.com/users/jrieken/orgs", + "repos_url": "https://api.github.com/users/jrieken/repos", + "events_url": "https://api.github.com/users/jrieken/events{/privacy}", + "received_events_url": "https://api.github.com/users/jrieken/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "jrieken", + "id": 1794099, + "node_id": "MDQ6VXNlcjE3OTQwOTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/1794099?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/jrieken", + "html_url": "https://github.com/jrieken", + "followers_url": "https://api.github.com/users/jrieken/followers", + "following_url": "https://api.github.com/users/jrieken/following{/other_user}", + "gists_url": "https://api.github.com/users/jrieken/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jrieken/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jrieken/subscriptions", + "organizations_url": "https://api.github.com/users/jrieken/orgs", + "repos_url": "https://api.github.com/users/jrieken/repos", + "events_url": "https://api.github.com/users/jrieken/events{/privacy}", + "received_events_url": "https://api.github.com/users/jrieken/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 15, + "created_at": "2021-01-11T06:09:50Z", + "updated_at": "2021-02-09T13:40:28Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "\r\n\r\n\r\n\r\n\r\n\r\n### Problem\r\nSince the contribution point: `editor/title` is open for all the extensions, sometimes different extensions may have conflicts at this area. For example, in Java, such conflicts affect the run experience when the user installs both the Java extensions and the Code Runner extension:\r\n\r\n![image](https://user-images.githubusercontent.com/6193897/104149682-30373100-5412-11eb-84be-8f05bfa9c042.png)\r\n\r\nThis is a very open and big topic, as a author of VS Code extensions, it will be great if VS Code as a platform, can provide solutions for this issue.\r\n\r\n### Potential Solutions\r\nBelow are just rough ideas on this, it's open for discussion!\r\n\r\n#### Editor Metadata\r\nExtensions can register context value per document/editor, and register command on the editor title area according to the context value. For example. the Java Debugger can use this to mark if the current Java file is executable or not. And register the run/debug command into the editor context area if it's executable.\r\n\r\n> This can somehow achieved by using the `in` expression of the when clause, something like `resource in hasMainMethodFiles`. But we also need to [get the context value dynamically from the code](https://github.com/microsoft/vscode/issues/10471#issuecomment-718548790) to handle with the corporation between multiple extensions. \r\n\r\nMeanwhile the Java feature team can contribute changes to the Code Runner extension to align the UX (for example, hide the run button from Code Runner if the current Java file contains an executable Main class).\r\n\r\n#### Official Support for the run experience in the editor title area.\r\nThis also may have some opportunity since I believe it's somehow related with #85759, if VS Code team will consider provide official `run/debug` functionality area in the editor title.\r\n\r\n", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/109277", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/109277/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/109277/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/109277/events", + "html_url": "https://github.com/microsoft/vscode/issues/109277", + "id": 728636389, + "node_id": "MDU6SXNzdWU3Mjg2MzYzODk=", + "number": 109277, + "title": "Let extensions hook into url opening", + "user": { + "login": "mjbvz", + "id": 12821956, + "node_id": "MDQ6VXNlcjEyODIxOTU2", + "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mjbvz", + "html_url": "https://github.com/mjbvz", + "followers_url": "https://api.github.com/users/mjbvz/followers", + "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", + "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", + "organizations_url": "https://api.github.com/users/mjbvz/orgs", + "repos_url": "https://api.github.com/users/mjbvz/repos", + "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", + "received_events_url": "https://api.github.com/users/mjbvz/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 290465400, + "node_id": "MDU6TGFiZWwyOTA0NjU0MDA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api", + "name": "api", + "color": "1d76db", + "default": false, + "description": "" + }, + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 578047123, + "node_id": "MDU6TGFiZWw1NzgwNDcxMjM=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/under-discussion", + "name": "under-discussion", + "color": "dcdcdc", + "default": false, + "description": "Issue is under discussion for relevance, priority, approach" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "mjbvz", + "id": 12821956, + "node_id": "MDQ6VXNlcjEyODIxOTU2", + "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mjbvz", + "html_url": "https://github.com/mjbvz", + "followers_url": "https://api.github.com/users/mjbvz/followers", + "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", + "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", + "organizations_url": "https://api.github.com/users/mjbvz/orgs", + "repos_url": "https://api.github.com/users/mjbvz/repos", + "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", + "received_events_url": "https://api.github.com/users/mjbvz/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "mjbvz", + "id": 12821956, + "node_id": "MDQ6VXNlcjEyODIxOTU2", + "avatar_url": "https://avatars.githubusercontent.com/u/12821956?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mjbvz", + "html_url": "https://github.com/mjbvz", + "followers_url": "https://api.github.com/users/mjbvz/followers", + "following_url": "https://api.github.com/users/mjbvz/following{/other_user}", + "gists_url": "https://api.github.com/users/mjbvz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mjbvz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mjbvz/subscriptions", + "organizations_url": "https://api.github.com/users/mjbvz/orgs", + "repos_url": "https://api.github.com/users/mjbvz/repos", + "events_url": "https://api.github.com/users/mjbvz/events{/privacy}", + "received_events_url": "https://api.github.com/users/mjbvz/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 10, + "created_at": "2020-10-24T02:27:26Z", + "updated_at": "2021-02-11T23:51:08Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "## Overview\r\nLet extensions hook into url opening. Motivating use case: I click on a link in the integrated terminal and it opens in my [browser preview extension](https://marketplace.visualstudio.com/items?itemName=auchenberg.vscode-browser-preview)\r\n\r\nPotential places to handle links:\r\n\r\n- Links in the terminal\r\n- Links in documents\r\n- Links from the remote port forwarding views\r\n- Debugger launch?\r\n- Open external?\r\n\r\n## Additional requirements\r\n\r\n- A url opener should be able to decline opening a link\r\n\r\n Some openers may only support specific types of links, such as `localhost`\r\n\r\n- Clicking a link should activate relevant extensions\r\n\r\n We'd need a new activation event so that extensions can make sure they handle link opening\r\n\r\n- Let users fallback to VS Code's default behavior\r\n\r\n This typically is to open using the default browser\r\n\r\n- Handle multiple url openers being registered at the same time\r\n\r\n Users should be able to select which opener to use in this case. They should potentially be able to specify a default opener.\r\n", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/107467", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/107467/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/107467/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/107467/events", + "html_url": "https://github.com/microsoft/vscode/issues/107467", + "id": 709128519, + "node_id": "MDU6SXNzdWU3MDkxMjg1MTk=", + "number": 107467, + "title": "Testing in VS Code", + "user": { + "login": "connor4312", + "id": 2230985, + "node_id": "MDQ6VXNlcjIyMzA5ODU=", + "avatar_url": "https://avatars.githubusercontent.com/u/2230985?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/connor4312", + "html_url": "https://github.com/connor4312", + "followers_url": "https://api.github.com/users/connor4312/followers", + "following_url": "https://api.github.com/users/connor4312/following{/other_user}", + "gists_url": "https://api.github.com/users/connor4312/gists{/gist_id}", + "starred_url": "https://api.github.com/users/connor4312/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/connor4312/subscriptions", + "organizations_url": "https://api.github.com/users/connor4312/orgs", + "repos_url": "https://api.github.com/users/connor4312/repos", + "events_url": "https://api.github.com/users/connor4312/events{/privacy}", + "received_events_url": "https://api.github.com/users/connor4312/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 293426086, + "node_id": "MDU6TGFiZWwyOTM0MjYwODY=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/plan-item", + "name": "plan-item", + "color": "dcdcdc", + "default": false, + "description": "VS Code - planned item for upcoming" + }, + { + "id": 578047123, + "node_id": "MDU6TGFiZWw1NzgwNDcxMjM=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/under-discussion", + "name": "under-discussion", + "color": "dcdcdc", + "default": false, + "description": "Issue is under discussion for relevance, priority, approach" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "connor4312", + "id": 2230985, + "node_id": "MDQ6VXNlcjIyMzA5ODU=", + "avatar_url": "https://avatars.githubusercontent.com/u/2230985?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/connor4312", + "html_url": "https://github.com/connor4312", + "followers_url": "https://api.github.com/users/connor4312/followers", + "following_url": "https://api.github.com/users/connor4312/following{/other_user}", + "gists_url": "https://api.github.com/users/connor4312/gists{/gist_id}", + "starred_url": "https://api.github.com/users/connor4312/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/connor4312/subscriptions", + "organizations_url": "https://api.github.com/users/connor4312/orgs", + "repos_url": "https://api.github.com/users/connor4312/repos", + "events_url": "https://api.github.com/users/connor4312/events{/privacy}", + "received_events_url": "https://api.github.com/users/connor4312/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "connor4312", + "id": 2230985, + "node_id": "MDQ6VXNlcjIyMzA5ODU=", + "avatar_url": "https://avatars.githubusercontent.com/u/2230985?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/connor4312", + "html_url": "https://github.com/connor4312", + "followers_url": "https://api.github.com/users/connor4312/followers", + "following_url": "https://api.github.com/users/connor4312/following{/other_user}", + "gists_url": "https://api.github.com/users/connor4312/gists{/gist_id}", + "starred_url": "https://api.github.com/users/connor4312/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/connor4312/subscriptions", + "organizations_url": "https://api.github.com/users/connor4312/orgs", + "repos_url": "https://api.github.com/users/connor4312/repos", + "events_url": "https://api.github.com/users/connor4312/events{/privacy}", + "received_events_url": "https://api.github.com/users/connor4312/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "sandy081", + "id": 10746682, + "node_id": "MDQ6VXNlcjEwNzQ2Njgy", + "avatar_url": "https://avatars.githubusercontent.com/u/10746682?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sandy081", + "html_url": "https://github.com/sandy081", + "followers_url": "https://api.github.com/users/sandy081/followers", + "following_url": "https://api.github.com/users/sandy081/following{/other_user}", + "gists_url": "https://api.github.com/users/sandy081/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sandy081/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sandy081/subscriptions", + "organizations_url": "https://api.github.com/users/sandy081/orgs", + "repos_url": "https://api.github.com/users/sandy081/repos", + "events_url": "https://api.github.com/users/sandy081/events{/privacy}", + "received_events_url": "https://api.github.com/users/sandy081/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 53, + "created_at": "2020-09-25T17:19:53Z", + "updated_at": "2021-02-16T21:11:36Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "## State of the World\r\n\r\nTesting support in VS Code has been a feature request for [a long time](https://github.com/microsoft/vscode/issues/9505). The VS Code community has build excellent extensions around testing, for example:\r\n\r\n- The [Test Explorer UI](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer) from @hbenl\r\n- [Wallaby.js](https://wallabyjs.com/) from the Wallaby team\r\n- [Jest](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) from @orta\r\n- ...and many more\r\n\r\nEach implementation of testing presents a different set of features, UI, and idiomaticity. Because there is no sanctioned approach to tests in VS Code, extension developers tend to make bespoke implementations, as we've seen in the Python and Java language extensions. Ideally, like in debugging, a VS Code user would have just about the same experience as they work between projects and languages.\r\n\r\n## VS Code's Approach\r\n\r\n> Investigate how VS Code can improve the testing support. Several extensions are already providing testing support, explore what APIs/UIs could be added to improve these testing extensions and the test running experience. -- [2020 Roadmap](https://github.com/microsoft/vscode/wiki/Roadmap#testing)\r\n\r\nThe Test Explorer UI presents the best point of inspiration for us, as there are many existing extensions built on its API: it's capable and proven. Regardless of the direction we take in VS Code, we should have a way for its Test Adapters to be upgraded to the new world.\r\n\r\nWallaby is an excellent extension, but it's tailored and purpose-built to JavaScript, and includes functionality which is not readily portable to other languages. While it is a good source for inspiration, we're not aiming to encompass Wallaby's feature set in the extension points we provide, at least not yet.\r\n\r\nWe're prototyping an API in the extension host, but there are a number of approaches we can take:\r\n\r\n\r\n\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n
Extension Host ('traditional' VS Code API)'Test Protocol' (like DAP/LSP)Extension (like existing test explorer)
\r\n\t\t\t+ Simple to adopt for extension authors
\r\n\t\t\t+ Easier to manage state
\r\n\t\t\t+ Clear way to build 'official' test extensions
\r\n\t\t
\r\n\t\t\t+ Encourages keeping expensive work in child processes
\r\n\t\t\t+ Could be theoretically shared with VS and other editors
\r\n\t\t
\r\n\t\t\t+ Keep VS Code core slim
\r\n\t\t\t+ Unclear whether there's significant functionality we'd want that's not already possible in exthost api
\r\n\t\t
\r\n\t\t\t- The 'obvious path' is doing heavy lifting in the extension host process, which is undesirable
\r\n\t\t
\r\n\t\t\t- Additional implementation and maintainence complexity for VS Code
\r\n\t\t\t- Less friendly, additional complexity than TS APIs for extension authors
\r\n\t\t
\r\n\t\t\t- Additional extension and set of libraries to maintain+version for types and implementation
\r\n\t\t\t- Less clear there's an official pathway for test extensions
\r\n\t\t
\r\n\r\n## API Design\r\n\r\nThe following is a working draft of an API design. It should not be considered final, or anything close to final. This post will be edited as it evolves.\r\n\r\n#### Changes versus the [Test Adapter API](https://github.com/hbenl/vscode-test-adapter-api)\r\n\r\nAs mentioned, the test adapter API and this one provide a similar end user experience. Here are the notable changes we made:\r\n\r\n- The test adapter API does not distinguish between watching a workspace and watching a file. In some cases, there is an existing process that reads workspace tests (such as a language server in Java) or it's not much more expensive to get workspace tests than file tests (such as mocha, perhaps). However, some cases, like Go, providing tests for a single file can be done very cheaply and efficiently without needing to involve the workspace.\r\n\r\n\tIn this API we expect the `TestProvider` to, after activation, always provide tests for the visible text editors, and we only request tests for the entire workspace when required (i.e. when the UI needs to enumerate them).\r\n\r\n- We have modeled the test state more closely after the existing `DiagnosticCollection`, where the Test Adapter API uses only events to enumerate tests and does not have a central collection.\r\n\r\n- The Test Adapter API makes the distinction between suites and tests, we do not. They have almost identical capabilities, and in [at least one scenario](https://blog.golang.org/subtests) the 'suites' are more like tests and the leaf 'tests' cannot be run individually.\r\n\r\n- We use object identity rather than ID for referencing tests. This is in line with other items in the VS Code API, including Diagnostics.\r\n\r\n#### Ideas and Open Questions\r\n\r\n- We do not (yet) have a concept of test invalidation and auto-run, which in the test adapter API via the \"retire\" event. We are still looking into how this can best be implemented.\r\n\t\r\n\tIn a golden scenario, invalidation of tests would be done by a language server which can intelligently determine specific tests that should be invalidated when a file or a file dependency changes. Maybe this is still handled by an event on the TestProvider, but if we take a \"Test Protocol\" approach then coordination will be harder.\r\n- As marked in the `todo`, we will expose APIs for other extensions to read test state and build UI, but this is not yet included in the API design.\r\n- How should errors loading tests be handled? Emit diagnostics or have some test-specific code?\r\n\r\n- We would like to support code coverage in testing as well, but that is further down the line.\r\n\r\n- How can we let users learn about/onboard to testing from within VS Code?\r\n\r\n### API\r\n\r\nSee the current working proposal in https://github.com/microsoft/vscode/blob/master/src/vs/vscode.proposed.d.ts (ctrl+f for 107467)", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/105690", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/105690/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/105690/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/105690/events", + "html_url": "https://github.com/microsoft/vscode/issues/105690", + "id": 688793797, + "node_id": "MDU6SXNzdWU2ODg3OTM3OTc=", + "number": 105690, + "title": "Extension API for Inline Values", + "user": { + "login": "weinand", + "id": 1898161, + "node_id": "MDQ6VXNlcjE4OTgxNjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1898161?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/weinand", + "html_url": "https://github.com/weinand", + "followers_url": "https://api.github.com/users/weinand/followers", + "following_url": "https://api.github.com/users/weinand/following{/other_user}", + "gists_url": "https://api.github.com/users/weinand/gists{/gist_id}", + "starred_url": "https://api.github.com/users/weinand/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/weinand/subscriptions", + "organizations_url": "https://api.github.com/users/weinand/orgs", + "repos_url": "https://api.github.com/users/weinand/repos", + "events_url": "https://api.github.com/users/weinand/events{/privacy}", + "received_events_url": "https://api.github.com/users/weinand/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 290465400, + "node_id": "MDU6TGFiZWwyOTA0NjU0MDA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api", + "name": "api", + "color": "1d76db", + "default": false, + "description": "" + }, + { + "id": 869332220, + "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", + "name": "api-proposal", + "color": "c5def5", + "default": false, + "description": "" + }, + { + "id": 291054922, + "node_id": "MDU6TGFiZWwyOTEwNTQ5MjI=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/debug", + "name": "debug", + "color": "1d76db", + "default": false, + "description": "Debug viewlet, configurations, breakpoints, adapter issues" + }, + { + "id": 272689392, + "node_id": "MDU6TGFiZWwyNzI2ODkzOTI=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/feature-request", + "name": "feature-request", + "color": "dcdcdc", + "default": false, + "description": "Request for new features or functionality" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "weinand", + "id": 1898161, + "node_id": "MDQ6VXNlcjE4OTgxNjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1898161?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/weinand", + "html_url": "https://github.com/weinand", + "followers_url": "https://api.github.com/users/weinand/followers", + "following_url": "https://api.github.com/users/weinand/following{/other_user}", + "gists_url": "https://api.github.com/users/weinand/gists{/gist_id}", + "starred_url": "https://api.github.com/users/weinand/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/weinand/subscriptions", + "organizations_url": "https://api.github.com/users/weinand/orgs", + "repos_url": "https://api.github.com/users/weinand/repos", + "events_url": "https://api.github.com/users/weinand/events{/privacy}", + "received_events_url": "https://api.github.com/users/weinand/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "weinand", + "id": 1898161, + "node_id": "MDQ6VXNlcjE4OTgxNjE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1898161?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/weinand", + "html_url": "https://github.com/weinand", + "followers_url": "https://api.github.com/users/weinand/followers", + "following_url": "https://api.github.com/users/weinand/following{/other_user}", + "gists_url": "https://api.github.com/users/weinand/gists{/gist_id}", + "starred_url": "https://api.github.com/users/weinand/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/weinand/subscriptions", + "organizations_url": "https://api.github.com/users/weinand/orgs", + "repos_url": "https://api.github.com/users/weinand/repos", + "events_url": "https://api.github.com/users/weinand/events{/privacy}", + "received_events_url": "https://api.github.com/users/weinand/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/142", + "html_url": "https://github.com/microsoft/vscode/milestone/142", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/142/labels", + "id": 6286096, + "node_id": "MDk6TWlsZXN0b25lNjI4NjA5Ng==", + "number": 142, + "title": "February 2021", + "description": "", + "creator": { + "login": "Tyriar", + "id": 2193314, + "node_id": "MDQ6VXNlcjIxOTMzMTQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/2193314?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Tyriar", + "html_url": "https://github.com/Tyriar", + "followers_url": "https://api.github.com/users/Tyriar/followers", + "following_url": "https://api.github.com/users/Tyriar/following{/other_user}", + "gists_url": "https://api.github.com/users/Tyriar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Tyriar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Tyriar/subscriptions", + "organizations_url": "https://api.github.com/users/Tyriar/orgs", + "repos_url": "https://api.github.com/users/Tyriar/repos", + "events_url": "https://api.github.com/users/Tyriar/events{/privacy}", + "received_events_url": "https://api.github.com/users/Tyriar/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 232, + "closed_issues": 300, + "state": "open", + "created_at": "2021-01-07T18:23:10Z", + "updated_at": "2021-02-17T10:48:47Z", + "due_on": null, + "closed_at": null + }, + "comments": 7, + "created_at": "2020-08-30T21:21:23Z", + "updated_at": "2021-02-08T05:21:59Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "Today the \"Show Inline Values\" feature of VS Code's debugger is based on a generic implementation in the VS Code core and provides neither customisability through settings, nor extensibility via extensions.\r\n\r\nAs a consequence, it is not a perfect fit for all languages (e.g. #101797) and sometimes even shows incorrect values because it doesn't understand the scope regions of the underlying language. \r\n\r\nThis features asks for an extension API that either replaces the built-in implementation completely or allows to replace parts of the implementation with custom code.\r\n", + "performed_via_github_app": null, + "score": 1 + } + ] + } + ] } ] \ No newline at end of file diff --git a/.vscode/notebooks/inbox.github-issues b/.vscode/notebooks/inbox.github-issues index be0a2609cb2..69c1b7b0aa9 100644 --- a/.vscode/notebooks/inbox.github-issues +++ b/.vscode/notebooks/inbox.github-issues @@ -3,45 +3,56 @@ "kind": 1, "language": "markdown", "value": "## tl;dr: Triage Inbox\n\nAll inbox issues but not those that need more information. These issues need to be triaged, e.g assigned to a user or ask for more information", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", - "value": "$inbox -label:\"needs more info\"" + "value": "$inbox -label:\"needs more info\"", + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "##### `Config`: defines the inbox query", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", - "value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item " + "value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item ", + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "## Inbox tracking and Issue triage", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "New issues or pull requests submitted by the community are initially triaged by an [automatic classification bot](https://github.com/microsoft/vscode-github-triage-actions/tree/master/classifier-deep). Issues that the bot does not correctly triage are then triaged by a team member. The team rotates the inbox tracker on a weekly basis.\n\nA [mirror](https://github.com/JacksonKearl/testissues/issues) of the VS Code issue stream is available with details about how the bot classifies issues, including feature-area classifications and confidence ratings. Per-category confidence thresholds and feature-area ownership data is maintained in [.github/classifier.json](https://github.com/microsoft/vscode/blob/main/.github/classifier.json). \n\n💡 The bot is being run through a GitHub action that runs every 30 minutes. Give the bot the opportunity to classify an issue before doing it manually.\n\n### Inbox Tracking\n\nThe inbox tracker is responsible for the [global inbox](https://github.com/microsoft/vscode/issues?utf8=%E2%9C%93&q=is%3Aopen+no%3Aassignee+-label%3Afeature-request+-label%3Atestplan-item+-label%3Aplan-item) containing all **open issues and pull requests** that\n- are neither **feature requests** nor **test plan items** nor **plan items** and\n- have **no owner assignment**.\n\nThe **inbox tracker** may perform any step described in our [issue triaging documentation](https://github.com/microsoft/vscode/wiki/Issues-Triaging) but its main responsibility is to route issues to the actual feature area owner.\n\nFeature area owners track the **feature area inbox** containing all **open issues and pull requests** that\n- are personally assigned to them and are not assigned to any milestone\n- are labeled with their feature area label and are not assigned to any milestone.\nThis secondary triage may involve any of the steps described in our [issue triaging documentation](https://github.com/microsoft/vscode/wiki/Issues-Triaging) and results in a fully triaged or closed issue.\n\nThe [github triage extension](https://github.com/microsoft/vscode-github-triage-extension) can be used to assist with triaging — it provides a \"Command Palette\"-style list of triaging actions like assignment, labeling, and triggers for various bot actions.", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "## All Inbox Items\n\nAll issues that have no assignee and that have neither **feature requests** nor **test plan items** nor **plan items**.", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", - "value": "$inbox" + "value": "$inbox", + "editable": true, + "outputs": [] } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 1502c29db16..4cb58bad7a6 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -3,114 +3,133 @@ "kind": 1, "language": "markdown", "value": "##### `Config`: This should be changed every month/milestone", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog\n\n// current milestone name\n$milestone=milestone:\"February 2021\"", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "github-issues", "value": "## Milestone Work", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos $milestone assignee:@me is:open", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "github-issues", "value": "## Bugs, Debt, Features...", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "#### My Bugs", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open label:bug", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "#### Debt & Engineering", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open label:debt OR $repos assignee:@me is:open label:engineering", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "#### Performance 🐌 🔜 🏎", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open label:perf OR $repos assignee:@me is:open label:perf-startup OR $repos assignee:@me is:open label:perf-bloat OR $repos assignee:@me is:open label:freeze-slow-crash-leak", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "#### Feature Requests", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open label:feature-request milestone:Backlog sort:reactions-+1-desc", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open milestone:\"Backlog Candidates\"", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "### Personal Inbox\n", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "\n#### Missing Type label", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"needs more info\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "#### Not Actionable", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "$repos assignee:@me is:open label:\"needs more info\"", - "editable": true + "editable": true, + "outputs": [] } ] \ No newline at end of file diff --git a/.vscode/notebooks/papercuts.github-issues b/.vscode/notebooks/papercuts.github-issues index 4da4734ce5d..01bb21518fc 100644 --- a/.vscode/notebooks/papercuts.github-issues +++ b/.vscode/notebooks/papercuts.github-issues @@ -3,42 +3,49 @@ "kind": 1, "language": "markdown", "value": "## Papercuts\n\nThis notebook serves as an ongoing collection of papercut issues that we encounter while dogfooding. With that in mind only promote issues that really turn you off, e.g. issues that make you want to stop using VS Code or its extensions. To mark an issue (bug, feature-request, etc.) as papercut add the labels: `papercut :drop_of_blood:`", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "## All Papercuts\n\nThese are all papercut issues that we encounter while dogfooding vscode or extensions that we author.", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "repo:microsoft/vscode is:open -label:notebook label:\"papercut :drop_of_blood:\"", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "## Native Notebook", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "repo:microsoft/vscode is:open label:notebook label:\"papercut :drop_of_blood:\"", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 1, "language": "markdown", "value": "### My Papercuts", - "editable": true + "editable": true, + "outputs": [] }, { "kind": 2, "language": "github-issues", "value": "repo:microsoft/vscode is:open assignee:@me label:\"papercut :drop_of_blood:\"", - "editable": true + "editable": true, + "outputs": [] } -] +] \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0222b24c2a4..eabef1693e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,7 +43,9 @@ } ], "eslint.options": { - "rulePaths": ["./build/lib/eslint"] + "rulePaths": [ + "./build/lib/eslint" + ] }, "typescript.tsdk": "node_modules/typescript/lib", "npm.exclude": "**/extensions/**", @@ -53,11 +55,15 @@ "typescript.preferences.quoteStyle": "single", "json.schemas": [ { - "fileMatch": ["cgmanifest.json"], + "fileMatch": [ + "cgmanifest.json" + ], "url": "./.vscode/cgmanifest.schema.json" }, { - "fileMatch": ["cglicenses.json"], + "fileMatch": [ + "cglicenses.json" + ], "url": "./.vscode/cglicenses.schema.json" } ], @@ -78,5 +84,6 @@ "editor.defaultFormatter": "vscode.typescript-language-features", "editor.formatOnSave": true, }, - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "notebook.experimental.useMarkdownRenderer": true, } diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 4f2024a6209..c51ed5644c6 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -140,4 +140,6 @@ steps: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: "Component Detection" + inputs: + sourceScanPath: $(Build.SourcesDirectory) continueOnError: true diff --git a/build/lib/electron.ts b/build/lib/electron.ts index beb1b571f52..06c8033edb4 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -32,7 +32,7 @@ export const config = { version: util.getElectronVersion(), productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2019 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2021 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', diff --git a/build/package.json b/build/package.json index 7de0c8f734c..a6ece3f020f 100644 --- a/build/package.json +++ b/build/package.json @@ -51,7 +51,7 @@ "p-limit": "^3.1.0", "plist": "^3.0.1", "source-map": "0.6.1", - "typescript": "4.2.0-dev.20201207", + "typescript": "^4.3.0-dev.20210216", "vsce": "1.48.0", "vscode-universal": "deepak1556/universal#61454d96223b774c53cda10f72c2098c0ce02d58" }, diff --git a/build/yarn.lock b/build/yarn.lock index a111c831be1..28014afaad9 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1695,16 +1695,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@4.2.0-dev.20201207: - version "4.2.0-dev.20201207" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.0-dev.20201207.tgz#19a34bc7d2d42a7467c512c63f135587ac848807" - integrity sha512-fPHBDi/fgdX4WiRC7cFVv/aL069PgUaDWuLYUSHatWZujz/Lkc9bkf/zL3rKdNSCxlNKAMs3fhJv/yompOphZA== - typescript@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== +typescript@^4.3.0-dev.20210216: + version "4.3.0-dev.20210216" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210216.tgz#233327e6094008c02265ba140f8d9ece9133421e" + integrity sha512-pJLcC/kqnE+0rftTRc2/gYBkz9nl+kJfaU8sSOLYnzUvD8p+LOZMzXfaLoKPdGFJ6U9+Ox/sYV9HBTJVEjSTYg== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index a3ef34f3d83..28bcda61df2 100644 --- a/extensions/configuration-editing/src/configurationEditingMain.ts +++ b/extensions/configuration-editing/src/configurationEditingMain.ts @@ -22,6 +22,9 @@ export function activate(context: vscode.ExtensionContext): void { // task.json variable suggestions context.subscriptions.push(registerVariableCompletions('**/tasks.json')); + + // keybindings.json/package.json context key suggestions + context.subscriptions.push(registerContextKeyCompletions()); } function registerSettingsCompletions(): vscode.Disposable { @@ -136,3 +139,45 @@ vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', lan return result; } }, { label: 'Launch Targets' }); + +function registerContextKeyCompletions(): vscode.Disposable { + type ContextKeyInfo = { key: string, type?: string, description?: string }; + return vscode.languages.registerCompletionItemProvider( + [{ language: 'json', pattern: '**/package.json' }, { language: 'jsonc', pattern: '**/keybindings.json' }], + { + async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) { + + const location = getLocation(document.getText(), document.offsetAt(position)); + + if (location.isAtPropertyKey) { + return; + } + + if (!location.matches(['*', 'when']) && !location.matches(['contributes', 'menus', '*', '*', 'when'])) { + return; + } + + const replacing = document.getWordRangeAtPosition(position, /[^"\s]+/); + if (!replacing) { + return; + } + const inserting = replacing.with(undefined, position); + + const data = await vscode.commands.executeCommand('getContextKeyInfo'); + if (token.isCancellationRequested || !data) { + return; + } + + const result = new vscode.CompletionList(); + for (const item of data) { + const completion = new vscode.CompletionItem(item.key, vscode.CompletionItemKind.Constant); + completion.detail = item.type; + completion.range = { replacing, inserting }; + completion.documentation = item.description; + result.items.push(completion); + } + return result; + } + } + ); +} diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 0766cda7bc4..5804c797484 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -217,6 +217,11 @@ "type": "boolean", "default": false, "description": "%emmetPreferencesOutputReverseAttributes%" + }, + "css.color.short": { + "type": "boolean", + "default": true, + "description": "%emmetPreferencesCssColorShort%" } } }, diff --git a/extensions/emmet/package.nls.json b/extensions/emmet/package.nls.json index 8e168ec4c4e..0c0d875d87c 100644 --- a/extensions/emmet/package.nls.json +++ b/extensions/emmet/package.nls.json @@ -56,5 +56,6 @@ "emmetPreferencesCssMsProperties": "Comma separated CSS properties that get the 'ms' vendor prefix when used in Emmet abbreviation that starts with `-`. Set to empty string to always avoid the 'ms' prefix.", "emmetPreferencesCssFuzzySearchMinScore": "The minimum score (from 0 to 1) that fuzzy-matched abbreviation should achieve. Lower values may produce many false-positive matches, higher values may reduce possible matches.", "emmetOptimizeStylesheetParsing": "When set to `false`, the whole file is parsed to determine if current position is valid for expanding Emmet abbreviations. When set to `true`, only the content around the current position in css/scss/less files is parsed.", - "emmetPreferencesOutputReverseAttributes": "If `true`, reverses attribute merging directions when resolving snippets." + "emmetPreferencesOutputReverseAttributes": "If `true`, reverses attribute merging directions when resolving snippets.", + "emmetPreferencesCssColorShort": "If `true`, color values like #f will be expanded to #fff instead of #ffffff." } diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index e9ac076c3f0..117f331df39 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -54,9 +54,9 @@ integrity sha1-Rs/+oRmgoAMxKiHC2bVijLX81EI= "@types/node@^12.19.9": - version "12.20.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.0.tgz#692dfdecd6c97f5380c42dd50f19261f9f604deb" - integrity sha512-0/41wHcurotvSOTHQUFkgL702c3pyWR1mToSrrX3pGPvGfpHTv3Ksx0M4UVuU5VJfjVb62Eyr1eKO1tWNUCg2Q== + version "12.20.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.1.tgz#63d36c10e162666f0107f247cdca76542c3c7472" + integrity sha512-tCkE96/ZTO+cWbln2xfyvd6ngHLanvVlJ3e5BeirJ3BYI5GbAyubIrmV4JjjugDly5D9fHjOL5MNsqsCnqwW6g== emmet@^2.3.0: version "2.3.1" @@ -77,9 +77,9 @@ jsonc-parser@^2.3.0: integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== vscode-emmet-helper@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.3.0.tgz#a98357ad5ac9c71d7c00396f22b7963b1a74cc5c" - integrity sha512-QhU8+HlynMuUkqBYgA3wIDTSsUNkw8GWxLR214Hjvwr0lkFZa0CRqW/PzAI1CFREjSrTxJYQvkVnbfatZzKAuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.3.1.tgz#f4f1d3b656441d96ded901536c014dcbf2419d9c" + integrity sha512-05zamij6z60pF6z4Dg6csbypc/Uxi9mBPzaPkfjOwdkn6+lYSrsiOME74MZLE4rhyWd46bvEtuGxAuR6gRKDpw== dependencies: emmet "^2.3.0" jsonc-parser "^2.3.0" diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 13ec13b735b..6a0ecfa357f 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -229,7 +229,7 @@ export class GitHubServer { /* __GDPR__ "session" : { - "isEdu": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + "isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" } } */ this.telemetryReporter.sendTelemetryEvent('session', { diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 9363431f8a2..22c512dbf61 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -439,6 +439,9 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { if (hrefPath[0] !== '/') { // Fix #93691, use this.resource.fsPath instead of this.resource.path hrefPath = path.join(path.dirname(this.resource.fsPath), hrefPath); + } else { + // Handle any normalized file paths + hrefPath = vscode.Uri.parse(hrefPath.replace('/file', '')).fsPath; } const config = vscode.workspace.getConfiguration('markdown', this.resource); diff --git a/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts b/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts index 67548f5ec19..66a51dc239a 100644 --- a/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/jsDocCompletions.ts @@ -21,7 +21,7 @@ class JsDocCompletionItem extends vscode.CompletionItem { public readonly document: vscode.TextDocument, public readonly position: vscode.Position ) { - super('/** */', vscode.CompletionItemKind.Snippet); + super('/** */', vscode.CompletionItemKind.Text); this.detail = localize('typescript.jsDocCompletionItem.documentation', 'JSDoc comment'); this.sortText = '\0'; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts index 34a8218e3a1..1c19fd0d753 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts @@ -12,8 +12,8 @@ suite('Notebook Document', function () { const contentProvider = new class implements vscode.NotebookContentProvider { async openNotebook(uri: vscode.Uri, _openContext: vscode.NotebookDocumentOpenContext): Promise { return { - cells: [{ cellKind: vscode.NotebookCellKind.Code, source: uri.toString(), language: 'javascript', metadata: {}, outputs: [] }], - metadata: {} + cells: [{ cellKind: vscode.NotebookCellKind.Code, source: uri.toString(), language: 'javascript', metadata: new vscode.NotebookCellMetadata(), outputs: [] }], + metadata: new vscode.NotebookDocumentMetadata() }; } async resolveNotebook(_document: vscode.NotebookDocument, _webview: vscode.NotebookCommunication) { @@ -232,4 +232,98 @@ suite('Notebook Document', function () { assert.strictEqual(data.changes[0].items[0], document.cells[0]); assert.strictEqual(data.changes[0].items[1], document.cells[1]); }); + + test('workspace edit API (appendNotebookCellOutput, replaceCellOutput, event)', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const document = await vscode.notebook.openNotebookDocument(uri); + + const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); + const edit = new vscode.WorkspaceEdit(); + const firstCellOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'bar')]); + edit.appendNotebookCellOutput(document.uri, 0, [firstCellOutput]); + const success = await vscode.workspace.applyEdit(edit); + const data = await outputChangeEvent; + + assert.strictEqual(success, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.deepStrictEqual(document.cells[0].outputs, [firstCellOutput]); + + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].outputs.length, 1); + assert.deepStrictEqual(data.cells[0].outputs, [firstCellOutput]); + + + { + const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); + const edit = new vscode.WorkspaceEdit(); + const secondCellOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'baz')]); + edit.appendNotebookCellOutput(document.uri, 0, [secondCellOutput]); + const success = await vscode.workspace.applyEdit(edit); + const data = await outputChangeEvent; + + assert.strictEqual(success, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 2); + assert.deepStrictEqual(document.cells[0].outputs, [firstCellOutput, secondCellOutput]); + + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].outputs.length, 2); + assert.deepStrictEqual(data.cells[0].outputs, [firstCellOutput, secondCellOutput]); + } + + { + const outputChangeEvent = utils.asPromise(vscode.notebook.onDidChangeCellOutputs); + const edit = new vscode.WorkspaceEdit(); + const thirdOutput = new vscode.NotebookCellOutput([new vscode.NotebookCellOutputItem('foo', 'baz1')]); + edit.replaceNotebookCellOutput(document.uri, 0, [thirdOutput]); + const success = await vscode.workspace.applyEdit(edit); + const data = await outputChangeEvent; + + assert.strictEqual(success, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.deepStrictEqual(document.cells[0].outputs, [thirdOutput]); + + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].outputs.length, 1); + assert.deepStrictEqual(data.cells[0].outputs, [thirdOutput]); + } + }); + + test('document save API', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const notebook = await vscode.notebook.openNotebookDocument(uri); + + assert.strictEqual(notebook.uri.toString(), uri.toString()); + assert.strictEqual(notebook.isDirty, false); + assert.strictEqual(notebook.isUntitled, false); + assert.strictEqual(notebook.cells.length, 1); + assert.strictEqual(notebook.viewType, 'notebook.nbdtest'); + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(notebook.uri, 0, 0, [{ + cellKind: vscode.NotebookCellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new_markdown' + }, { + cellKind: vscode.NotebookCellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new_code' + }]); + + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + assert.strictEqual(notebook.isDirty, true); + + await notebook.save(); + assert.strictEqual(notebook.isDirty, false); + }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index b437120c23c..06fff1fafb0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -6,7 +6,7 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty } from '../utils'; +import { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty, saveAllEditors } from '../utils'; // Since `workbench.action.splitEditor` command does await properly // Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves @@ -27,7 +27,7 @@ async function saveFileAndCloseAll(resource: vscode.Uri) { }); }); await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); await documentClosed; } @@ -44,7 +44,7 @@ async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) { }); }); await vscode.commands.executeCommand('workbench.action.files.saveAll'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); await documentClosed; } @@ -65,14 +65,6 @@ async function withEvent(event: vscode.Event, callback: (e: Promise) => await callback(e); } -function assertInitalState() { - // no-op unless we figure out why some documents are opened after the editor is closed - - // assert.strictEqual(vscode.window.activeNotebookEditor, undefined); - // assert.strictEqual(vscode.notebook.notebookDocuments.length, 0); - // assert.strictEqual(vscode.notebook.visibleNotebookEditors.length, 0); -} - suite('Notebook API tests', function () { const disposables: vscode.Disposable[] = []; @@ -90,24 +82,20 @@ suite('Notebook API tests', function () { openNotebook: async (_resource: vscode.Uri): Promise => { if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) { return { - metadata: {}, + metadata: new vscode.NotebookDocumentMetadata(), cells: [] }; } const dto: vscode.NotebookData = { - metadata: { - custom: { testMetadata: false } - }, + metadata: new vscode.NotebookDocumentMetadata().with({ custom: { testMetadata: false } }), cells: [ { source: 'test', language: 'typescript', cellKind: vscode.NotebookCellKind.Code, outputs: [], - metadata: { - custom: { testCellMetadata: 123 } - } + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }) } ] }; @@ -135,7 +123,7 @@ suite('Notebook API tests', function () { id: 'mainKernel', label: 'Notebook Test Kernel', isPreferred: true, - supportedLanguages: ['typescript'], + supportedLanguages: ['typescript', 'javascript'], executeAllCells: async (_document: vscode.NotebookDocument) => { const edit = new vscode.WorkspaceEdit(); @@ -174,7 +162,7 @@ suite('Notebook API tests', function () { id: 'secondaryKernel', label: 'Notebook Secondary Test Kernel', isPreferred: false, - supportedLanguages: ['typescript'], + supportedLanguages: ['typescript', 'javascript'], executeAllCells: async (_document: vscode.NotebookDocument) => { const edit = new vscode.WorkspaceEdit(); edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([ @@ -213,48 +201,8 @@ suite('Notebook API tests', function () { })); }); - // test.only('crash', async function () { - // for (let i = 0; i < 200; i++) { - // let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor'); - - // resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor'); - // } - // }); - - // test.only('crash', async function () { - // for (let i = 0; i < 200; i++) { - // let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // } - // }); - - test('document open/close event', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, '.vsctestnb'); - const firstDocumentOpen = asPromise(vscode.notebook.onDidOpenNotebookDocument); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await firstDocumentOpen; - - const firstDocumentClose = asPromise(vscode.notebook.onDidCloseNotebookDocument); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - await firstDocumentClose; - }); - test('shared document in notebook editors', async function () { - assertInitalState(); - - const resource = await createRandomFile('', undefined, '.vsctestnb'); + const resource = await createRandomFile(undefined, undefined, '.vsctestnb'); let counter = 0; const disposables: vscode.Disposable[] = []; disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { @@ -268,28 +216,24 @@ suite('Notebook API tests', function () { await splitEditor(); assert.strictEqual(counter, 1); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); assert.strictEqual(counter, 0); disposables.forEach(d => d.dispose()); }); test('editor open/close event', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); const firstEditorOpen = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await firstEditorOpen; const firstEditorClose = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); await firstEditorClose; }); test('editor open/close event 2', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); let count = 0; const disposables: vscode.Disposable[] = []; @@ -303,13 +247,11 @@ suite('Notebook API tests', function () { await splitEditor(); assert.strictEqual(count, 2); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); assert.strictEqual(count, 0); }); test('editor editing event 2', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -377,12 +319,10 @@ suite('Notebook API tests', function () { // language: 'markdown' // }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await saveAllFilesAndCloseAll(undefined); }); test('editor move cell event', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); @@ -412,19 +352,16 @@ suite('Notebook API tests', function () { ] }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await saveAllEditors(); + await closeAllEditors(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstEditor = vscode.window.activeNotebookEditor; assert.strictEqual(firstEditor?.document.cells.length, 1); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await saveAllFilesAndCloseAll(undefined); }); test('notebook editor active/visible', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstEditor = vscode.window.activeNotebookEditor; @@ -453,12 +390,10 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await saveAllFilesAndCloseAll(undefined); }); test('notebook active editor change', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); const firstEditorOpen = asPromise(vscode.window.onDidChangeActiveNotebookEditor); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -471,111 +406,35 @@ suite('Notebook API tests', function () { await saveFileAndCloseAll(resource); }); - test('edit API (replaceCells)', async function () { - assertInitalState(); + test('change cell language', async function () { const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].cellKind, vscode.NotebookCellKind.Code); + await withEvent(vscode.notebook.onDidChangeCellLanguage, async event => { + await vscode.commands.executeCommand('notebook.cell.changeLanguage', { start: 0, end: 1 }, 'javascript'); + await event; + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].language, 'javascript'); }); - const cellChangeEventRet = await cellsChangeEvent; - assert.strictEqual(cellChangeEventRet.document === vscode.window.activeNotebookEditor?.document, true); - assert.strictEqual(cellChangeEventRet.document.isDirty, true); - assert.strictEqual(cellChangeEventRet.changes.length, 1); - assert.strictEqual(cellChangeEventRet.changes[0].start, 1); - assert.strictEqual(cellChangeEventRet.changes[0].deletedCount, 0); - assert.strictEqual(cellChangeEventRet.changes[0].items[0] === vscode.window.activeNotebookEditor!.document.cells[1], true); + // switch to markdown will change the cell kind + await withEvent(vscode.notebook.onDidChangeNotebookCells, async event => { + await vscode.commands.executeCommand('notebook.cell.changeLanguage', { start: 0, end: 1 }, 'markdown'); + await event; + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].language, 'markdown'); + assert.strictEqual(vscode.window.activeNotebookEditor?.document.cells[0].cellKind, vscode.NotebookCellKind.Markdown); + }); await saveAllFilesAndCloseAll(resource); }); - test('edit API (replaceOutput, USE NotebookCellOutput-type)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('application/foo', 'bar'), - new vscode.NotebookCellOutputItem('application/json', { data: true }, { metadata: true }), - ])]); - }); - - const document = vscode.window.activeNotebookEditor?.document!; - assert.strictEqual(document.isDirty, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 1); - - // consuming is OLD api (for now) - const [output] = document.cells[0].outputs; - - assert.strictEqual(output.outputs.length, 2); - assert.strictEqual(output.outputs[0].mime, 'application/foo'); - assert.strictEqual(output.outputs[0].value, 'bar'); - assert.strictEqual(output.outputs[1].mime, 'application/json'); - assert.deepStrictEqual(output.outputs[1].value, { data: true }); - - await saveAllFilesAndCloseAll(undefined); - }); - - test('edit API (replaceOutput)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('foo', 'bar') - ])]); - }); - - const document = vscode.window.activeNotebookEditor?.document!; - assert.strictEqual(document.isDirty, true); - assert.strictEqual(document.cells.length, 1); - assert.strictEqual(document.cells[0].outputs.length, 1); - assert.strictEqual(document.cells[0].outputs[0].outputs.length, 1); - assert.strictEqual(document.cells[0].outputs[0].outputs[0].mime, 'foo'); - assert.strictEqual(document.cells[0].outputs[0].outputs[0].value, 'bar'); - - await saveAllFilesAndCloseAll(undefined); - }); - - test('edit API (replaceOutput, event)', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - const outputChangeEvent = asPromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('foo', 'bar') - ])]); - }); - - const value = await outputChangeEvent; - assert.strictEqual(value.document === vscode.window.activeNotebookEditor?.document, true); - assert.strictEqual(value.document.isDirty, true); - assert.strictEqual(value.cells.length, 1); - assert.strictEqual(value.document.cells.length, 1); - assert.strictEqual(value.document.cells[0].outputs.length, 1); - assert.strictEqual(value.document.cells[0].outputs[0].outputs.length, 1); - assert.strictEqual(value.document.cells[0].outputs[0].outputs[0].mime, 'foo'); - assert.strictEqual(value.document.cells[0].outputs[0].outputs[0].value, 'bar'); - - await saveAllFilesAndCloseAll(undefined); - }); - test('edit API (replaceMetadata)', async function () { - - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellMetadata(0, { inputCollapsed: true, executionOrder: 17 }); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true, executionOrder: 17 })); }); const document = vscode.window.activeNotebookEditor?.document!; @@ -588,15 +447,13 @@ suite('Notebook API tests', function () { }); test('edit API (replaceMetadata, event)', async function () { - - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const event = asPromise(vscode.notebook.onDidChangeCellMetadata); await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellMetadata(0, { inputCollapsed: true, executionOrder: 17 }); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true, executionOrder: 17 })); }); const data = await event; @@ -609,7 +466,6 @@ suite('Notebook API tests', function () { }); test('edit API batch edits', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -618,7 +474,7 @@ suite('Notebook API tests', function () { const version = vscode.window.activeNotebookEditor!.document.version; await vscode.window.activeNotebookEditor!.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, { runnable: false }); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ runnable: false })); }); await cellsChangeEvent; @@ -628,7 +484,6 @@ suite('Notebook API tests', function () { }); test('edit API batch edits undo/redo', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -637,7 +492,7 @@ suite('Notebook API tests', function () { const version = vscode.window.activeNotebookEditor!.document.version; await vscode.window.activeNotebookEditor!.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, { runnable: false }); + editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ runnable: false })); }); await cellsChangeEvent; @@ -655,7 +510,6 @@ suite('Notebook API tests', function () { }); test('initialzation should not emit cell change events.', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); let count = 0; @@ -676,7 +530,6 @@ suite('Notebook API tests', function () { // suite('notebook workflow', () => { test('notebook open', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -698,7 +551,6 @@ suite('Notebook API tests', function () { }); test('notebook cell actions', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -772,7 +624,6 @@ suite('Notebook API tests', function () { }); test('notebook join cells', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -796,7 +647,6 @@ suite('Notebook API tests', function () { }); test('move cells will not recreate cells in ExtHost', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); @@ -812,36 +662,10 @@ suite('Notebook API tests', function () { assert.deepStrictEqual(activeCell, newActiveCell); await saveFileAndCloseAll(resource); - // TODO@rebornix, there are still some events order issue. - // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(newActiveCell!), 2); }); - // test.only('document metadata is respected', async function () { - // const resource = await createRandomFile('', undefined, '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // const editor = vscode.window.activeNotebookEditor!; - - // assert.strictEqual(editor.document.cells.length, 1); - // editor.document.metadata.editable = false; - // await editor.edit(builder => builder.delete(0)); - // assert.strictEqual(editor.document.cells.length, 1, 'should not delete cell'); // Not editable, no effect - // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); - // assert.strictEqual(editor.document.cells.length, 1, 'should not insert cell'); // Not editable, no effect - - // editor.document.metadata.editable = true; - // await editor.edit(builder => builder.delete(0)); - // assert.strictEqual(editor.document.cells.length, 0, 'should delete cell'); // Editable, it worked - // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); - // assert.strictEqual(editor.document.cells.length, 1, 'should insert cell'); // Editable, it worked - - // // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - // }); test('cell runnable metadata is respected', async () => { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -852,14 +676,14 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 0); let metadataChangeEvent = asPromise(vscode.notebook.onDidChangeCellMetadata); - await updateCellMetadata(resource, cell, { ...cell.metadata, runnable: false }); + await updateCellMetadata(resource, cell, cell.metadata.with({ runnable: false })); await metadataChangeEvent; await vscode.commands.executeCommand('notebook.cell.execute'); assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work metadataChangeEvent = asPromise(vscode.notebook.onDidChangeCellMetadata); - await updateCellMetadata(resource, cell, { ...cell.metadata, runnable: true }); + await updateCellMetadata(resource, cell, cell.metadata.with({ runnable: true })); await metadataChangeEvent; await vscode.commands.executeCommand('notebook.cell.execute'); @@ -870,7 +694,6 @@ suite('Notebook API tests', function () { }); test('document runnable metadata is respected', async () => { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -880,7 +703,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 0); await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { - updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: false }); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: false })); await event; }); @@ -888,7 +711,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { - updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); await event; }); @@ -898,14 +721,12 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await saveAllFilesAndCloseAll(undefined); }); // TODO@rebornix this is wrong, `await vscode.commands.executeCommand('notebook.execute');` doesn't wait until the workspace edit is applied test.skip('cell execute command takes arguments', async () => { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -915,12 +736,10 @@ suite('Notebook API tests', function () { await vscode.commands.executeCommand('notebook.execute'); assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await saveAllFilesAndCloseAll(undefined); }); test('cell execute command takes arguments 2', async () => { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -928,7 +747,7 @@ suite('Notebook API tests', function () { const cell = editor.document.cells[0]; await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { - updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); await event; }); @@ -954,14 +773,10 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await saveAllFilesAndCloseAll(undefined); }); test('document execute command takes arguments', async () => { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -969,7 +784,7 @@ suite('Notebook API tests', function () { const cell = editor.document.cells[0]; const metadataChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); await metadataChangeEvent; assert.strictEqual(editor.document.metadata.runnable, true); @@ -994,14 +809,10 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); }); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await saveAllFilesAndCloseAll(undefined); }); test('cell execute and select kernel', async () => { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -1009,7 +820,7 @@ suite('Notebook API tests', function () { const cell = editor.document.cells[0]; const metadataChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); + updateNotebookMetadata(editor.document.uri, editor.document.metadata.with({ runnable: true })); await metadataChangeEvent; await vscode.commands.executeCommand('notebook.cell.execute'); @@ -1029,14 +840,12 @@ suite('Notebook API tests', function () { 'my second output' ]); - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await saveAllFilesAndCloseAll(undefined); }); // }); // suite('notebook dirty state', () => { test('notebook open', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -1070,7 +879,6 @@ suite('Notebook API tests', function () { // suite('notebook undo redo', () => { test('notebook open', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -1112,115 +920,7 @@ suite('Notebook API tests', function () { await saveFileAndCloseAll(resource); }); - // test.skip('execute and then undo redo', async function () { - // assertInitalState(); - // const resource = await createRandomFile('', undefined, '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - // const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // const cellChangeEventRet = await cellsChangeEvent; - // assert.strictEqual(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); - // assert.strictEqual(cellChangeEventRet.changes.length, 1); - // assert.deepStrictEqual(cellChangeEventRet.changes[0], { - // start: 1, - // deletedCount: 0, - // deletedItems: [], - // items: [ - // vscode.window.activeNotebookEditor!.document.cells[1] - // ] - // }); - - // const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; - - // const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - // await vscode.commands.executeCommand('notebook.cell.moveUp'); - // const moveCellEventRet = await moveCellEvent; - // assert.deepStrictEqual(moveCellEventRet, { - // document: vscode.window.activeNotebookEditor!.document, - // changes: [ - // { - // start: 1, - // deletedCount: 1, - // deletedItems: [secondCell], - // items: [] - // }, - // { - // start: 0, - // deletedCount: 0, - // deletedItems: [], - // items: [vscode.window.activeNotebookEditor?.document.cells[0]] - // } - // ] - // }); - - // const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - // await vscode.commands.executeCommand('notebook.cell.execute'); - // const cellOutputsAddedRet = await cellOutputChange; - // assert.deepStrictEqual(cellOutputsAddedRet, { - // document: vscode.window.activeNotebookEditor!.document, - // cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - // }); - // assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 1); - - // const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - // await vscode.commands.executeCommand('undo'); - // const cellOutputsCleardRet = await cellOutputClear; - // assert.deepStrictEqual(cellOutputsCleardRet, { - // document: vscode.window.activeNotebookEditor!.document, - // cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - // }); - // assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 0); - - // await saveFileAndCloseAll(resource); - // }); - - // }); - - // suite('notebook working copy', () => { - // test('notebook revert on close', async function () { - // const resource = await createRandomFile('', undefined, '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); - - // // close active editor from command will revert the file - // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - // assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); - // assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); - // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); - - // await vscode.commands.executeCommand('workbench.action.files.save'); - // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - // }); - - // test('notebook revert', async function () { - // const resource = await createRandomFile('', undefined, '.vsctestnb'); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - - // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); - // await vscode.commands.executeCommand('workbench.action.files.revert'); - - // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - // assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); - // assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); - // assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 1); - // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); - - // await vscode.commands.executeCommand('workbench.action.files.saveAll'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // }); - test('multiple tabs: dirty + clean', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); @@ -1246,7 +946,6 @@ suite('Notebook API tests', function () { }); test('multiple tabs: two dirty tabs and switching', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); @@ -1279,13 +978,9 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), ''); await saveAllFilesAndCloseAll(secondResource); - // await vscode.commands.executeCommand('workbench.action.files.saveAll'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); }); test('multiple tabs: different editors with same document', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstNotebookEditor = vscode.window.activeNotebookEditor; @@ -1301,18 +996,11 @@ suite('Notebook API tests', function () { assert.notEqual(firstNotebookEditor, secondNotebookEditor); assert.strictEqual(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); - // assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png'))); await saveAllFilesAndCloseAll(resource); - - // await vscode.commands.executeCommand('workbench.action.files.saveAll'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); }); - // }); - // suite('metadata', () => { test('custom metadata should be supported', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -1326,7 +1014,6 @@ suite('Notebook API tests', function () { // TODO@rebornix skip as it crashes the process all the time test.skip('custom metadata should be supported 2', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); @@ -1342,20 +1029,9 @@ suite('Notebook API tests', function () { await saveFileAndCloseAll(resource); }); - // }); - // suite('regression', () => { - // test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () { - // assertInitalState(); - // await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { "viewType": "notebookCoreTest" }); - // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - // }); test('#106657. Opening a notebook from markers view is broken ', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -1373,7 +1049,6 @@ suite('Notebook API tests', function () { }); test.skip('Cannot open notebook from cell-uri with vscode.open-command', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -1391,7 +1066,6 @@ suite('Notebook API tests', function () { }); test('#97830, #97764. Support switch to other editor types', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); @@ -1403,18 +1077,17 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); // no kernel -> no default language - assert.strictEqual(vscode.window.activeNotebookEditor!.kernel, undefined); + // assert.strictEqual(vscode.window.activeNotebookEditor!.kernel, undefined); assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, resource.path); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); }); // open text editor, pin, and then open a notebook test('#96105 - dirty editors', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); const edit = new vscode.WorkspaceEdit(); @@ -1426,19 +1099,17 @@ suite('Notebook API tests', function () { assert.notEqual(vscode.window.activeNotebookEditor, undefined, 'notebook first'); // assert.notEqual(vscode.window.activeTextEditor, undefined); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); }); test('#102411 - untitled notebook creation failed', async function () { - assertInitalState(); await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); assert.notEqual(vscode.window.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); }); test('#102423 - copy/paste shares the same text buffer', async function () { - assertInitalState(); const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); @@ -1458,8 +1129,96 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); assert.notEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), vscode.window.activeNotebookEditor!.document.cells[1].document.getText()); - await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await closeAllEditors(); }); + + test('#116598, output items change event.', async function () { + + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const edit = new vscode.WorkspaceEdit(); + edit.appendNotebookCellOutput(resource, 0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('application/foo', 'bar'), + new vscode.NotebookCellOutputItem('application/json', { data: true }, { metadata: true }), + ])]); + await vscode.workspace.applyEdit(edit); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs.length, 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs.length, 2); + + const appendEdit = new vscode.WorkspaceEdit(); + const newItem = new vscode.NotebookCellOutputItem('text/plain', '1'); + appendEdit.appendNotebookCellOutputItems( + resource, + 0, + vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].id, + [newItem] + ); + await vscode.workspace.applyEdit(appendEdit); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs.length, 3); + assert.deepStrictEqual(vscode.window.activeNotebookEditor!.document.cells[0].outputs[0].outputs[2], newItem); + }); + + test('#115855 onDidSaveNotebookDocument', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = asPromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.window.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.NotebookCellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + }); + + const cellChangeEventRet = await cellsChangeEvent; + assert.strictEqual(cellChangeEventRet.document === vscode.window.activeNotebookEditor?.document, true); + assert.strictEqual(cellChangeEventRet.document.isDirty, true); + + await withEvent(vscode.notebook.onDidSaveNotebookDocument, async event => { + await vscode.commands.executeCommand('workbench.action.files.saveAll'); + await event; + assert.strictEqual(cellChangeEventRet.document.isDirty, false); + }); + await saveAllFilesAndCloseAll(resource); + }); + + + test('#116808, active kernel should not be undefined', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await withEvent(vscode.notebook.onDidChangeActiveNotebookKernel, async event => { + await event; + assert.notStrictEqual(vscode.window.activeNotebookEditor?.kernel, undefined); + assert.strictEqual(vscode.window.activeNotebookEditor?.kernel?.id, 'mainKernel'); + }); + + await saveAllFilesAndCloseAll(resource); + }); + + test('Numeric metadata should get updated correctly', async function () { + const resource = await createRandomFile('', undefined, '.vsctestnb'); + const document = await vscode.notebook.openNotebookDocument(resource); + + const edit = new vscode.WorkspaceEdit(); + const runStartTime = Date.now(); + const lastRunDuration = Date.now() + 1000; + const runState = vscode.NotebookCellRunState.Success; + const executionOrder = 1234; + const metadata = document.cells[0].metadata.with({ + ...document.cells[0].metadata, + runStartTime, + runState, + lastRunDuration, + executionOrder + }); + edit.replaceNotebookCellMetadata(document.uri, 0, metadata); + await vscode.workspace.applyEdit(edit); + + assert.strictEqual(document.cells[0].metadata.runStartTime, runStartTime); + assert.strictEqual(document.cells[0].metadata.lastRunDuration, lastRunDuration); + assert.strictEqual(document.cells[0].metadata.executionOrder, executionOrder); + assert.strictEqual(document.cells[0].metadata.runState, vscode.NotebookCellRunState.Success); + }); + // }); // suite('webview', () => { @@ -1474,7 +1233,7 @@ suite('Notebook API tests', function () { // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); // const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); // assert.strictEqual(uri.scheme, 'vscode-webview-resource'); - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // await closeAllEditors(); // }); @@ -1499,6 +1258,6 @@ suite('Notebook API tests', function () { // await vscode.commands.executeCommand('notebook.cell.execute'); // await promise; - // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // await closeAllEditors(); // }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index a24019b21fe..5e7a544291d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions, Terminal } from 'vscode'; -import { doesNotThrow, equal, deepEqual, throws } from 'assert'; +import { doesNotThrow, equal, deepEqual, throws, strictEqual } from 'assert'; import { assertNoRpc } from '../utils'; // Disable terminal tests: @@ -19,8 +19,6 @@ import { assertNoRpc } from '../utils'; extensionContext = (global as any).testExtensionContext; const config = workspace.getConfiguration('terminal.integrated'); - // Disable conpty in integration tests because of https://github.com/microsoft/vscode/issues/76548 - await config.update('windowsEnableConpty', false, ConfigurationTarget.Global); // Disable exit alerts as tests may trigger then and we're not testing the notifications await config.update('showExitAlert', false, ConfigurationTarget.Global); // Canvas may cause problems when running in a container @@ -57,42 +55,28 @@ import { assertNoRpc } from '../utils'; }); }); - (process.platform === 'linux' ? test.skip : test)('echo works in the default shell', (done) => { - disposables.push(window.onDidOpenTerminal(term => { - try { - equal(terminal, term); - } catch (e) { - done(e); - return; - } - let data = ''; - const dataDisposable = window.onDidWriteTerminalData(e => { - try { - equal(terminal, e.terminal); - } catch (e) { - done(e); - return; - } - data += e.data; - if (data.indexOf(expected) !== 0) { - dataDisposable.dispose(); - terminal.dispose(); - disposables.push(window.onDidCloseTerminal(() => { - done(); - })); - } + test('echo works in the default shell', async () => { + const terminal = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(t => { + strictEqual(terminal, t); + r(terminal); + })); + // Use a single character to avoid winpty/conpty issues with injected sequences + const terminal = window.createTerminal({ + env: { TEST: '`' } }); - disposables.push(dataDisposable); - })); - // Use a single character to avoid winpty/conpty issues with injected sequences - const expected = '`'; - const terminal = window.createTerminal({ - env: { - TEST: '`' - } + terminal.show(); }); - terminal.show(); - doesNotThrow(() => { + + let data = ''; + await new Promise(r => { + disposables.push(window.onDidWriteTerminalData(e => { + strictEqual(terminal, e.terminal); + data += e.data; + if (data.indexOf('`') !== 0) { + r(); + } + })); // Print an environment variable value so the echo statement doesn't get matched if (process.platform === 'win32') { terminal.sendText(`$env:TEST`); @@ -100,6 +84,14 @@ import { assertNoRpc } from '../utils'; terminal.sendText(`echo $TEST`); } }); + + await new Promise(r => { + terminal.dispose(); + disposables.push(window.onDidCloseTerminal(t => { + strictEqual(terminal, t); + r(); + })); + }); }); test('onDidCloseTerminal event fires when terminal is disposed', async () => { diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 153ce130e24..78164843df9 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -8,7 +8,7 @@ "activationEvents": [ "*" ], - "main": "./out/notebookTestMain", + "main": "./out/extension", "enableProposedApi": true, "engines": { "vscode": "^1.25.0" diff --git a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts b/extensions/vscode-notebook-tests/src/extension.ts similarity index 92% rename from extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts rename to extensions/vscode-notebook-tests/src/extension.ts index 0e3e7426f23..55231411594 100644 --- a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts +++ b/extensions/vscode-notebook-tests/src/extension.ts @@ -11,7 +11,7 @@ function wait(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } -export function smokeTestActivate(context: vscode.ExtensionContext): any { +export function activate(context: vscode.ExtensionContext): any { context.subscriptions.push(vscode.commands.registerCommand('vscode-notebook-tests.createNewNotebook', async () => { const workspacePath = vscode.workspace.workspaceFolders![0].uri.fsPath; const notebookPath = path.join(workspacePath, 'test.smoke-nb'); @@ -23,25 +23,21 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookSmokeTest', { openNotebook: async (_resource: vscode.Uri) => { const dto: vscode.NotebookData = { - metadata: {}, + metadata: new vscode.NotebookDocumentMetadata(), cells: [ { source: 'code()', language: 'typescript', cellKind: vscode.NotebookCellKind.Code, outputs: [], - metadata: { - custom: { testCellMetadata: 123 } - } + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }) }, { source: 'Markdown Cell', language: 'markdown', cellKind: vscode.NotebookCellKind.Markdown, outputs: [], - metadata: { - custom: { testCellMetadata: 123 } - } + metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }) } ] }; diff --git a/package.json b/package.json index 3b0ce899f1c..59bf1158a51 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.54.0", - "distro": "59d93479b4a5c88173a91fec50e348126e94a818", + "distro": "8abc3b69c9a71c8da5a17f3e06068485a0a90c4c", "author": { "name": "Microsoft Corporation" }, @@ -190,7 +190,7 @@ "style-loader": "^1.0.0", "ts-loader": "^6.2.1", "tsec": "0.1.3", - "typescript": "4.2.0-dev.20201207", + "typescript": "^4.3.0-dev.20210216", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 3013dc04b90..3f8390b0b2b 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -45,31 +45,33 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: Tests in the extension host -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% + +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% . +call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% . if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %GITWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --enable-proposed-api=vscode.git --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --enable-proposed-api=vscode.git %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in commonJS (CSS, HTML) diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index b1d5e9588e3..0eeefd80bff 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -65,30 +65,31 @@ fi after_suite # Tests in the extension host -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR + +ALL_PLATFORMS_API_TESTS_EXTRA_ARGS="--disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-keytar --disable-extensions --user-data-dir=$VSCODEUSERDATADIR" + +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -# TODO(deepak1556): Disable workspace test temporarily -# https://github.com/microsoft/vscode/issues/111288 -#"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR -#after_suite - -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/markdown-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/markdown-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS +after_suite + +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite # Tests in commonJS (CSS, HTML) diff --git a/src/main.js b/src/main.js index 2584223e1d6..97961a64821 100644 --- a/src/main.js +++ b/src/main.js @@ -371,7 +371,8 @@ function configureCrashReporter() { companyName: companyName, productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, submitURL, - uploadToServer: !crashReporterDirectory + uploadToServer: !crashReporterDirectory, + compress: true }); } diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 2a28dace3c2..49667d70eef 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, IActionRunner } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export interface IContextMenuEvent { readonly shiftKey?: boolean; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 8f1b3448554..d4802492f21 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1475,7 +1475,7 @@ export class ModifierKeyEmitter extends Emitter { metaKey: false }; - this._subscriptions.add(domEvent(document.body, 'keydown', true)(e => { + this._subscriptions.add(domEvent(window, 'keydown', true)(e => { const event = new StandardKeyboardEvent(e); // If Alt-key keydown event is repeated, ignore it #112347 @@ -1509,7 +1509,7 @@ export class ModifierKeyEmitter extends Emitter { } })); - this._subscriptions.add(domEvent(document.body, 'keyup', true)(e => { + this._subscriptions.add(domEvent(window, 'keyup', true)(e => { if (!e.altKey && this._keyStatus.altKey) { this._keyStatus.lastKeyReleased = 'alt'; } else if (!e.ctrlKey && this._keyStatus.ctrlKey) { diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 9abeda1c547..f8cde279f9d 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -8,13 +8,14 @@ import * as platform from 'vs/base/common/platform'; import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; -import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator } from 'vs/base/common/actions'; import * as types from 'vs/base/common/types'; import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { DataTransfers } from 'vs/base/browser/dnd'; import { isFirefox } from 'vs/base/browser/browser'; import { $, addDisposableListener, append, EventHelper, EventLike, EventType } from 'vs/base/browser/dom'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export interface IBaseActionViewItemOptions { draggable?: boolean; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 75dbbb12cc4..cc0608fe9fb 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -55,6 +55,7 @@ } .monaco-action-bar .action-item.disabled .action-label, +.monaco-action-bar .action-item.disabled .action-label::before, .monaco-action-bar .action-item.disabled .action-label:hover { opacity: 0.4; } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 83135785546..3912a6c58e0 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./actionbar'; -import { Disposable, dispose } from 'vs/base/common/lifecycle'; -import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator, IActionViewItem, IActionViewItemProvider } from 'vs/base/common/actions'; +import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -13,6 +13,19 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Emitter } from 'vs/base/common/event'; import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +export interface IActionViewItem extends IDisposable { + actionRunner: IActionRunner; + setActionContext(context: any): void; + render(element: HTMLElement): void; + isEnabled(): boolean; + focus(fromRight?: boolean): void; // TODO@isidorn what is this? + blur(): void; +} + +export interface IActionViewItemProvider { + (action: IAction): IActionViewItem | undefined; +} + export const enum ActionsOrientation { HORIZONTAL, HORIZONTAL_REVERSE, @@ -35,8 +48,6 @@ export interface IActionBarOptions { readonly triggerKeys?: ActionTrigger; readonly allowContextMenu?: boolean; readonly preventLoopNavigation?: boolean; - // Pass true here when the up and down keys should not be eaten up by the ActionBar. For example, when an ActionBar is in the list. - readonly respectOrientationForPreviousAndNextKey?: boolean; } export interface IActionOptions extends IActionViewItemOptions { @@ -120,22 +131,22 @@ export class ActionBar extends Disposable implements IActionRunner { switch (this._orientation) { case ActionsOrientation.HORIZONTAL: - previousKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow] : [KeyCode.LeftArrow, KeyCode.UpArrow]; - nextKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.RightArrow] : [KeyCode.RightArrow, KeyCode.DownArrow]; + previousKeys = [KeyCode.LeftArrow]; + nextKeys = [KeyCode.RightArrow]; break; case ActionsOrientation.HORIZONTAL_REVERSE: - previousKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.RightArrow] : [KeyCode.RightArrow, KeyCode.DownArrow]; - nextKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.LeftArrow] : [KeyCode.LeftArrow, KeyCode.UpArrow]; + previousKeys = [KeyCode.RightArrow]; + nextKeys = [KeyCode.LeftArrow]; this.domNode.className += ' reverse'; break; case ActionsOrientation.VERTICAL: - previousKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.UpArrow] : [KeyCode.LeftArrow, KeyCode.UpArrow]; - nextKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.DownArrow] : [KeyCode.RightArrow, KeyCode.DownArrow]; + previousKeys = [KeyCode.UpArrow]; + nextKeys = [KeyCode.DownArrow]; this.domNode.className += ' vertical'; break; case ActionsOrientation.VERTICAL_REVERSE: - previousKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.DownArrow] : [KeyCode.RightArrow, KeyCode.DownArrow]; - nextKeys = this.options.respectOrientationForPreviousAndNextKey ? [KeyCode.UpArrow] : [KeyCode.LeftArrow, KeyCode.UpArrow]; + previousKeys = [KeyCode.DownArrow]; + nextKeys = [KeyCode.UpArrow]; this.domNode.className += ' vertical reverse'; break; } @@ -228,9 +239,9 @@ export class ActionBar extends Disposable implements IActionRunner { setFocusable(focusable: boolean): void { this.focusable = focusable; if (this.focusable) { - const first = this.viewItems.find(vi => vi instanceof BaseActionViewItem); - if (first instanceof BaseActionViewItem) { - first.setFocusable(true); + const firstEnabled = this.viewItems.find(vi => vi instanceof BaseActionViewItem && vi.isEnabled()); + if (firstEnabled instanceof BaseActionViewItem) { + firstEnabled.setFocusable(true); } } else { this.viewItems.forEach(vi => { @@ -319,7 +330,7 @@ export class ActionBar extends Disposable implements IActionRunner { item.setActionContext(this.context); item.render(actionViewItemElement); - if (this.focusable && this.viewItems.length === 0 && item instanceof BaseActionViewItem) { + if (this.focusable && item instanceof BaseActionViewItem && this.viewItems.length === 0) { // We need to allow for the first enabled item to be focused on using tab navigation #106441 item.setFocusable(true); } @@ -335,7 +346,7 @@ export class ActionBar extends Disposable implements IActionRunner { index++; } }); - if (this.focusedItem) { + if (typeof this.focusedItem === 'number') { // After a clear actions might be re-added to simply toggle some actions. We should preserve focus #97128 this.focus(this.focusedItem); } @@ -419,19 +430,15 @@ export class ActionBar extends Disposable implements IActionRunner { } const startIndex = this.focusedItem; - let item: IActionViewItem; - do { - if (this.options.preventLoopNavigation && this.focusedItem + 1 >= this.viewItems.length) { - this.focusedItem = startIndex; - return false; - } + if (this.options.preventLoopNavigation && this.focusedItem + 1 >= this.viewItems.length) { + this.focusedItem = startIndex; + return false; + } - this.focusedItem = (this.focusedItem + 1) % this.viewItems.length; - item = this.viewItems[this.focusedItem]; - } while (this.focusedItem !== startIndex && !item.isEnabled()); + this.focusedItem = (this.focusedItem + 1) % this.viewItems.length; - if (this.focusedItem === startIndex && !item.isEnabled()) { + if (this.focusedItem === startIndex) { this.focusedItem = undefined; } @@ -445,24 +452,19 @@ export class ActionBar extends Disposable implements IActionRunner { } const startIndex = this.focusedItem; - let item: IActionViewItem; + this.focusedItem = this.focusedItem - 1; - do { - this.focusedItem = this.focusedItem - 1; - - if (this.focusedItem < 0) { - if (this.options.preventLoopNavigation) { - this.focusedItem = startIndex; - return false; - } - - this.focusedItem = this.viewItems.length - 1; + if (this.focusedItem < 0) { + if (this.options.preventLoopNavigation) { + this.focusedItem = startIndex; + return false; } - item = this.viewItems[this.focusedItem]; - } while (this.focusedItem !== startIndex && !item.isEnabled()); + this.focusedItem = this.viewItems.length - 1; + } - if (this.focusedItem === startIndex && !item.isEnabled()) { + + if (this.focusedItem === startIndex) { this.focusedItem = undefined; } @@ -480,12 +482,10 @@ export class ActionBar extends Disposable implements IActionRunner { const actionViewItem = item; if (i === this.focusedItem) { - if (types.isFunction(actionViewItem.isEnabled)) { - if (actionViewItem.isEnabled() && types.isFunction(actionViewItem.focus)) { - actionViewItem.focus(fromRight); - } else { - this.actionsList.focus({ preventScroll }); - } + if (types.isFunction(actionViewItem.focus)) { + actionViewItem.focus(fromRight); + } else { + this.actionsList.focus({ preventScroll }); } } else { if (types.isFunction(actionViewItem.blur)) { diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css index bae18d887f4..4b5b5141489 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css @@ -13,7 +13,7 @@ } } -.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin .codicon-gear.codicon-modifier-spin { +.codicon-sync.codicon-modifier-spin, .codicon-loading.codicon-modifier-spin, .codicon-gear.codicon-modifier-spin { /* Use steps to throttle FPS to reduce CPU usage */ animation: codicon-spin 1.5s steps(30) infinite; } diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 01abb46b868..b1a42c0999c 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 2aaac8a7ab3..67e2e9bb7c1 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./dropdown'; -import { Action, IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; +import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -14,6 +14,7 @@ import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseAction import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { Codicon } from 'vs/base/common/codicons'; +import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -172,7 +173,10 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { const menuActionsProvider = { getActions: () => { const actionsProvider = (this.options).menuActionsOrProvider; - return [this._action, ...(Array.isArray(actionsProvider) ? actionsProvider : actionsProvider.getActions())]; + return [this._action, ...(Array.isArray(actionsProvider) + ? actionsProvider + : (actionsProvider as IActionProvider).getActions()) // TODO: microsoft/TypeScript#42768 + ]; } }; this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(this.options).menuActionClassNames || []] }); diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 9a9132359ce..958d5f12868 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -5,8 +5,8 @@ import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; -import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider, EmptySubmenuAction } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionRunner, IAction, SubmenuAction, Separator, EmptySubmenuAction } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { EventType, EventHelper, EventLike, isAncestor, addDisposableListener, append, $, clearNode, createStyleSheet, isInShadowDOM, getActiveElement, Dimension, IDomNodePagePosition } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -86,7 +86,6 @@ export class Menu extends ActionBar { context: options.context, actionRunner: options.actionRunner, ariaLabel: options.ariaLabel, - respectOrientationForPreviousAndNextKey: true, triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true } }); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 3b91ffa5573..8d1c5b6b17a 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -5,8 +5,8 @@ import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; -import { Action, IActionRunner, IAction, IActionViewItemProvider, SubmenuAction } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IActionRunner, IAction, SubmenuAction } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -28,7 +28,6 @@ export interface IToolBarOptions { anchorAlignmentProvider?: () => AnchorAlignment; renderDropdownAsChildElement?: boolean; moreIcon?: CSSIcon; - readonly respectOrientationForPreviousAndNextKey?: boolean; } /** @@ -64,7 +63,6 @@ export class ToolBar extends Disposable { orientation: options.orientation, ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, - respectOrientationForPreviousAndNextKey: options.respectOrientationForPreviousAndNextKey, actionViewItemProvider: (action: IAction) => { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 74db8a99f21..466be3c5cae 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -39,19 +39,6 @@ export interface IActionRunner extends IDisposable { readonly onBeforeRun: Event; } -export interface IActionViewItem extends IDisposable { - actionRunner: IActionRunner; - setActionContext(context: any): void; - render(element: any /* HTMLElement */): void; - isEnabled(): boolean; - focus(fromRight?: boolean): void; // TODO@isidorn what is this? - blur(): void; -} - -export interface IActionViewItemProvider { - (action: IAction): IActionViewItem | undefined; -} - export interface IActionChangeEvent { readonly label?: string; readonly tooltip?: string; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 8682ac8158a..95601fc7ac0 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -543,6 +543,9 @@ export namespace Codicon { export const typeHierarchySub = new Codicon('type-hierarchy-sub', { character: '\\ebba' }); export const typeHierarchySuper = new Codicon('type-hierarchy-super', { character: '\\ebbb' }); export const gitPullRequestCreate = new Codicon('git-pull-request-create', { character: '\\ebbc' }); + export const runAbove = new Codicon('run-above', { character: '\\ebbd' }); + export const runBelow = new Codicon('run-below', { character: '\\ebbe' }); + export const notebookTemplate = new Codicon('notebook-template', { character: '\\ebbf' }); export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition); } diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index adfbdc906ab..8b60c8757dc 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -94,19 +94,19 @@ export namespace Iterable { /** * Returns an iterable slice of the array, with the same semantics as `array.slice()`. */ - export function* slice(iterable: ReadonlyArray, from: number, to = iterable.length): Iterable { + export function* slice(arr: ReadonlyArray, from: number, to = arr.length): Iterable { if (from < 0) { - from += iterable.length; + from += arr.length; } if (to < 0) { - to += iterable.length; - } else if (to > iterable.length) { - to = iterable.length; + to += arr.length; + } else if (to > arr.length) { + to = arr.length; } for (; from < to; from++) { - yield iterable[from]; + yield arr[from]; } } @@ -135,4 +135,25 @@ export namespace Iterable { return [consumed, { [Symbol.iterator]() { return iterator; } }]; } + + /** + * Returns whether the iterables are the same length and all items are + * equal using the comparator function. + */ + export function equals(a: Iterable, b: Iterable, comparator = (at: T, bt: T) => at === bt) { + const ai = a[Symbol.iterator](); + const bi = b[Symbol.iterator](); + while (true) { + const an = ai.next(); + const bn = bi.next(); + + if (an.done !== bn.done) { + return false; + } else if (an.done) { + return true; + } else if (!comparator(an.value, bn.value)) { + return false; + } + } + } } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 0e08876e537..685cbaa95e4 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -1048,3 +1048,85 @@ export class LRUCache extends LinkedMap { } } } + +type IndexRecord = [fn: (value: V) => R, map: Map]; + +/** + * Map that supports multiple indicies whose keys are derived from the value. + */ +export class IndexedSet implements Set { + public get size() { + return this.source.size; + } + + private source = new Set(); + private readonly indexes: IndexRecord[] = []; + + /** + * Creates a map that maintains a copy of the items in the set, indexed + * by the given 'indexer' function. + */ + index(indexer: (value: V) => R) { + const map = new Map(); + for (const value of this.source) { + map.set(indexer(value), value); + } + + this.indexes.push([indexer, map]); + return map as ReadonlyMap; + } + + add(value: V): this { + this.source.add(value); + for (const [index, map] of this.indexes) { + map.set(index(value), value); + } + + return this; + } + + clear(): void { + this.source.clear(); + for (const [, map] of this.indexes) { + map.clear(); + } + } + + delete(value: V): boolean { + if (!this.source.delete(value)) { + return false; + } + + for (const [index, map] of this.indexes) { + map.delete(index(value)); + } + + return true; + } + + forEach(callbackfn: (value: V, value2: V, set: Set) => void, thisArg?: any): void { + this.source.forEach(callbackfn, thisArg); + } + + has(value: V): boolean { + return this.source.has(value); + } + + [Symbol.toStringTag]: string; + + values(): IterableIterator { + return this.source.values(); + } + + entries(): IterableIterator<[V, V]> { + return this.source.entries(); + } + + keys(): IterableIterator { + return this.source.keys(); + } + + [Symbol.iterator](): IterableIterator { + return this.source.values(); + } +} diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index a409e7722ab..402d5149722 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -64,6 +64,8 @@ export namespace Schemas { export const vscodeSettings = 'vscode-settings'; + export const vscodeWorkspaceTrust = 'vscode-workspace-trust'; + export const webviewPanel = 'webview-panel'; /** diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 66023e7d651..a2c28e17070 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -84,7 +84,7 @@ export class Storage extends Disposable implements IStorage { private cache = new Map(); - private readonly flushDelayer = this._register(new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY)); + private readonly flushDelayer = new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY); private pendingDeletes = new Set(); private pendingInserts = new Map(); @@ -319,6 +319,13 @@ export class Storage extends Disposable implements IStorage { return new Promise(resolve => this.whenFlushedCallbacks.push(resolve)); } + + dispose(): void { + this.flushDelayer.cancel(); // workaround https://github.com/microsoft/vscode/issues/116777 + this.flushDelayer.dispose(); + + super.dispose(); + } } export class InMemoryStorageDatabase implements IStorageDatabase { diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 46193801f71..990e531905e 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -104,6 +104,7 @@ flakySuite('Storage Library', function () { strictEqual(deletePromiseResolved, true); await storage.close(); + await storage.close(); // it is ok to call this multiple times }); test('external changes', async () => { diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index 7f32bc3eb6a..a7cdd692c6f 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -25,4 +25,11 @@ suite('Iterable', function () { assert.equal(Iterable.first(customIterable), 'one'); // fresh }); + test('equals', () => { + assert.strictEqual(Iterable.equals([1, 2], [1, 2]), true); + assert.strictEqual(Iterable.equals([1, 2], [1]), false); + assert.strictEqual(Iterable.equals([1], [1, 2]), false); + assert.strictEqual(Iterable.equals([2, 1], [1, 2]), false); + }); + }); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index f157455531a..0c9fc51e097 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator, ConfigKeysIterator } from 'vs/base/common/map'; -import { URI } from 'vs/base/common/uri'; +import { ConfigKeysIterator, IndexedSet, LinkedMap, LRUCache, PathIterator, ResourceMap, StringIterator, TernarySearchTree, Touch, UriIterator } from 'vs/base/common/map'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; suite('Map', () => { @@ -1020,4 +1020,30 @@ suite('Map', () => { assert.strictEqual(map.get(windowsFile), 'true'); assert.strictEqual(map.get(uncFile), 'true'); }); + + test('IndexedSet - add', () => { + const i = new IndexedSet(); + i.add(2); + const map = i.index(v => v ** 2); + assert.deepStrictEqual(map, new Map([[4, 2]])); + + i.add(3); + assert.deepStrictEqual(map, new Map([[9, 3], [4, 2]])); + }); + + test('IndexedSet - delete', () => { + const i = new IndexedSet(); + const map = i.index(v => v ** 2); + i.add(2); + i.delete(2); + assert.deepStrictEqual(map, new Map([])); + }); + + test('IndexedSet - clear', () => { + const i = new IndexedSet(); + const map = i.index(v => v ** 2); + i.add(2); + i.clear(); + assert.deepStrictEqual(map, new Map([])); + }); }); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index f60f74463ca..4ebb2bf0fec 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -80,6 +80,9 @@ import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProc import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { join } from 'vs/base/common/path'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { LocalPtyService } from 'vs/platform/terminal/electron-browser/localPtyService'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; class SharedProcessMain extends Disposable { @@ -259,6 +262,10 @@ class SharedProcessMain extends Disposable { services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); + // Terminal + const localPtyService = this._register(new LocalPtyService(logService)); + services.set(ILocalPtyService, localPtyService); + return new InstantiationService(services); } @@ -296,6 +303,11 @@ class SharedProcessMain extends Disposable { const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService)); const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + + // Terminal + const localPtyService = accessor.get(ILocalPtyService); + const localPtyChannel = ProxyChannel.fromService(localPtyService); + this.server.registerChannel(TerminalIpcChannels.LocalPty, localPtyChannel); } private registerErrorHandler(logService: ILogService): void { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 1a378fd011f..6d03ad233a4 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -37,7 +37,7 @@ import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; @@ -225,11 +225,19 @@ export class CodeApplication extends Disposable { this.nativeHostMainService?.openExternal(undefined, url); }); - session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => { + const webviewFrameUrl = 'about:blank?webviewFrame'; + + session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => { + if (details.requestingUrl === webviewFrameUrl) { + return callback(permission === 'clipboard-read'); + } return callback(false); }); - session.defaultSession.setPermissionCheckHandler((webContents, permission /* 'media' */) => { + session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => { + if (details.requestingUrl === webviewFrameUrl) { + return permission === 'clipboard-read'; + } return false; }); }); @@ -388,7 +396,7 @@ export class CodeApplication extends Disposable { // take only the message and stack property const friendlyError = { - message: err.message, + message: `[uncaught exception in main]: ${err.message}`, stack: err.stack }; @@ -465,7 +473,7 @@ export class CodeApplication extends Disposable { const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer, fileProtocolHandler)); // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentMainService.args.trace) { @@ -926,11 +934,28 @@ export class CodeApplication extends Disposable { return { fileUri: URI.file(path) }; } - private async afterWindowOpen(accessor: ServicesAccessor): Promise { + private async afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): Promise { // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; + // Observe shared process for errors + const telemetryService = accessor.get(ITelemetryService); + this._register(sharedProcess.onDidError(e => { + + // Logging + onUnexpectedError(new Error(e.message)); + + // Telemetry + type SharedProcessErrorClassification = { + type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; + }; + type SharedProcessErrorEvent = { + type: WindowError; + }; + telemetryService.publicLog2('sharedprocesserror', { type: e.type }); + })); + // Windows: install mutex const win32MutexName = product.win32MutexName; if (isWindows && win32MutexName) { diff --git a/src/vs/code/electron-main/protocol.ts b/src/vs/code/electron-main/protocol.ts index 3e535155c76..298bee5c07b 100644 --- a/src/vs/code/electron-main/protocol.ts +++ b/src/vs/code/electron-main/protocol.ts @@ -31,8 +31,11 @@ export class FileProtocolHandler extends Disposable { // Define an initial set of roots we allow loading from // - appRoot : all files installed as part of the app // - extensions : all files shipped from extensions + // - storage : all files in global and workspace storage (https://github.com/microsoft/vscode/issues/116735) this.validRoots.set(URI.file(environmentService.appRoot), true); this.validRoots.set(URI.file(environmentService.extensionsPath), true); + this.validRoots.set(environmentService.globalStorageHome, true); + this.validRoots.set(environmentService.workspaceStorageHome, true); // Register vscode-file:// handler defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback)); diff --git a/src/vs/editor/browser/core/keybindingCancellation.ts b/src/vs/editor/browser/core/keybindingCancellation.ts index 1f260f9b6cd..206fbeac885 100644 --- a/src/vs/editor/browser/core/keybindingCancellation.ts +++ b/src/vs/editor/browser/core/keybindingCancellation.ts @@ -12,6 +12,7 @@ import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cance import { LinkedList } from 'vs/base/common/linkedList'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { localize } from 'vs/nls'; const IEditorCancellationTokens = createDecorator('IEditorCancelService'); @@ -22,7 +23,7 @@ interface IEditorCancellationTokens { cancel(editor: ICodeEditor): void; } -const ctxCancellableOperation = new RawContextKey('cancellableOperation', false); +const ctxCancellableOperation = new RawContextKey('cancellableOperation', false, localize('cancellableOperation', 'Whether the editor runs a cancellable operation, e.g. like \'Peek References\'')); registerSingleton(IEditorCancellationTokens, class implements IEditorCancellationTokens { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index c67cb67e60c..6256e77b6ec 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1151,6 +1151,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } else { result.wordWrapOverride1 = this._diffWordWrap; } + if (options.originalAriaLabel) { + result.ariaLabel = options.originalAriaLabel; + } result.readOnly = !this._originalIsEditable; result.extraEditorClassName = 'original-in-monaco-diff-editor'; return { @@ -1164,6 +1167,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _adjustOptionsForRightHandSide(options: Readonly): editorBrowser.IEditorConstructionOptions { const result = this._adjustOptionsForSubEditor(options); + if (options.modifiedAriaLabel) { + result.ariaLabel = options.modifiedAriaLabel; + } + result.wordWrapOverride1 = this._diffWordWrap; result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; result.scrollbar!.verticalHasArrows = false; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index bce85536162..fd183e4fd8c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -690,6 +690,14 @@ export interface IDiffEditorOptions extends IEditorOptions { * Control the wrapping of the diff editor. */ diffWordWrap?: 'off' | 'on' | 'inherit'; + /** + * Aria label for original editor. + */ + originalAriaLabel?: string; + /** + * Aria label for modifed editor. + */ + modifiedAriaLabel?: string; } //#endregion diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index f4b946213dd..8f07046449c 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -266,7 +266,7 @@ export interface HoverProvider { } /** - * An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are + * An evaluatable expression represents additional information for an expression in a document. Evaluatable expressions are * evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget. * @internal */ @@ -275,15 +275,16 @@ export interface EvaluatableExpression { * The range to which this expression applies. */ range: IRange; - /* + /** * This expression overrides the expression extracted from the range. */ expression?: string; } + /** - * The hover provider interface defines the contract between extensions and - * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature. + * The evaluatable expression provider interface defines the contract between extensions and + * the debug hover. * @internal */ export interface EvaluatableExpressionProvider { @@ -295,6 +296,73 @@ export interface EvaluatableExpressionProvider { provideEvaluatableExpression(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +/** + * An open ended information bag passed to the inline value provider. + * A minimal context containes just the document location where the debugger has stopped. + * @internal + */ +export interface InlineValueContext { + stoppedLocation: Range; +} + +/** + * Provide inline value as text. + * @internal + */ +export interface InlineValueText { + type: 'text'; + range: IRange; + text: string; +} + +/** + * Provide inline value through a variable lookup. + * @internal + */ +export interface InlineValueVariableLookup { + type: 'variable'; + range: IRange; + variableName?: string; + caseSensitiveLookup: boolean; +} + +/** + * Provide inline value through an expression evaluation. + * @internal + */ +export interface InlineValueExpression { + type: 'expression'; + range: IRange; + expression?: string; +} + +/** + * Inline value information can be provided by different means: + * - directly as a text value (class InlineValueText). + * - as a name to use for a variable lookup (class InlineValueVariableLookup) + * - as an evaluatable expression (class InlineValueEvaluatableExpression) + * The InlineValue types combines all inline value types into one type. + * @internal + */ +export type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueExpression; + +/** + * The inline values provider interface defines the contract between extensions and + * the debugger's inline values feature. + * @internal + */ +export interface InlineValuesProvider { + /** + */ + onDidChangeInlineValues?: Event | undefined; + /** + * Provide the "inline values" for the given range and document. Multiple hovers at the same + * position will be merged by the editor. A hover can have a range which defaults + * to the word range at the position when omitted. + */ + provideInlineValues(model: model.ITextModel, viewPort: Range, context: InlineValueContext, token: CancellationToken): ProviderResult; +} + export const enum CompletionItemKind { Method, Function, @@ -1745,6 +1813,11 @@ export const HoverProviderRegistry = new LanguageFeatureRegistry( */ export const EvaluatableExpressionProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const InlineValuesProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/editor/common/modes/languageFeatureRegistry.ts b/src/vs/editor/common/modes/languageFeatureRegistry.ts index b5cd1ca4a75..7dec322f53f 100644 --- a/src/vs/editor/common/modes/languageFeatureRegistry.ts +++ b/src/vs/editor/common/modes/languageFeatureRegistry.ts @@ -9,7 +9,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; import { MovingAverage } from 'vs/base/common/numbers'; import { ITextModel } from 'vs/editor/common/model'; -import { LanguageSelector, score } from 'vs/editor/common/modes/languageSelector'; +import { LanguageFilter, LanguageSelector, score } from 'vs/editor/common/modes/languageSelector'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; interface Entry { @@ -25,7 +25,7 @@ function isExclusive(selector: LanguageSelector): boolean { } else if (Array.isArray(selector)) { return selector.every(isExclusive); } else { - return !!selector.exclusive; + return !!(selector as LanguageFilter).exclusive; // TODO: microsoft/TypeScript#42768 } } diff --git a/src/vs/editor/common/modes/languageSelector.ts b/src/vs/editor/common/modes/languageSelector.ts index eff5d35ab30..02826877fdc 100644 --- a/src/vs/editor/common/modes/languageSelector.ts +++ b/src/vs/editor/common/modes/languageSelector.ts @@ -55,7 +55,7 @@ export function score(selector: LanguageSelector | undefined, candidateUri: URI, } else if (selector) { // filter -> select accordingly, use defaults for scheme - const { language, pattern, scheme, hasAccessToAllModels } = selector; + const { language, pattern, scheme, hasAccessToAllModels } = selector as LanguageFilter; // TODO: microsoft/TypeScript#42768 if (!candidateIsSynchronized && !hasAccessToAllModels) { return 0; diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 60aca88ad68..d32d7146a6a 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -37,9 +37,14 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe // Name & Extension(s) if (name) { classes.push(`${name}-name-file-icon`); - const dotSegments = name.split('.'); - for (let i = 1; i < dotSegments.length; i++) { - classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one + // Avoid doing an explosive combination of extensions for very long filenames + // (most file systems do not allow files > 255 length) with lots of `.` characters + // https://github.com/microsoft/vscode/issues/116199 + if (name.length <= 255) { + const dotSegments = name.split('.'); + for (let i = 1; i < dotSegments.length; i++) { + classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one + } } classes.push(`ext-file-icon`); // extra segment to increase file-ext score } diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 25b7687bc4a..3fc3033b397 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -223,8 +223,7 @@ function getDocumentation( return undefined; } -CommandsRegistry.registerCommand('_executeCodeActionProvider', async function (accessor, ...args): Promise> { - const [resource, rangeOrSelection, kind, itemResolveCount] = args; +CommandsRegistry.registerCommand('_executeCodeActionProvider', async function (accessor, resource: URI, rangeOrSelection: Range | Selection, kind?: string, itemResolveCount?: number): Promise> { if (!(resource instanceof URI)) { throw illegalArgument(); } @@ -244,14 +243,14 @@ CommandsRegistry.registerCommand('_executeCodeActionProvider', async function (a throw illegalArgument(); } + const include = typeof kind === 'string' ? new CodeActionKind(kind) : undefined; const codeActionSet = await getCodeActions( model, validatedRangeOrSelection, - { type: modes.CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + { type: modes.CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include } }, Progress.None, CancellationToken.None); - const resolving: Promise[] = []; const resolveCount = Math.min(codeActionSet.validActions.length, typeof itemResolveCount === 'number' ? itemResolveCount : 0); for (let i = 0; i < resolveCount; i++) { diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index 319f0d521d7..d6f3357d3cd 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -24,7 +24,7 @@ export class MessageController implements IEditorContribution { public static readonly ID = 'editor.contrib.messageController'; - static readonly MESSAGE_VISIBLE = new RawContextKey('messageVisible', false); + static readonly MESSAGE_VISIBLE = new RawContextKey('messageVisible', false, nls.localize('messageVisible', 'Whether the editor is currently showing an inline message')); static get(editor: ICodeEditor): MessageController { return editor.getContribution(MessageController.ID); diff --git a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts index 9eed3c9900c..5adcbc39882 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetDetails.ts @@ -131,7 +131,7 @@ export class SuggestDetailsWidget { md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name}' with '${item.word}'` : ' (no prefix)'}\n`; md += `distance: ${item.distance}, see localityBonus-setting\n`; md += `index: ${item.idx}, based on ${item.completion.sortText && `sortText: "${item.completion.sortText}"` || 'label'}\n`; - md += `commit characters: ${item.completion.commitCharacters}\n`; + md += `commit characters: ${item.completion.commitCharacters?.join('')}\n`; documentation = new MarkdownString().appendCodeblock('empty', md); detail = `Provider: ${item.provider._debugDisplayName}`; } diff --git a/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts b/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts index 3aa92bca3f4..5739f38f198 100644 --- a/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts +++ b/src/vs/editor/contrib/suggest/suggestWidgetStatus.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IActionViewItemProvider, IAction } from 'vs/base/common/actions'; +import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { suggestWidgetStatusbarMenu } from 'vs/editor/contrib/suggest/suggest'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5b0421be3f5..7b729f29901 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3247,6 +3247,14 @@ declare namespace monaco.editor { * Control the wrapping of the diff editor. */ diffWordWrap?: 'off' | 'on' | 'inherit'; + /** + * Aria label for original editor. + */ + originalAriaLabel?: string; + /** + * Aria label for modifed editor. + */ + modifiedAriaLabel?: string; } /** @@ -4080,12 +4088,12 @@ declare namespace monaco.editor { accessibilitySupport: IEditorOption; accessibilityPageSize: IEditorOption; ariaLabel: IEditorOption; - autoClosingBrackets: IEditorOption; - autoClosingOvertype: IEditorOption; - autoClosingQuotes: IEditorOption; + autoClosingBrackets: IEditorOption; + autoClosingOvertype: IEditorOption; + autoClosingQuotes: IEditorOption; autoIndent: IEditorOption; automaticLayout: IEditorOption; - autoSurround: IEditorOption; + autoSurround: IEditorOption; stickyTabStops: IEditorOption; codeLens: IEditorOption; codeLensFontFamily: IEditorOption; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 1f4a5a81567..7be645fcff0 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -121,6 +121,7 @@ export class MenuId { static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu'); + static readonly TestItem = new MenuId('TestItem'); static readonly TouchBarContext = new MenuId('TouchBarContext'); static readonly TitleBarContext = new MenuId('TitleBarContext'); static readonly TunnelContext = new MenuId('TunnelContext'); diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 00e2bde4875..e40016315b3 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -8,6 +8,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; import { distinct } from 'vs/base/common/objects'; +import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression, RawContextKey, ContextKeyInfo } from 'vs/platform/contextkey/common/contextkey'; @@ -578,6 +579,17 @@ CommandsRegistry.registerCommand(SET_CONTEXT_COMMAND_ID, function (accessor, con accessor.get(IContextKeyService).createKey(String(contextKey), contextValue); }); +CommandsRegistry.registerCommand({ + id: 'getContextKeyInfo', + handler() { + return [...RawContextKey.all()].sort((a, b) => a.key.localeCompare(b.key)); + }, + description: { + description: localize('getContextKeyInfo', "A command that returns information about context keys"), + args: [] + } +}); + CommandsRegistry.registerCommand('_generateContextKeyInfo', function () { const result: ContextKeyInfo[] = []; const seen = new Set(); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 22405c003f8..af2326a7565 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -1259,7 +1259,7 @@ export class ContextKeyOrExpr implements IContextKeyExpression { export interface ContextKeyInfo { readonly key: string; - readonly type: string; + readonly type?: string; readonly description?: string; } @@ -1273,12 +1273,16 @@ export class RawContextKey extends ContextKeyDefinedExpr { private readonly _defaultValue: T | undefined; - constructor(readonly key: string, defaultValue: T | undefined, description?: string) { + constructor(readonly key: string, defaultValue: T | undefined, metaOrHide?: string | true | { type: string, description: string }) { super(key); this._defaultValue = defaultValue; // collect all context keys into a central place - RawContextKey._info.push({ key, description, type: typeof defaultValue }); + if (typeof metaOrHide === 'object') { + RawContextKey._info.push({ ...metaOrHide, key }); + } else if (metaOrHide !== true) { + RawContextKey._info.push({ key, description: metaOrHide, type: defaultValue !== null && defaultValue !== undefined ? typeof defaultValue : undefined }); + } } public bindTo(target: IContextKeyService): IContextKey { diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 019410e6a30..d36a3dfc49c 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -63,6 +63,7 @@ export interface NativeParsedArgs { 'export-default-configuration'?: string; 'install-source'?: string; 'disable-updates'?: boolean; + 'disable-keytar'?: boolean; 'disable-crash-reporter'?: boolean; 'crash-reporter-directory'?: string; 'crash-reporter-id'?: string; diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts index 35b09d89d39..49acc89b969 100644 --- a/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -36,6 +36,7 @@ export interface IEnvironmentMainService extends INativeEnvironmentService { sandbox: boolean; driverVerbose: boolean; disableUpdates: boolean; + disableKeytar: boolean; } export class EnvironmentMainService extends NativeEnvironmentService implements IEnvironmentMainService { @@ -61,6 +62,9 @@ export class EnvironmentMainService extends NativeEnvironmentService implements @memoize get disableUpdates(): boolean { return !!this._args['disable-updates']; } + @memoize + get disableKeytar(): boolean { return !!this._args['disable-keytar']; } + @memoize get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; } } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 7b6a60b5728..6f532262ae0 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -95,6 +95,7 @@ export const OPTIONS: OptionDescriptions> = { 'skip-release-notes': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, + 'disable-keytar': { type: 'boolean' }, 'disable-crash-reporter': { type: 'boolean' }, 'crash-reporter-directory': { type: 'string' }, 'crash-reporter-id': { type: 'string' }, diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 8f0a3b204c3..a3b24d7c4a7 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -113,6 +113,15 @@ export interface IAuthenticationContribution { readonly label: string; } +export interface IGettingStartedContent { + readonly id: string; + readonly title: string; + readonly description: string; + readonly button: { title: string } & ({ command?: never, link: string } | { command: string, link?: never }), + readonly media: { path: string | { hc: string, light: string, dark: string }, altText: string }, + readonly when?: string; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration | IConfiguration[]; @@ -132,6 +141,7 @@ export interface IExtensionContributions { readonly customEditors?: readonly IWebviewEditor[]; readonly codeActions?: readonly ICodeActionContribution[]; authentication?: IAuthenticationContribution[]; + gettingStarted?: IGettingStartedContent[]; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index ab2a5236895..3dfc15e444a 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -458,7 +458,7 @@ export class FileService extends Disposable implements IFileService { try { // if the etag is provided, we await the result of the validation - // due to the likelyhood of hitting a NOT_MODIFIED_SINCE result. + // due to the likelihood of hitting a NOT_MODIFIED_SINCE result. // otherwise, we let it run in parallel to the file reading for // optimal startup performance. if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED) { diff --git a/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts b/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts index f28a15dca5c..f11db7ea10a 100644 --- a/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts +++ b/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nativeKeymap from 'native-keymap'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IKeyboardLayoutData, IKeyboardLayoutMainService as ICommonKeyboardLayoutMainService } from 'vs/platform/keyboardLayout/common/keyboardLayoutMainService'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import * as nativeKeymap from 'native-keymap'; +import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; export const IKeyboardLayoutMainService = createDecorator('keyboardLayoutMainService'); @@ -23,10 +24,17 @@ export class KeyboardLayoutMainService extends Disposable implements ICommonKeyb private _initPromise: Promise | null; private _keyboardLayoutData: IKeyboardLayoutData | null; - constructor() { + constructor( + @ILifecycleMainService lifecycleMainService: ILifecycleMainService + ) { super(); this._initPromise = null; this._keyboardLayoutData = null; + + // perf: automatically trigger initialize after windows + // have opened so that we can do this work in parallel + // to the window load. + lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this._initialize()); } private _initialize(): Promise { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index cd39b86801e..0262b0e4a05 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -1183,7 +1183,7 @@ configurationRegistry.registerConfiguration({ 'description': localize({ key: 'openModeModifier', comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.'] - }, "Controls how to open items in trees and lists using the mouse (if supported). For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable.") + }, "Controls how to open items in trees and lists using the mouse (if supported). Note that some trees and lists might choose to ignore this setting if it is not applicable.") }, [horizontalScrollingKey]: { 'type': 'boolean', @@ -1227,7 +1227,7 @@ configurationRegistry.registerConfiguration({ [treeExpandMode]: { type: 'string', enum: ['singleClick', 'doubleClick'], - default: 'doubleClick', + default: 'singleClick', description: localize('expand mode', "Controls how tree folders are expanded when clicking the folder names. Note that some trees and lists might choose to ignore this setting if it is not applicable."), } } diff --git a/src/vs/platform/log/browser/log.ts b/src/vs/platform/log/browser/log.ts index 8b1d44c4b91..8fe0f14599d 100644 --- a/src/vs/platform/log/browser/log.ts +++ b/src/vs/platform/log/browser/log.ts @@ -9,6 +9,18 @@ interface IAutomatedWindow { codeAutomationLog(type: string, args: any[]): void; } +function logLevelToString(level: LogLevel): string { + switch (level) { + case LogLevel.Trace: return 'trace'; + case LogLevel.Debug: return 'debug'; + case LogLevel.Info: return 'info'; + case LogLevel.Warning: return 'warn'; + case LogLevel.Error: return 'error'; + case LogLevel.Critical: return 'critical'; + } + return 'info'; +} + /** * A logger that is used when VSCode is running in the web with * an automation such as playwright. We expect a global codeAutomationLog @@ -19,7 +31,7 @@ export class ConsoleLogInAutomationLogger extends AdapterLogger implements ILogg declare codeAutomationLog: any; constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) { - super({ log: (type, args) => this.consoleLog(type, args) }, logLevel); + super({ log: (level, args) => this.consoleLog(logLevelToString(level), args) }, logLevel); } private consoleLog(type: string, args: any[]): void { diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 524381951d1..8a2764bc6c4 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -314,44 +314,44 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { export class AdapterLogger extends AbstractLogger implements ILogger { - constructor(private readonly adapter: { log: (type: string, args: any[]) => void }, logLevel: LogLevel = DEFAULT_LOG_LEVEL) { + constructor(private readonly adapter: { log: (logLevel: LogLevel, args: any[]) => void }, logLevel: LogLevel = DEFAULT_LOG_LEVEL) { super(); this.setLevel(logLevel); } trace(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Trace) { - this.adapter.log('trace', [this.extractMessage(message), ...args]); + this.adapter.log(LogLevel.Trace, [this.extractMessage(message), ...args]); } } debug(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Debug) { - this.adapter.log('debug', [this.extractMessage(message), ...args]); + this.adapter.log(LogLevel.Debug, [this.extractMessage(message), ...args]); } } info(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Info) { - this.adapter.log('info', [this.extractMessage(message), ...args]); + this.adapter.log(LogLevel.Info, [this.extractMessage(message), ...args]); } } warn(message: string | Error, ...args: any[]): void { if (this.getLevel() <= LogLevel.Warning) { - this.adapter.log('warn', [this.extractMessage(message), ...args]); + this.adapter.log(LogLevel.Warning, [this.extractMessage(message), ...args]); } } error(message: string | Error, ...args: any[]): void { if (this.getLevel() <= LogLevel.Error) { - this.adapter.log('error', [this.extractMessage(message), ...args]); + this.adapter.log(LogLevel.Error, [this.extractMessage(message), ...args]); } } critical(message: string | Error, ...args: any[]): void { if (this.getLevel() <= LogLevel.Critical) { - this.adapter.log('critical', [this.extractMessage(message), ...args]); + this.adapter.log(LogLevel.Critical, [this.extractMessage(message), ...args]); } } diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 4c18165d9b4..4b8ac5fab13 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { LogLevel, ILogService, LogService, ILoggerService, ILogger, AbstractMessageLogger, ILoggerOptions } from 'vs/platform/log/common/log'; +import { LogLevel, ILogService, LogService, ILoggerService, ILogger, AbstractMessageLogger, ILoggerOptions, AdapterLogger } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; @@ -34,72 +34,6 @@ export class LogLevelChannel implements IServerChannel { } -export class LoggerChannel implements IServerChannel { - - private consoleLogger: ILogger | undefined; - private readonly loggers = new Map(); - - constructor(private readonly loggerService: ILoggerService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - async call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'createConsoleLogger': return this.createConsoleLogger(); - case 'createLogger': this.createLogger(URI.revive(arg[0]), arg[1]); return; - case 'log': return this.log(URI.revive(arg[0]), arg[1]); - } - - throw new Error(`Call not found: ${command}`); - } - - private createConsoleLogger(): void { - this.consoleLogger = new class extends AbstractMessageLogger { - protected log(level: LogLevel, message: string) { - let consoleFn = console.log; - - switch (level) { - case LogLevel.Error: - consoleFn = console.error; - break; - case LogLevel.Warning: - consoleFn = console.warn; - break; - case LogLevel.Info: - consoleFn = console.info; - break; - } - - consoleFn.call(console, message); - } - }(); - } - - private createLogger(file: URI, options: ILoggerOptions): void { - this.loggers.set(file.toString(), this.loggerService.createLogger(file, options)); - } - - private log(file: URI | undefined, messages: [LogLevel, string][]): void { - const logger = file ? this.loggers.get(file.toString()) : this.consoleLogger; - if (!logger) { - throw new Error('Create the logger before logging'); - } - for (const [level, message] of messages) { - switch (level) { - case LogLevel.Trace: logger.trace(message); break; - case LogLevel.Debug: logger.debug(message); break; - case LogLevel.Info: logger.info(message); break; - case LogLevel.Warning: logger.warn(message); break; - case LogLevel.Error: logger.error(message); break; - case LogLevel.Critical: logger.critical(message); break; - default: throw new Error('Invalid log level'); - } - } - } -} - export class LogLevelChannelClient { constructor(private channel: IChannel) { } @@ -118,6 +52,118 @@ export class LogLevelChannelClient { } +export class LoggerChannel implements IServerChannel { + + private readonly loggers = new Map(); + + constructor(private readonly loggerService: ILoggerService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + async call(_: unknown, command: string, arg?: any): Promise { + switch (command) { + case 'createLogger': this.createLogger(URI.revive(arg[0]), arg[1]); return; + case 'log': return this.log(URI.revive(arg[0]), arg[1]); + case 'consoleLog': return this.consoleLog(arg[0], arg[1]); + } + + throw new Error(`Call not found: ${command}`); + } + + private createLogger(file: URI, options: ILoggerOptions): void { + this.loggers.set(file.toString(), this.loggerService.createLogger(file, options)); + } + + private consoleLog(level: LogLevel, args: any[]): void { + let consoleFn = console.log; + + switch (level) { + case LogLevel.Error: + consoleFn = console.error; + break; + case LogLevel.Warning: + consoleFn = console.warn; + break; + case LogLevel.Info: + consoleFn = console.info; + break; + } + + consoleFn.call(console, ...args); + } + + private log(file: URI, messages: [LogLevel, string][]): void { + const logger = this.loggers.get(file.toString()); + if (!logger) { + throw new Error('Create the logger before logging'); + } + for (const [level, message] of messages) { + switch (level) { + case LogLevel.Trace: logger.trace(message); break; + case LogLevel.Debug: logger.debug(message); break; + case LogLevel.Info: logger.info(message); break; + case LogLevel.Warning: logger.warn(message); break; + case LogLevel.Error: logger.error(message); break; + case LogLevel.Critical: logger.critical(message); break; + default: throw new Error('Invalid log level'); + } + } + } +} + +export class LoggerChannelClient implements ILoggerService { + + declare readonly _serviceBrand: undefined; + + constructor(private readonly channel: IChannel) { } + + createConsoleMainLogger(): ILogger { + return new AdapterLogger({ + log: (level: LogLevel, args: any[]) => { + this.channel.call('consoleLog', [level, args]); + } + }); + } + + createLogger(file: URI, options?: ILoggerOptions): ILogger { + return new Logger(this.channel, file, options); + } + +} + +class Logger extends AbstractMessageLogger { + + private isLoggerCreated: boolean = false; + private buffer: [LogLevel, string][] = []; + + constructor( + private readonly channel: IChannel, + private readonly file: URI, + loggerOptions?: ILoggerOptions, + ) { + super(loggerOptions?.always); + this.channel.call('createLogger', [file, loggerOptions]) + .then(() => { + this._log(this.buffer); + this.isLoggerCreated = true; + }); + } + + protected log(level: LogLevel, message: string) { + this._log([[level, message]]); + } + + private _log(messages: [LogLevel, string][]) { + if (this.isLoggerCreated) { + this.channel.call('log', [this.file, messages]); + } else { + this.buffer.push(...messages); + } + } +} + export class FollowerLogService extends LogService implements ILogService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index ba9d54e54fd..989cf596f6f 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -658,7 +658,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain private static readonly PASSWORD_CHUNK_SIZE = NativeHostMainService.MAX_PASSWORD_LENGTH - 100; async getPassword(windowId: number | undefined, service: string, account: string): Promise { - const keytar = await import('keytar'); + const keytar = await this.withKeytar(); const password = await keytar.getPassword(service, account); if (password) { @@ -686,7 +686,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async setPassword(windowId: number | undefined, service: string, account: string, password: string): Promise { - const keytar = await import('keytar'); + const keytar = await this.withKeytar(); if (isWindows && password.length > NativeHostMainService.MAX_PASSWORD_LENGTH) { let index = 0; @@ -714,7 +714,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async deletePassword(windowId: number | undefined, service: string, account: string): Promise { - const keytar = await import('keytar'); + const keytar = await this.withKeytar(); const didDelete = await keytar.deletePassword(service, account); if (didDelete) { @@ -725,17 +725,25 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async findPassword(windowId: number | undefined, service: string): Promise { - const keytar = await import('keytar'); + const keytar = await this.withKeytar(); return keytar.findPassword(service); } async findCredentials(windowId: number | undefined, service: string): Promise> { - const keytar = await import('keytar'); + const keytar = await this.withKeytar(); return keytar.findCredentials(service); } + private async withKeytar(): Promise { + if (this.environmentMainService.disableKeytar) { + throw new Error('keytar has been disabled via --disable-keytar option'); + } + + return await import('keytar'); + } + //#endregion private windowById(windowId: number | undefined): ICodeWindow | undefined { diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 3f6ace923a6..06f3213babb 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -23,7 +23,6 @@ export interface IBuiltInExtension { } export type ConfigurationSyncStore = { - web?: Partial>, url: string, insidersUrl: string, stableUrl: string, diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 966a8830bf6..8c40cb0f9c3 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron'; +import { BrowserWindow, ipcMain, Event as ElectronEvent, MessagePortMain } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; @@ -15,13 +15,18 @@ import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedP import { Disposable } from 'vs/base/common/lifecycle'; import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { assertIsDefined } from 'vs/base/common/types'; +import { Emitter, Event } from 'vs/base/common/event'; +import { WindowError } from 'vs/platform/windows/electron-main/windows'; export class SharedProcess extends Disposable implements ISharedProcess { private readonly whenSpawnedBarrier = new Barrier(); private window: BrowserWindow | undefined = undefined; - private windowCloseListener: ((event: Event) => void) | undefined = undefined; + private windowCloseListener: ((event: ElectronEvent) => void) | undefined = undefined; + + private readonly _onDidError = this._register(new Emitter<{ type: WindowError, message: string }>()); + readonly onDidError = Event.buffer(this._onDidError.event); // buffer until we have a listener! constructor( private readonly machineId: string, @@ -186,7 +191,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } // Prevent the window from closing - this.windowCloseListener = (e: Event) => { + this.windowCloseListener = (e: ElectronEvent) => { this.logService.trace('SharedProcess#close prevented'); // We never allow to close the shared process unless we get explicitly disposed() @@ -201,9 +206,11 @@ export class SharedProcess extends Disposable implements ISharedProcess { this.window.on('close', this.windowCloseListener); // Crashes & Unrsponsive & Failed to load - this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`SharedProcess: crashed (detail: ${details?.reason})`)); - this.window.on('unresponsive', () => this.logService.error('SharedProcess: detected unresponsive window')); - this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('SharedProcess: failed to load window, ', errorDescription)); + // We use `onUnexpectedError` explicitly because the error handler + // will send the error to the active window to log in devtools too + this.window.webContents.on('render-process-gone', (event, details) => this._onDidError.fire({ type: WindowError.CRASHED, message: `SharedProcess: crashed (detail: ${details?.reason})` })); + this.window.on('unresponsive', () => this._onDidError.fire({ type: WindowError.UNRESPONSIVE, message: 'SharedProcess: detected unresponsive window' })); + this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this._onDidError.fire({ type: WindowError.LOAD, message: `SharedProcess: failed to load window: ${errorDescription}` })); } spawn(userEnv: NodeJS.ProcessEnv): void { diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index aa2eb5f6c7d..5cfb899602e 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { StorageScope, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -12,11 +12,13 @@ import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { IStorage, Storage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { runWhenIdle, RunOnceScheduler, Promises } from 'vs/base/common/async'; +import { Promises } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; export class BrowserStorageService extends AbstractStorageService { + private static BROWSER_DEFAULT_FLUSH_INTERVAL = 5 * 1000; // every 5s because async operations are not permitted on shutdown + private globalStorage: IStorage | undefined; private workspaceStorage: IStorage | undefined; @@ -26,38 +28,26 @@ export class BrowserStorageService extends AbstractStorageService { private globalStorageFile: URI | undefined; private workspaceStorageFile: URI | undefined; - private initializePromise: Promise | undefined; - - private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */)); - private runWhenIdleDisposable: IDisposable | undefined = undefined; - get hasPendingUpdate(): boolean { return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); } constructor( + private readonly payload: IWorkspaceInitializationPayload, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService ) { - super(); + super({ flushInterval: BrowserStorageService.BROWSER_DEFAULT_FLUSH_INTERVAL }); } - initialize(payload: IWorkspaceInitializationPayload): Promise { - if (!this.initializePromise) { - this.initializePromise = this.doInitialize(payload); - } - - return this.initializePromise; - } - - private async doInitialize(payload: IWorkspaceInitializationPayload): Promise { + protected async doInitialize(): Promise { // Ensure state folder exists const stateRoot = joinPath(this.environmentService.userRoamingDataHome, 'state'); await this.fileService.createFolder(stateRoot); // Workspace Storage - this.workspaceStorageFile = joinPath(stateRoot, `${payload.id}.json`); + this.workspaceStorageFile = joinPath(stateRoot, `${this.payload.id}.json`); this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, false /* do not watch for external changes */, this.fileService)); this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase)); @@ -90,13 +80,6 @@ export class BrowserStorageService extends AbstractStorageService { } else if (firstWorkspaceOpen) { this.workspaceStorage.set(IS_NEW_KEY, false); } - - // In the browser we do not have support for long running unload sequences. As such, - // we cannot ask for saving state in that moment, because that would result in a - // long running operation. - // Instead, periodically ask customers to save save. The library will be clever enough - // to only save state that has actually changed. - this.periodicFlushScheduler.schedule(); } protected getStorage(scope: StorageScope): IStorage | undefined { @@ -111,30 +94,17 @@ export class BrowserStorageService extends AbstractStorageService { throw new Error('Migrating storage is currently unsupported in Web'); } - private doFlushWhenIdle(): void { - - // Dispose any previous idle runner - dispose(this.runWhenIdleDisposable); - - // Run when idle - this.runWhenIdleDisposable = runWhenIdle(() => { - - // this event will potentially cause new state to be stored - // since new state will only be created while the document - // has focus, one optimization is to not run this when the - // document has no focus, assuming that state has not changed - // - // another optimization is to not collect more state if we - // have a pending update already running which indicates - // that the connection is either slow or disconnected and - // thus unhealthy. - if (document.hasFocus() && !this.hasPendingUpdate) { - this.flush(); - } - - // repeat - this.periodicFlushScheduler.schedule(); - }); + protected shouldFlushWhenIdle(): boolean { + // this flush() will potentially cause new state to be stored + // since new state will only be created while the document + // has focus, one optimization is to not run this when the + // document has no focus, assuming that state has not changed + // + // another optimization is to not collect more state if we + // have a pending update already running which indicates + // that the connection is either slow or disconnected and + // thus unhealthy. + return document.hasFocus() && !this.hasPendingUpdate; } close(): void { @@ -148,13 +118,6 @@ export class BrowserStorageService extends AbstractStorageService { // get triggered in this phase. this.dispose(); } - - dispose(): void { - dispose(this.runWhenIdleDisposable); - this.runWhenIdleDisposable = undefined; - - super.dispose(); - } } export class FileStorageDatabase extends Disposable implements IStorageDatabase { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index aad6f035908..faecb2618ea 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -5,11 +5,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter, PauseableEmitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { InMemoryStorageDatabase, IStorage, Storage } from 'vs/base/parts/storage/common/storage'; -import { Promises } from 'vs/base/common/async'; +import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; export const IS_NEW_KEY = '__$__isNewStorageMarker'; const TARGET_KEY = '__$__targetStorageMarker'; @@ -220,10 +220,16 @@ interface IKeyTargets { [key: string]: StorageTarget } +export interface IStorageServiceOptions { + flushInterval: number; +} + export abstract class AbstractStorageService extends Disposable implements IStorageService { declare readonly _serviceBrand: undefined; + private static DEFAULT_FLUSH_INTERVAL = 60 * 1000; // every minute + private readonly _onDidChangeValue = this._register(new PauseableEmitter()); readonly onDidChangeValue = this._onDidChangeValue.event; @@ -233,6 +239,56 @@ export abstract class AbstractStorageService extends Disposable implements IStor private readonly _onWillSaveState = this._register(new Emitter()); readonly onWillSaveState = this._onWillSaveState.event; + private initializationPromise: Promise | undefined; + + private readonly flushWhenIdleScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), this.options.flushInterval)); + private readonly runFlushWhenIdle = this._register(new MutableDisposable()); + + constructor(private options: IStorageServiceOptions = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) { + super(); + } + + private doFlushWhenIdle(): void { + this.runFlushWhenIdle.value = runWhenIdle(() => { + if (this.shouldFlushWhenIdle()) { + this.flush(); + } + + // repeat + this.flushWhenIdleScheduler.schedule(); + }); + } + + protected shouldFlushWhenIdle(): boolean { + return true; + } + + protected stopFlushWhenIdle(): void { + dispose([this.runFlushWhenIdle, this.flushWhenIdleScheduler]); + } + + initialize(): Promise { + if (!this.initializationPromise) { + this.initializationPromise = (async () => { + + // Ask subclasses to initialize storage + await this.doInitialize(); + + // On some OS we do not get enough time to persist state on shutdown (e.g. when + // Windows restarts after applying updates). In other cases, VSCode might crash, + // so we periodically save state to reduce the chance of loosing any state. + // In the browser we do not have support for long running unload sequences. As such, + // we cannot ask for saving state in that moment, because that would result in a + // long running operation. + // Instead, periodically ask customers to save save. The library will be clever enough + // to only save state that has actually changed. + this.flushWhenIdleScheduler.schedule(); + })(); + } + + return this.initializationPromise; + } + protected emitDidChangeValue(scope: StorageScope, key: string): void { // Specially handle `TARGET_KEY` @@ -424,6 +480,8 @@ export abstract class AbstractStorageService extends Disposable implements IStor // --- abstract + protected abstract doInitialize(): Promise; + protected abstract getStorage(scope: StorageScope): IStorage | undefined; protected abstract getLogDetails(scope: StorageScope): string | undefined; @@ -451,6 +509,8 @@ export class InMemoryStorageService extends AbstractStorageService { return scope === StorageScope.GLOBAL ? 'inMemory (global)' : 'inMemory (workspace)'; } + protected async doInitialize(): Promise { } + async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { // not supported } diff --git a/src/vs/platform/storage/common/storageIpc.ts b/src/vs/platform/storage/common/storageIpc.ts index fe88c0815c7..a95938a3ae5 100644 --- a/src/vs/platform/storage/common/storageIpc.ts +++ b/src/vs/platform/storage/common/storageIpc.ts @@ -29,9 +29,9 @@ export interface ISerializableItemsChangeEvent { abstract class BaseStorageDatabaseClient extends Disposable implements IStorageDatabase { - abstract onDidChangeItemsExternal: Event; + abstract readonly onDidChangeItemsExternal: Event; - constructor(protected channel: IChannel, private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined) { + constructor(protected channel: IChannel, protected workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined) { super(); } @@ -56,16 +56,7 @@ abstract class BaseStorageDatabaseClient extends Disposable implements IStorageD return this.channel.call('updateItems', serializableRequest); } - async close(): Promise { - - // The database connection is not owned by us, but rather on the - // main side, as such we do not forward the close() request but - // let main side handle this properly via lifecycle methods. - // - // However, we cleanup our listeners because we are no longer - // interested in change events from the global database - this.dispose(); - } + abstract close(): Promise; } class GlobalStorageDatabaseClient extends BaseStorageDatabaseClient implements IStorageDatabase { @@ -91,6 +82,14 @@ class GlobalStorageDatabaseClient extends BaseStorageDatabaseClient implements I }); } } + + async close(): Promise { + + // The global storage database is shared across all instances so + // we do not await it. However we dispose the listener for external + // changes because we no longer interested int it. + this.dispose(); + } } class WorkspaceStorageDatabaseClient extends BaseStorageDatabaseClient implements IStorageDatabase { @@ -100,12 +99,33 @@ class WorkspaceStorageDatabaseClient extends BaseStorageDatabaseClient implement constructor(channel: IChannel, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier) { super(channel, workspace); } + + async close(): Promise { + const serializableRequest: ISerializableUpdateRequest = { workspace: this.workspace }; + + return this.channel.call('close', serializableRequest); + } } export class StorageDatabaseChannelClient extends Disposable { - readonly globalStorage = new GlobalStorageDatabaseClient(this.channel); - readonly workspaceStorage = this.workspace ? new WorkspaceStorageDatabaseClient(this.channel, this.workspace) : undefined; + private _globalStorage: GlobalStorageDatabaseClient | undefined = undefined; + get globalStorage() { + if (!this._globalStorage) { + this._globalStorage = new GlobalStorageDatabaseClient(this.channel); + } + + return this._globalStorage; + } + + private _workspaceStorage: WorkspaceStorageDatabaseClient | undefined = undefined; + get workspaceStorage() { + if (!this._workspaceStorage && this.workspace) { + this._workspaceStorage = new WorkspaceStorageDatabaseClient(this.channel, this.workspace); + } + + return this._workspaceStorage; + } constructor( private channel: IChannel, diff --git a/src/vs/platform/storage/electron-main/storageIpc.ts b/src/vs/platform/storage/electron-main/storageIpc.ts index 2fb542e34cf..f8a6224c5c7 100644 --- a/src/vs/platform/storage/electron-main/storageIpc.ts +++ b/src/vs/platform/storage/electron-main/storageIpc.ts @@ -91,14 +91,27 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel case 'updateItems': { const items: ISerializableUpdateRequest = arg; + if (items.insert) { for (const [key, value] of items.insert) { - storage.store(key, value); + storage.set(key, value); } } if (items.delete) { - items.delete.forEach(key => storage.remove(key)); + items.delete.forEach(key => storage.delete(key)); + } + + break; + } + + case 'close': { + + // We only allow to close workspace scoped storage because + // global storage is shared across all windows and closes + // only on shutdown. + if (workspace) { + return storage.close(); } break; @@ -113,7 +126,7 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel const storage = workspace ? this.storageMainService.workspaceStorage(workspace) : this.storageMainService.globalStorage; try { - await storage.initialize(); + await storage.init(); } catch (error) { this.logService.error(`StorageIPC#init: Unable to init ${workspace ? 'workspace' : 'global'} storage due to ${error}`); } diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index 9d1f5c1b6ce..c9a430dccef 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -50,7 +50,7 @@ export interface IStorageMain extends IDisposable { /** * Required call to ensure the service can be used. */ - initialize(): Promise; + init(): Promise; /** * Retrieve an element stored with the given key from storage. Use @@ -59,32 +59,16 @@ export interface IStorageMain extends IDisposable { get(key: string, fallbackValue: string): string; get(key: string, fallbackValue?: string): string | undefined; - /** - * Retrieve an element stored with the given key from storage. Use - * the provided defaultValue if the element is null or undefined. The element - * will be converted to a boolean. - */ - getBoolean(key: string, fallbackValue: boolean): boolean; - getBoolean(key: string, fallbackValue?: boolean): boolean | undefined; - - /** - * Retrieve an element stored with the given key from storage. Use - * the provided defaultValue if the element is null or undefined. The element - * will be converted to a number using parseInt with a base of 10. - */ - getNumber(key: string, fallbackValue: number): number; - getNumber(key: string, fallbackValue?: number): number | undefined; - /** * Store a string value under the given key to storage. The value will * be converted to a string. */ - store(key: string, value: string | boolean | number | undefined | null): void; + set(key: string, value: string | boolean | number | undefined | null): void; /** * Delete an element stored under the provided key from storage. */ - remove(key: string): void; + delete(key: string): void; /** * Close the storage connection. @@ -114,17 +98,26 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { super(); } - initialize(): Promise { + init(): Promise { if (!this.initializePromise) { this.initializePromise = (async () => { try { - const storage = await this.doInitialize(); - // Replace our in-memory storage with the initialized - // one once that is finished and use it from then on + // Create storage via subclasses + const storage = await this.doCreate(); + + // Replace our in-memory storage with the real + // once as soon as possible without awaiting + // the init call. this.storage.dispose(); this.storage = storage; + // Re-emit storage changes via event + this._register(storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key }))); + + // Await storage init + await this.doInit(storage); + // Ensure we track wether storage is new or not const isNewStorage = storage.getBoolean(IS_NEW_KEY); if (isNewStorage === undefined) { @@ -148,7 +141,11 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { }; } - protected abstract doInitialize(): Promise; + protected doInit(storage: IStorage): Promise { + return storage.init(); + } + + protected abstract doCreate(): Promise; get items(): Map { return this.storage.items; } @@ -158,28 +155,24 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { return this.storage.get(key, fallbackValue); } - getBoolean(key: string, fallbackValue: boolean): boolean; - getBoolean(key: string, fallbackValue?: boolean): boolean | undefined; - getBoolean(key: string, fallbackValue?: boolean): boolean | undefined { - return this.storage.getBoolean(key, fallbackValue); - } - - getNumber(key: string, fallbackValue: number): number; - getNumber(key: string, fallbackValue?: number): number | undefined; - getNumber(key: string, fallbackValue?: number): number | undefined { - return this.storage.getNumber(key, fallbackValue); - } - - store(key: string, value: string | boolean | number | undefined | null): Promise { + set(key: string, value: string | boolean | number | undefined | null): Promise { return this.storage.set(key, value); } - remove(key: string): Promise { + delete(key: string): Promise { return this.storage.delete(key); } async close(): Promise { + // Ensure we are not accidentally leaving + // a pending initialized storage behind in + // case close() was called before init() + // finishes + if (this.initializePromise) { + await this.initializePromise; + } + // Propagate to storage lib await this.storage.close(); @@ -200,7 +193,7 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { super(logService); } - protected async doInitialize(): Promise { + protected async doCreate(): Promise { let storagePath: string; if (this.options.useInMemoryStorage) { storagePath = SQLiteStorageDatabase.IN_MEMORY_PATH; @@ -208,24 +201,19 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { storagePath = join(this.environmentService.globalStorageHome.fsPath, GlobalStorageMain.STORAGE_NAME); } - // Create Storage - const storage = new Storage(new SQLiteStorageDatabase(storagePath, { + return new Storage(new SQLiteStorageDatabase(storagePath, { logging: this.createLogginOptions() })); + } - // Re-emit storage changes via event - this._register(storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key }))); - - // Forward init to SQLite DB - await storage.init(); + protected async doInit(storage: IStorage): Promise { + await super.doInit(storage); // Apply global telemetry values as part of the initialization this.updateTelemetryState(storage); - - return storage; } - private updateTelemetryState(storage: Storage): void { + private updateTelemetryState(storage: IStorage): void { // Instance UUID (once) const instanceId = storage.get(instanceStorageKey, undefined); @@ -263,23 +251,12 @@ export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMai super(logService); } - protected async doInitialize(): Promise { - - // Prepare workspace storage folder for DB + protected async doCreate(): Promise { const { storageFilePath, wasCreated } = await this.prepareWorkspaceStorageFolder(); - // Create Storage - const storage = new Storage(new SQLiteStorageDatabase(storageFilePath, { + return new Storage(new SQLiteStorageDatabase(storageFilePath, { logging: this.createLogginOptions() }), { hint: wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined }); - - // Re-emit storage changes via event - this._register(storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key }))); - - // Forward init to SQLite DB - await storage.init(); - - return storage; } private async prepareWorkspaceStorageFolder(): Promise<{ storageFilePath: string, wasCreated: boolean }> { @@ -298,15 +275,16 @@ export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMai return { storageFilePath: workspaceStorageDatabasePath, wasCreated: false }; } + // Ensure storage folder exists await promises.mkdir(workspaceStorageFolderPath, { recursive: true }); - // Write metadata into folder + // Write metadata into folder (but do not await) this.ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath); return { storageFilePath: workspaceStorageDatabasePath, wasCreated: true }; } - private ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath: string): void { + private async ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath: string): Promise { let meta: object | undefined = undefined; if (isSingleFolderWorkspaceIdentifier(this.workspace)) { meta = { folder: this.workspace.uri.toString() }; @@ -315,17 +293,15 @@ export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMai } if (meta) { - (async () => { - try { - const workspaceStorageMetaPath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_META_NAME); - const storageExists = await exists(workspaceStorageMetaPath); - if (!storageExists) { - await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); - } - } catch (error) { - this.logService.error(`StorageMain#ensureWorkspaceStorageFolderMeta(): Unable to create workspace storage metadata due to ${error}`); + try { + const workspaceStorageMetaPath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_META_NAME); + const storageExists = await exists(workspaceStorageMetaPath); + if (!storageExists) { + await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); } - })(); + } catch (error) { + this.logService.error(`StorageMain#ensureWorkspaceStorageFolderMeta(): Unable to create workspace storage metadata due to ${error}`); + } } } } diff --git a/src/vs/platform/storage/electron-main/storageMainService.ts b/src/vs/platform/storage/electron-main/storageMainService.ts index a82e8efd0a8..842bee97c24 100644 --- a/src/vs/platform/storage/electron-main/storageMainService.ts +++ b/src/vs/platform/storage/electron-main/storageMainService.ts @@ -62,14 +62,14 @@ export class StorageMainService extends Disposable implements IStorageMainServic (async () => { await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); - this.globalStorage.initialize(); + this.globalStorage.init(); })(); // Workspace Storage: Warmup when related window with workspace loads if (this.enableMainWorkspaceStorage()) { this._register(this.lifecycleMainService.onWillLoadWindow(async e => { if (e.workspace) { - this.workspaceStorage(e.workspace).initialize(); + this.workspaceStorage(e.workspace).init(); } })); } diff --git a/src/vs/platform/storage/electron-sandbox/storageService2.ts b/src/vs/platform/storage/electron-sandbox/storageService2.ts index c9bbecee81c..f8d6e9ae7c3 100644 --- a/src/vs/platform/storage/electron-sandbox/storageService2.ts +++ b/src/vs/platform/storage/electron-sandbox/storageService2.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; import { StorageScope, WillSaveStateReason, AbstractStorageService } from 'vs/platform/storage/common/storage'; import { Storage, IStorage } from 'vs/base/parts/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; -import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { Promises } from 'vs/base/common/async'; import { mark } from 'vs/base/common/performance'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { StorageDatabaseChannelClient } from 'vs/platform/storage/common/storageIpc'; @@ -16,43 +16,57 @@ import { joinPath } from 'vs/base/common/resources'; export class NativeStorageService2 extends AbstractStorageService { + // Global Storage is readonly and shared across windows private readonly globalStorage: IStorage; - private readonly workspaceStorage: IStorage | undefined; - private initializePromise: Promise | undefined; - - private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 60 * 1000 /* every minute */)); - private runWhenIdleDisposable: IDisposable | undefined = undefined; + // Workspace Storage is scoped to a window but can change + // in the current window, when entering a workspace! + private workspaceStorage: IStorage | undefined = undefined; + private workspaceStorageId: string | undefined = undefined; + private workspaceStorageDisposable = this._register(new MutableDisposable()); constructor( - private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined, - mainProcessService: IMainProcessService, + workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined, + private readonly mainProcessService: IMainProcessService, private readonly environmentService: IEnvironmentService ) { super(); - // Connect to storage via channel client - const storageDataBaseClient = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), workspace); - this.globalStorage = new Storage(storageDataBaseClient.globalStorage); - this.workspaceStorage = storageDataBaseClient.workspaceStorage ? new Storage(storageDataBaseClient.workspaceStorage) : undefined; - - this.registerListeners(); + this.globalStorage = this.createGlobalStorage(); + this.workspaceStorage = this.createWorkspaceStorage(workspace); } - private registerListeners(): void { - this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key))); - this._register(this.workspaceStorage?.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)) ?? Disposable.None); + private createGlobalStorage(): IStorage { + const storageDataBaseClient = new StorageDatabaseChannelClient(this.mainProcessService.getChannel('storage'), undefined); + + const globalStorage = new Storage(storageDataBaseClient.globalStorage); + + this._register(globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key))); + + return globalStorage; } - initialize(): Promise { - if (!this.initializePromise) { - this.initializePromise = this.doInitialize(); + private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorage; + private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): IStorage | undefined; + private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): IStorage | undefined { + const storageDataBaseClient = new StorageDatabaseChannelClient(this.mainProcessService.getChannel('storage'), workspace); + + if (storageDataBaseClient.workspaceStorage) { + const workspaceStorage = new Storage(storageDataBaseClient.workspaceStorage); + + this.workspaceStorageDisposable.value = workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)); + this.workspaceStorageId = workspace?.id; + + return workspaceStorage; + } else { + this.workspaceStorageDisposable.clear(); + this.workspaceStorageId = undefined; + + return undefined; } - - return this.initializePromise; } - private async doInitialize(): Promise { + protected async doInitialize(): Promise { // Init all storage locations mark('code/willInitStorage'); @@ -64,11 +78,6 @@ export class NativeStorageService2 extends AbstractStorageService { } finally { mark('code/didInitStorage'); } - - // On some OS we do not get enough time to persist state on shutdown (e.g. when - // Windows restarts after applying updates). In other cases, VSCode might crash, - // so we periodically save state to reduce the chance of loosing any state. - this.periodicFlushScheduler.schedule(); } protected getStorage(scope: StorageScope): IStorage | undefined { @@ -76,31 +85,13 @@ export class NativeStorageService2 extends AbstractStorageService { } protected getLogDetails(scope: StorageScope): string | undefined { - return scope === StorageScope.GLOBAL ? this.environmentService.globalStorageHome.fsPath : this.workspace ? `${joinPath(this.environmentService.workspaceStorageHome, this.workspace.id, 'state.vscdb').fsPath} [!!! Experimental Main Storage !!!]` : undefined; - } - - private doFlushWhenIdle(): void { - - // Dispose any previous idle runner - dispose(this.runWhenIdleDisposable); - - // Run when idle - this.runWhenIdleDisposable = runWhenIdle(() => { - - // send event to collect state - this.flush(); - - // repeat - this.periodicFlushScheduler.schedule(); - }); + return scope === StorageScope.GLOBAL ? this.environmentService.globalStorageHome.fsPath : this.workspaceStorageId ? `${joinPath(this.environmentService.workspaceStorageHome, this.workspaceStorageId, 'state.vscdb').fsPath} [!!! Experimental Main Storage !!!]` : undefined; } async close(): Promise { // Stop periodic scheduler and idle runner as we now collect state normally - this.periodicFlushScheduler.dispose(); - dispose(this.runWhenIdleDisposable); - this.runWhenIdleDisposable = undefined; + this.stopFlushWhenIdle(); // Signal as event so that clients can still store data this.emitWillSaveState(WillSaveStateReason.SHUTDOWN); @@ -113,22 +104,24 @@ export class NativeStorageService2 extends AbstractStorageService { } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { - // if (this.workspaceStoragePath === SQLiteStorageDatabase.IN_MEMORY_PATH) { - // return; // no migration needed if running in memory - // } - // // Close workspace DB to be able to copy - // await this.getStorage(StorageScope.WORKSPACE).close(); + // Keep current workspace storage items around to restore + const oldWorkspaceStorage = this.workspaceStorage; + const oldItems = oldWorkspaceStorage?.items ?? new Map(); - // // Prepare new workspace storage folder - // const result = await this.prepareWorkspaceStorageFolder(toWorkspace); + // Close current which will change to new workspace storage + if (oldWorkspaceStorage) { + await oldWorkspaceStorage.close(); + oldWorkspaceStorage.dispose(); + } - // const newWorkspaceStoragePath = join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME); + // Create new workspace storage & init + this.workspaceStorage = this.createWorkspaceStorage(toWorkspace); + await this.workspaceStorage.init(); - // // Copy current storage over to new workspace storage - // await copy(assertIsDefined(this.workspaceStoragePath), newWorkspaceStoragePath, { preserveSymlinks: false }); - - // // Recreate and init workspace storage - // return this.createWorkspaceStorage(newWorkspaceStoragePath).init(); + // Copy over previous keys + for (const [key, value] of oldItems) { + this.workspaceStorage.set(key, value); + } } } diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 316263b1b52..3b33b0ba27b 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -15,7 +15,7 @@ import { copy, exists, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { assertIsDefined } from 'vs/base/common/types'; -import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { Promises } from 'vs/base/common/async'; export class NativeStorageService extends AbstractStorageService { @@ -28,13 +28,9 @@ export class NativeStorageService extends AbstractStorageService { private workspaceStorage: IStorage | undefined; private workspaceStorageListener: IDisposable | undefined; - private initializePromise: Promise | undefined; - - private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 60000 /* every minute */)); - private runWhenIdleDisposable: IDisposable | undefined = undefined; - constructor( private globalStorageDatabase: IStorageDatabase, + private payload: IWorkspaceInitializationPayload | undefined, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService ) { @@ -49,26 +45,13 @@ export class NativeStorageService extends AbstractStorageService { this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key))); } - initialize(payload?: IWorkspaceInitializationPayload): Promise { - if (!this.initializePromise) { - this.initializePromise = this.doInitialize(payload); - } - - return this.initializePromise; - } - - private async doInitialize(payload?: IWorkspaceInitializationPayload): Promise { + protected async doInitialize(): Promise { // Init all storage locations await Promises.settled([ this.initializeGlobalStorage(), - payload ? this.initializeWorkspaceStorage(payload) : Promise.resolve() + this.payload ? this.initializeWorkspaceStorage(this.payload) : Promise.resolve() ]); - - // On some OS we do not get enough time to persist state on shutdown (e.g. when - // Windows restarts after applying updates). In other cases, VSCode might crash, - // so we periodically save state to reduce the chance of loosing any state. - this.periodicFlushScheduler.schedule(); } private initializeGlobalStorage(): Promise { @@ -178,28 +161,10 @@ export class NativeStorageService extends AbstractStorageService { return scope === StorageScope.GLOBAL ? this.environmentService.globalStorageHome.fsPath : this.workspaceStoragePath; } - private doFlushWhenIdle(): void { - - // Dispose any previous idle runner - dispose(this.runWhenIdleDisposable); - - // Run when idle - this.runWhenIdleDisposable = runWhenIdle(() => { - - // send event to collect state - this.flush(); - - // repeat - this.periodicFlushScheduler.schedule(); - }); - } - async close(): Promise { // Stop periodic scheduler and idle runner as we now collect state normally - this.periodicFlushScheduler.dispose(); - dispose(this.runWhenIdleDisposable); - this.runWhenIdleDisposable = undefined; + this.stopFlushWhenIdle(); // Signal as event so that clients can still store data this.emitWillSaveState(WillSaveStateReason.SHUTDOWN); diff --git a/src/vs/platform/storage/test/browser/storageService.test.ts b/src/vs/platform/storage/test/browser/storageService.test.ts index fdeb01f61c6..f3e92ff0880 100644 --- a/src/vs/platform/storage/test/browser/storageService.test.ts +++ b/src/vs/platform/storage/test/browser/storageService.test.ts @@ -28,9 +28,9 @@ suite('StorageService (browser)', function () { const userDataProvider = disposables.add(new InMemoryFileSystemProvider()); disposables.add(fileService.registerProvider(Schemas.userData, userDataProvider)); - const storageService = disposables.add(new BrowserStorageService({ userRoamingDataHome: URI.file('/User').with({ scheme: Schemas.userData }) } as unknown as IEnvironmentService, fileService)); + const storageService = disposables.add(new BrowserStorageService({ id: String(Date.now()) }, { userRoamingDataHome: URI.file('/User').with({ scheme: Schemas.userData }) } as unknown as IEnvironmentService, fileService)); - await storageService.initialize({ id: String(Date.now()) }); + await storageService.initialize(); return storageService; }, diff --git a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts index 4ed240e033b..2a91d25b5d9 100644 --- a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts +++ b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts @@ -16,10 +16,10 @@ import { ILifecycleMainService, LifecycleMainPhase, ShutdownEvent, UnloadReason import { Emitter, Event } from 'vs/base/common/event'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { Promises, timeout } from 'vs/base/common/async'; +import { Promises } from 'vs/base/common/async'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -suite('StorageMainService (native)', function () { +suite('StorageMainService', function () { class TestStorageMainService extends StorageMainService { @@ -81,12 +81,12 @@ suite('StorageMainService (native)', function () { if (isGlobal) { strictEqual(storage.items.size, 0); strictEqual(storage.get(instanceStorageKey), undefined); - await storage.initialize(); + await storage.init(); strictEqual(typeof storage.get(instanceStorageKey), 'string'); strictEqual(typeof storage.get(firstSessionDateStorageKey), 'string'); strictEqual(typeof storage.get(currentSessionDateStorageKey), 'string'); } else { - await storage.initialize(); + await storage.init(); } let storageChangeEvent: IStorageChangeEvent | undefined = undefined; @@ -100,24 +100,24 @@ suite('StorageMainService (native)', function () { // Basic store/get/remove const size = storage.items.size; - storage.store('bar', 'foo'); + storage.set('bar', 'foo'); strictEqual(storageChangeEvent!.key, 'bar'); - storage.store('barNumber', 55); - storage.store('barBoolean', true); + storage.set('barNumber', 55); + storage.set('barBoolean', true); strictEqual(storage.get('bar'), 'foo'); - strictEqual(storage.getNumber('barNumber'), 55); - strictEqual(storage.getBoolean('barBoolean'), true); + strictEqual(storage.get('barNumber'), '55'); + strictEqual(storage.get('barBoolean'), 'true'); strictEqual(storage.items.size, size + 3); - storage.remove('bar'); + storage.delete('bar'); strictEqual(storage.get('bar'), undefined); strictEqual(storage.items.size, size + 2); // IS_NEW - strictEqual(storage.getBoolean(IS_NEW_KEY), true); + strictEqual(storage.get(IS_NEW_KEY), 'true'); // Close await storage.close(); @@ -160,9 +160,9 @@ suite('StorageMainService (native)', function () { strictEqual(workspaceStorage, storageMainService.workspaceStorage(workspace)); // same instance as long as not closed - await workspaceStorage.initialize(); + await globalStorage.init(); + await workspaceStorage.init(); - await timeout(0); await lifecycleMainService.fireOnWillShutdown(); strictEqual(didCloseGlobalStorage, true); @@ -173,4 +173,53 @@ suite('StorageMainService (native)', function () { return storage2.close(); }); + + test('storage closed before init works', async function () { + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService(), new TestConfigurationService()); + const workspace = { id: generateUuid() }; + + let workspaceStorage = storageMainService.workspaceStorage(workspace); + let didCloseWorkspaceStorage = false; + workspaceStorage.onDidCloseStorage(() => { + didCloseWorkspaceStorage = true; + }); + + let globalStorage = storageMainService.globalStorage; + let didCloseGlobalStorage = false; + globalStorage.onDidCloseStorage(() => { + didCloseGlobalStorage = true; + }); + + await globalStorage.close(); + await workspaceStorage.close(); + + strictEqual(didCloseGlobalStorage, true); + strictEqual(didCloseWorkspaceStorage, true); + }); + + test('storage closed before init awaits works', async function () { + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService(), new TestConfigurationService()); + const workspace = { id: generateUuid() }; + + let workspaceStorage = storageMainService.workspaceStorage(workspace); + let didCloseWorkspaceStorage = false; + workspaceStorage.onDidCloseStorage(() => { + didCloseWorkspaceStorage = true; + }); + + let globalStorage = storageMainService.globalStorage; + let didCloseGlobalStorage = false; + globalStorage.onDidCloseStorage(() => { + didCloseGlobalStorage = true; + }); + + globalStorage.init(); + workspaceStorage.init(); + + await globalStorage.close(); + await workspaceStorage.close(); + + strictEqual(didCloseGlobalStorage, true); + strictEqual(didCloseWorkspaceStorage, true); + }); }); diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index ce8eb321aac..68ba513e051 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -42,8 +42,8 @@ flakySuite('StorageService (native)', function () { await promises.mkdir(testDir, { recursive: true }); - const storageService = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir)); - await storageService.initialize({ id: String(Date.now()) }); + const storageService = new NativeStorageService(new InMemoryStorageDatabase(), { id: String(Date.now()) }, new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir)); + await storageService.initialize(); return storageService; }, @@ -55,8 +55,8 @@ flakySuite('StorageService (native)', function () { }); test('Migrate Data', async function () { - const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir)); - await storage.initialize({ id: String(Date.now()) }); + const storage = new NativeStorageService(new InMemoryStorageDatabase(), { id: String(Date.now()) }, new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir)); + await storage.initialize(); storage.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE); storage.store('barNumber', 55, StorageScope.WORKSPACE, StorageTarget.MACHINE); diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts new file mode 100644 index 00000000000..3f54a3daf49 --- /dev/null +++ b/src/vs/platform/terminal/common/terminal.ts @@ -0,0 +1,291 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; + +export enum TerminalIpcChannels { + /** + * Communicates between the renderer process and shared process. + */ + LocalPty = 'localPty', + /** + * Communicates between the shared process and the pty host process. + */ + PtyHost = 'ptyHost', + /** + * Deals with logging from the pty host process. + */ + Log = 'log', + /** + * Enables the detection of unresponsive pty hosts. + */ + Heartbeat = 'heartbeat' +} + +export interface IPtyService { + readonly _serviceBrand: undefined; + + readonly onPtyHostExit?: Event; + readonly onPtyHostStart?: Event; + readonly onPtyHostUnresponsive?: Event; + + readonly onProcessData: Event<{ id: number, event: IProcessDataEvent | string }>; + readonly onProcessExit: Event<{ id: number, event: number | undefined }>; + readonly onProcessReady: Event<{ id: number, event: { pid: number, cwd: string } }>; + readonly onProcessTitleChanged: Event<{ id: number, event: string }>; + readonly onProcessOverrideDimensions: Event<{ id: number, event: ITerminalDimensionsOverride | undefined }>; + readonly onProcessResolvedShellLaunchConfig: Event<{ id: number, event: IShellLaunchConfig }>; + + createProcess( + shellLaunchConfig: IShellLaunchConfig, + cwd: string, + cols: number, + rows: number, + env: IProcessEnvironment, + executableEnv: IProcessEnvironment, + windowsEnableConpty: boolean + ): Promise; + + shutdownAll?(): Promise; + + start(id: number): Promise; + + shutdown(id: number, immediate: boolean): Promise; + + input(id: number, data: string): Promise; + + resize(id: number, cols: number, rows: number): Promise; + + acknowledgeDataEvent(id: number, charCount: number): Promise; + + getInitialCwd(id: number): Promise; + + getCwd(id: number): Promise; + + getLatency(id: number): Promise; + + restartPtyHost?(): Promise; +} + +export enum HeartbeatConstants { + /** + * The duration between heartbeats + */ + BeatInterval = 5000, + /** + * Defines a multiplier for BeatInterval for how long to wait before starting the second wait + * timer. + */ + FirstWaitMultiplier = 1.2, + /** + * Defines a multiplier for BeatInterval for how long to wait before telling the user about + * non-responsiveness. The second timer is to avoid informing the user incorrectly when waking + * the computer up from sleep + */ + SecondWaitMultiplier = 1, + /** + * How long to wait before telling the user about non-responsiveness when they try to create a + * process. This short circuits the standard wait timeouts to tell the user sooner and only + * create process is handled to avoid additional perf overhead. + */ + CreateProcessTimeout = 2000 +} + +export interface IHeartbeatService { + readonly onBeat: Event; +} + +export interface IShellLaunchConfig { + /** + * The name of the terminal, if this is not set the name of the process will be used. + */ + name?: string; + + /** + * The shell executable (bash, cmd, etc.). + */ + executable?: string; + + /** + * The CLI arguments to use with executable, a string[] is in argv format and will be escaped, + * a string is in "CommandLine" pre-escaped format and will be used as is. The string option is + * only supported on Windows and will throw an exception if used on macOS or Linux. + */ + args?: string[] | string; + + /** + * The current working directory of the terminal, this overrides the `terminal.integrated.cwd` + * settings key. + */ + cwd?: string | URI; + + /** + * A custom environment for the terminal, if this is not set the environment will be inherited + * from the VS Code process. + */ + env?: ITerminalEnvironment; + + /** + * Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (e.g. if the + * shell is being launched by an extension). + */ + ignoreConfigurationCwd?: boolean; + + /** Whether to wait for a key press before closing the terminal. */ + waitOnExit?: boolean | string; + + /** + * A string including ANSI escape sequences that will be written to the terminal emulator + * _before_ the terminal process has launched, a trailing \n is added at the end of the string. + * This allows for example the terminal instance to display a styled message as the first line + * of the terminal. Use \x1b over \033 or \e for the escape control character. + */ + initialText?: string; + + /** + * Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`. + */ + isExtensionTerminal?: boolean; + + /** + * A UUID generated by the extension host process for terminals created on the extension host process. + */ + extHostTerminalId?: string; + + /** + * This is a terminal that attaches to an already running remote terminal. + */ + remoteAttach?: { id: number; pid: number; title: string; cwd: string; }; + + /** + * Whether the terminal process environment should be exactly as provided in + * `TerminalOptions.env`. When this is false (default), the environment will be based on the + * window's environment and also apply configured platform settings like + * `terminal.integrated.windows.env` on top. When this is true, the complete environment must be + * provided as nothing will be inherited from the process or any configuration. + */ + strictEnv?: boolean; + + /** + * When enabled the terminal will run the process as normal but not be surfaced to the user + * until `Terminal.show` is called. The typical usage for this is when you need to run + * something that may need interactivity but only want to tell the user about it when + * interaction is needed. Note that the terminals will still be exposed to all extensions + * as normal. + */ + hideFromUser?: boolean; + + /** + * Whether this terminal is not a terminal that the user directly created and uses, but rather + * a terminal used to drive some VS Code feature. + */ + isFeatureTerminal?: boolean; + + /** + * Whether flow control is enabled for this terminal. + */ + flowControl?: boolean; +} + +export interface ITerminalEnvironment { + [key: string]: string | null; +} + +export interface ITerminalLaunchError { + message: string; + code?: number; +} + +/** + * An interface representing a raw terminal child process, this contains a subset of the + * child_process.ChildProcess node.js interface. + */ +export interface ITerminalChildProcess { + onProcessData: Event; + onProcessExit: Event; + onProcessReady: Event<{ pid: number, cwd: string }>; + onProcessTitleChanged: Event; + onProcessOverrideDimensions?: Event; + onProcessResolvedShellLaunchConfig?: Event; + + /** + * Starts the process. + * + * @returns undefined when the process was successfully started, otherwise an object containing + * information on what went wrong. + */ + start(): Promise; + + /** + * Shutdown the terminal process. + * + * @param immediate When true the process will be killed immediately, otherwise the process will + * be given some time to make sure no additional data comes through. + */ + shutdown(immediate: boolean): void; + input(data: string): void; + resize(cols: number, rows: number): void; + + /** + * Acknowledge a data event has been parsed by the terminal, this is used to implement flow + * control to ensure remote processes to not get too far ahead of the client and flood the + * connection. + * @param charCount The number of characters being acknowledged. + */ + acknowledgeDataEvent(charCount: number): void; + + getInitialCwd(): Promise; + getCwd(): Promise; + getLatency(): Promise; +} + +export const enum FlowControlConstants { + /** + * The number of _unacknowledged_ chars to have been sent before the pty is paused in order for + * the client to catch up. + */ + HighWatermarkChars = 100000, + /** + * After flow control pauses the pty for the client the catch up, this is the number of + * _unacknowledged_ chars to have been caught up to on the client before resuming the pty again. + * This is used to attempt to prevent pauses in the flowing data; ideally while the pty is + * paused the number of unacknowledged chars would always be greater than 0 or the client will + * appear to stutter. In reality this balance is hard to accomplish though so heavy commands + * will likely pause as latency grows, not flooding the connection is the important thing as + * it's shared with other core functionality. + */ + LowWatermarkChars = 5000, + /** + * The number characters that are accumulated on the client side before sending an ack event. + * This must be less than or equal to LowWatermarkChars or the terminal max never unpause. + */ + CharCountAckSize = 5000 +} + +export interface IProcessDataEvent { + data: string; + sync: boolean; +} + +export interface ITerminalDimensions { + /** + * The columns of the terminal. + */ + readonly cols: number; + + /** + * The rows of the terminal. + */ + readonly rows: number; +} + +export interface ITerminalDimensionsOverride extends ITerminalDimensions { + /** + * indicate that xterm must receive these exact dimensions, even if they overflow the ui! + */ + forceExactSize?: boolean; +} diff --git a/src/vs/platform/terminal/electron-browser/localPtyService.ts b/src/vs/platform/terminal/electron-browser/localPtyService.ts new file mode 100644 index 00000000000..b7b9282058d --- /dev/null +++ b/src/vs/platform/terminal/electron-browser/localPtyService.ts @@ -0,0 +1,196 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants } from 'vs/platform/terminal/common/terminal'; +import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; +import { FileAccess } from 'vs/base/common/network'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { Emitter } from 'vs/base/common/event'; +import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; + +enum Constants { + MaxRestarts = 5 +} + +export class LocalPtyService extends Disposable implements IPtyService { + declare readonly _serviceBrand: undefined; + + private _client: Client; + // ProxyChannel is not used here because events get lost when forwarding across multiple proxies + private _proxy: IPtyService; + + private _restartCount = 0; + private _isDisposed = false; + + private _heartbeatFirstTimeout?: NodeJS.Timeout; + private _heartbeatSecondTimeout?: NodeJS.Timeout; + + private readonly _onPtyHostExit = this._register(new Emitter()); + readonly onPtyHostExit = this._onPtyHostExit.event; + private readonly _onPtyHostStart = this._register(new Emitter()); + readonly onPtyHostStart = this._onPtyHostStart.event; + private readonly _onPtyHostUnresponsive = this._register(new Emitter()); + readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event; + + private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>()); + readonly onProcessData = this._onProcessData.event; + private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>()); + readonly onProcessExit = this._onProcessExit.event; + private readonly _onProcessReady = this._register(new Emitter<{ id: number, event: { pid: number, cwd: string } }>()); + readonly onProcessReady = this._onProcessReady.event; + private readonly _onProcessTitleChanged = this._register(new Emitter<{ id: number, event: string }>()); + readonly onProcessTitleChanged = this._onProcessTitleChanged.event; + private readonly _onProcessOverrideDimensions = this._register(new Emitter<{ id: number, event: ITerminalDimensionsOverride | undefined }>()); + readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<{ id: number, event: IShellLaunchConfig }>()); + readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + + [this._client, this._proxy] = this._startPtyHost(); + } + + private _startPtyHost(): [Client, IPtyService] { + const client = this._register(new Client( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + { + serverName: 'Pty Host', + args: ['--type=ptyHost'], + env: { + VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client + } + } + )); + this._onPtyHostStart.fire(); + + const heartbeatService = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.Heartbeat)); + heartbeatService.onBeat(() => this._handleHeartbeat()); + + // Handle exit + this._register({ + dispose: () => { + this._disposePtyHost(); + } + }); + this._register(client.onDidProcessExit(e => { + this._onPtyHostExit.fire(e.code); + if (!this._isDisposed) { + if (this._restartCount <= Constants.MaxRestarts) { + this._logService.error(`ptyHost terminated unexpectedly with code ${e.code}`); + this._restartCount++; + this.restartPtyHost(); + } else { + this._logService.error(`ptyHost terminated unexpectedly with code ${e.code}, giving up`); + } + } + })); + + // Setup logging + const logChannel = client.getChannel(TerminalIpcChannels.Log); + this._register(this._logService.onDidChangeLogLevel(() => { + LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel()); + })); + + // Create proxy and forward events + const proxy = ProxyChannel.toService(client.getChannel(TerminalIpcChannels.PtyHost)); + this._register(proxy.onProcessData(e => this._onProcessData.fire(e))); + this._register(proxy.onProcessExit(e => this._onProcessExit.fire(e))); + this._register(proxy.onProcessReady(e => this._onProcessReady.fire(e))); + this._register(proxy.onProcessTitleChanged(e => this._onProcessTitleChanged.fire(e))); + this._register(proxy.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e))); + this._register(proxy.onProcessResolvedShellLaunchConfig(e => this._onProcessResolvedShellLaunchConfig.fire(e))); + return [client, proxy]; + } + + dispose() { + this._isDisposed = true; + super.dispose(); + } + + async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean): Promise { + const timeout = setTimeout(() => this._handleUnresponsiveCreateProcess(), HeartbeatConstants.CreateProcessTimeout); + const result = await this._proxy.createProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty); + clearTimeout(timeout); + return result; + } + start(id: number): Promise { + return this._proxy.start(id); + } + shutdown(id: number, immediate: boolean): Promise { + return this._proxy.shutdown(id, immediate); + } + input(id: number, data: string): Promise { + return this._proxy.input(id, data); + } + resize(id: number, cols: number, rows: number): Promise { + return this._proxy.resize(id, cols, rows); + } + acknowledgeDataEvent(id: number, charCount: number): Promise { + return this._proxy.acknowledgeDataEvent(id, charCount); + } + getInitialCwd(id: number): Promise { + return this._proxy.getInitialCwd(id); + } + getCwd(id: number): Promise { + return this._proxy.getCwd(id); + } + getLatency(id: number): Promise { + return this._proxy.getLatency(id); + } + + async restartPtyHost(): Promise { + this._disposePtyHost(); + [this._client, this._proxy] = this._startPtyHost(); + } + + private _disposePtyHost(): void { + if (this._proxy.shutdownAll) { + this._proxy.shutdownAll(); + } + this._client.dispose(); + } + + private _handleHeartbeat() { + this._clearHeartbeatTimeouts(); + this._heartbeatFirstTimeout = setTimeout(() => this._handleHeartbeatFirstTimeout(), HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier); + } + + private _handleHeartbeatFirstTimeout() { + this._logService.warn(`No ptyHost heartbeat after ${HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier}ms`); + this._heartbeatFirstTimeout = undefined; + this._heartbeatSecondTimeout = setTimeout(() => this._handleHeartbeatSecondTimeout(), HeartbeatConstants.BeatInterval * HeartbeatConstants.SecondWaitMultiplier); + } + + private _handleHeartbeatSecondTimeout() { + this._logService.error(`No ptyHost heartbeat after ${HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier}ms!`); + this._heartbeatSecondTimeout = undefined; + this._onPtyHostUnresponsive.fire(); + } + + private _handleUnresponsiveCreateProcess() { + this._clearHeartbeatTimeouts(); + this._logService.error(`No ptyHost response to createProcess after ${HeartbeatConstants.CreateProcessTimeout}ms`); + this._onPtyHostUnresponsive.fire(); + } + + private _clearHeartbeatTimeouts() { + if (this._heartbeatFirstTimeout) { + clearTimeout(this._heartbeatFirstTimeout); + this._heartbeatFirstTimeout = undefined; + } + if (this._heartbeatSecondTimeout) { + clearTimeout(this._heartbeatSecondTimeout); + this._heartbeatSecondTimeout = undefined; + } + } +} diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/src/vs/platform/terminal/electron-sandbox/terminal.ts similarity index 54% rename from extensions/vscode-notebook-tests/src/notebookTestMain.ts rename to src/vs/platform/terminal/electron-sandbox/terminal.ts index 8cf19b430be..1f1a3100648 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/src/vs/platform/terminal/electron-sandbox/terminal.ts @@ -3,12 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { smokeTestActivate } from './notebookSmokeTestMain'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IPtyService } from 'vs/platform/terminal/common/terminal'; -export function activate(context: vscode.ExtensionContext): any { - smokeTestActivate(context); +export const ILocalPtyService = createDecorator('localPtyService'); - - -} +export interface ILocalPtyService extends IPtyService { } diff --git a/src/vs/platform/terminal/node/heartbeatService.ts b/src/vs/platform/terminal/node/heartbeatService.ts new file mode 100644 index 00000000000..3a414958afd --- /dev/null +++ b/src/vs/platform/terminal/node/heartbeatService.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { HeartbeatConstants, IHeartbeatService } from 'vs/platform/terminal/common/terminal'; + +export class HeartbeatService extends Disposable implements IHeartbeatService { + private readonly _onBeat = this._register(new Emitter()); + readonly onBeat = this._onBeat.event; + + constructor() { + super(); + + const interval = setInterval(() => { + this._onBeat.fire(); + }, HeartbeatConstants.BeatInterval); + this._register(toDisposable(() => clearInterval(interval))); + } +} diff --git a/src/vs/platform/terminal/node/ptyHostMain.ts b/src/vs/platform/terminal/node/ptyHostMain.ts new file mode 100644 index 00000000000..9515ddc130a --- /dev/null +++ b/src/vs/platform/terminal/node/ptyHostMain.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { PtyService } from 'vs/platform/terminal/node/ptyService'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { ConsoleLogger, LogService } from 'vs/platform/log/common/log'; +import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; +import { HeartbeatService } from 'vs/platform/terminal/node/heartbeatService'; + +const server = new Server('ptyHost'); + +const logService = new LogService(new ConsoleLogger()); +const logChannel = new LogLevelChannel(logService); +server.registerChannel(TerminalIpcChannels.Log, logChannel); + +const heartbeatService = new HeartbeatService(); +server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService)); + +const ptyService = new PtyService(logService); +server.registerChannel(TerminalIpcChannels.PtyHost, ProxyChannel.fromService(ptyService)); + +process.once('exit', () => { + logService.dispose(); + heartbeatService.dispose(); + ptyService.dispose(); +}); diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts new file mode 100644 index 00000000000..1c260f3f56b --- /dev/null +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; +import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess'; +import { Emitter } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; + +// TODO: On disconnect/restart, this will overwrite the older terminals +let currentPtyId = 0; + +export class PtyService extends Disposable implements IPtyService { + declare readonly _serviceBrand: undefined; + + private readonly _ptys: Map = new Map(); + + private readonly _onHeartbeat = this._register(new Emitter()); + readonly onHeartbeat = this._onHeartbeat.event; + + private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>()); + readonly onProcessData = this._onProcessData.event; + private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>()); + readonly onProcessExit = this._onProcessExit.event; + private readonly _onProcessReady = this._register(new Emitter<{ id: number, event: { pid: number, cwd: string } }>()); + readonly onProcessReady = this._onProcessReady.event; + private readonly _onProcessTitleChanged = this._register(new Emitter<{ id: number, event: string }>()); + readonly onProcessTitleChanged = this._onProcessTitleChanged.event; + private readonly _onProcessOverrideDimensions = this._register(new Emitter<{ id: number, event: ITerminalDimensionsOverride | undefined }>()); + readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<{ id: number, event: IShellLaunchConfig }>()); + readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; + + constructor( + private readonly _logService: ILogService + ) { + super(); + } + + dispose() { + for (const pty of this._ptys.values()) { + pty.shutdown(true); + } + this._ptys.clear(); + } + + async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, executableEnv: IProcessEnvironment, windowsEnableConpty: boolean): Promise { + const id = ++currentPtyId; + const process = new TerminalProcess(shellLaunchConfig, cwd, cols, rows, env, executableEnv, windowsEnableConpty, this._logService); + process.onProcessData(event => this._onProcessData.fire({ id, event })); + process.onProcessExit(event => this._onProcessExit.fire({ id, event })); + process.onProcessReady(event => this._onProcessReady.fire({ id, event })); + process.onProcessTitleChanged(event => this._onProcessTitleChanged.fire({ id, event })); + if (process.onProcessOverrideDimensions) { + process.onProcessOverrideDimensions(event => this._onProcessOverrideDimensions.fire({ id, event })); + } + if (process.onProcessResolvedShellLaunchConfig) { + process.onProcessResolvedShellLaunchConfig(event => this._onProcessResolvedShellLaunchConfig.fire({ id, event })); + } + this._ptys.set(id, process); + return id; + } + + async shutdownAll(): Promise { + this.dispose(); + } + + async start(id: number): Promise { + return this._throwIfNoPty(id).start(); + } + + async shutdown(id: number, immediate: boolean): Promise { + return this._throwIfNoPty(id).shutdown(immediate); + } + + async input(id: number, data: string): Promise { + return this._throwIfNoPty(id).input(data); + } + + async resize(id: number, cols: number, rows: number): Promise { + return this._throwIfNoPty(id).resize(cols, rows); + } + + async acknowledgeDataEvent(id: number, charCount: number): Promise { + return this._throwIfNoPty(id).acknowledgeDataEvent(charCount); + } + + async getInitialCwd(id: number): Promise { + return this._throwIfNoPty(id).getInitialCwd(); + } + + async getCwd(id: number): Promise { + return this._throwIfNoPty(id).getCwd(); + } + + async getLatency(id: number): Promise { + return this._throwIfNoPty(id).getLatency(); + } + + private _throwIfNoPty(id: number): ITerminalChildProcess { + const pty = this._ptys.get(id); + if (!pty) { + throw new Error(`Could not find pty with id "${id}"`); + } + return pty; + } +} diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts new file mode 100644 index 00000000000..d5f83c85257 --- /dev/null +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as os from 'os'; +import * as path from 'vs/base/common/path'; +import { exists } from 'vs/base/node/pfs'; +import { isString } from 'vs/base/common/types'; +import { getCaseInsensitive } from 'vs/base/common/objects'; +import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; + +export function getWindowsBuildNumber(): number { + const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); + let buildNumber: number = 0; + if (osVersion && osVersion.length === 4) { + buildNumber = parseInt(osVersion[3]); + } + return buildNumber; +} + +export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment): Promise { + // If we have an absolute path then we take it. + if (path.isAbsolute(command)) { + return await exists(command) ? command : undefined; + } + if (cwd === undefined) { + cwd = process.cwd(); + } + const dir = path.dirname(command); + if (dir !== '.') { + // We have a directory and the directory is relative (see above). Make the path absolute + // to the current working directory. + const fullPath = path.join(cwd, command); + return await exists(fullPath) ? fullPath : undefined; + } + const envPath = getCaseInsensitive(env, 'PATH'); + if (paths === undefined && isString(envPath)) { + paths = envPath.split(path.delimiter); + } + // No PATH environment. Make path absolute to the cwd. + if (paths === undefined || paths.length === 0) { + const fullPath = path.join(cwd, command); + return await exists(fullPath) ? fullPath : undefined; + } + // We have a simple file name. We get the path variable from the env + // and try to find the executable on the path. + for (let pathEntry of paths) { + // The path entry is absolute. + let fullPath: string; + if (path.isAbsolute(pathEntry)) { + fullPath = path.join(pathEntry, command); + } else { + fullPath = path.join(cwd, pathEntry, command); + } + + if (await exists(fullPath)) { + return fullPath; + } + if (isWindows) { + let withExtension = fullPath + '.com'; + if (await exists(withExtension)) { + return withExtension; + } + withExtension = fullPath + '.exe'; + if (await exists(withExtension)) { + return withExtension; + } + } + } + const fullPath = path.join(cwd, command); + return await exists(fullPath) ? fullPath : undefined; +} diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts similarity index 97% rename from src/vs/workbench/contrib/terminal/node/terminalProcess.ts rename to src/vs/platform/terminal/node/terminalProcess.ts index 59ba95e9397..4f6727ce502 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -9,12 +9,11 @@ import type * as pty from 'node-pty'; import * as fs from 'fs'; import * as os from 'os'; import { Event, Emitter } from 'vs/base/common/event'; -import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, FlowControlConstants } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalLaunchError, FlowControlConstants, ITerminalChildProcess, ITerminalDimensionsOverride } from 'vs/platform/terminal/common/terminal'; import { exec } from 'child_process'; import { ILogService } from 'vs/platform/log/common/log'; -import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; +import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index b8be4d3be94..0124ffeab7d 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -34,7 +34,7 @@ type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; -function isSyncData(thing: any): thing is ISyncData { +export function isSyncData(thing: any): thing is ISyncData { if (thing && (thing.version !== undefined && typeof thing.version === 'number') && (thing.content !== undefined && typeof thing.content === 'string')) { diff --git a/src/vs/platform/userDataSync/common/globalStateMerge.ts b/src/vs/platform/userDataSync/common/globalStateMerge.ts index e519854a95b..cea1c3d6d9f 100644 --- a/src/vs/platform/userDataSync/common/globalStateMerge.ts +++ b/src/vs/platform/userDataSync/common/globalStateMerge.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { IStorageValue } from 'vs/platform/userDataSync/common/userDataSync'; +import { IStorageValue, SYNC_SERVICE_URL_TYPE } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; import { ILogService } from 'vs/platform/log/common/log'; @@ -30,15 +30,17 @@ export function merge(localStorage: IStringDictionary, remoteStor const local: { added: IStringDictionary, removed: string[], updated: IStringDictionary } = { added: {}, removed: [], updated: {} }; const remote: IStringDictionary = objects.deepClone(remoteStorage); + const isFirstTimeSync = !baseStorage; + // Added in local for (const key of baseToLocal.added.values()) { - // Skip if local was not synced before and remote also has the key - // In this case, remote gets precedence - if (!baseStorage && baseToRemote.added.has(key)) { + // If syncing for first time remote value gets precedence always, + // except for sync service type key - local value takes precedence for this key + if (key !== SYNC_SERVICE_URL_TYPE && isFirstTimeSync && baseToRemote.added.has(key)) { continue; - } else { - remote[key] = localStorage[key]; } + + remote[key] = localStorage[key]; } // Updated in local @@ -70,6 +72,12 @@ export function merge(localStorage: IStringDictionary, remoteStor if (localValue && localValue.value === remoteValue.value) { continue; } + + // Local sync service type value takes precedence if syncing for first time + if (key === SYNC_SERVICE_URL_TYPE && isFirstTimeSync && baseToLocal.added.has(key)) { + continue; + } + if (localValue) { local.updated[key] = remoteValue; } else { diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 7c69ad4635a..2e58f17ae95 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -5,7 +5,7 @@ import { IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncResourceEnablementService, - IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, USER_DATA_SYNC_SCHEME, IRemoteUserData, Change + IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, USER_DATA_SYNC_SCHEME, IRemoteUserData, Change, ALL_SYNC_RESOURCES, getEnablementKey, SYNC_SERVICE_URL_TYPE, UserDataSyncStoreType, IUserData, ISyncData, createSyncHeaders, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; @@ -15,7 +15,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { parse } from 'vs/base/common/json'; -import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, isSyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -23,6 +23,12 @@ import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { isWeb } from 'vs/base/common/platform'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IHeaders } from 'vs/base/parts/request/common/request'; +import { ILogService } from 'vs/platform/log/common/log'; const argvStoragePrefx = 'globalState.argv.'; const argvProperties: string[] = ['locale']; @@ -40,6 +46,18 @@ export interface IGlobalStateResourcePreview extends IResourcePreview { readonly storageKeys: StorageKeys; } +function formatAndStringify(globalState: IGlobalState): string { + const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : []; + const storage: IStringDictionary = {}; + storageKeys.forEach(key => storage[key] = globalState.storage[key]); + globalState.storage = storage; + const content = JSON.stringify(globalState); + const edits = format(content, undefined, {}); + return applyEdits(content, edits); +} + +const GLOBAL_STATE_DATA_VERSION = 1; + /** * Synchronises global state that includes * - Global storage with user scope @@ -52,7 +70,7 @@ export interface IGlobalStateResourcePreview extends IResourcePreview { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` }); - protected readonly version: number = 1; + protected readonly version: number = GLOBAL_STATE_DATA_VERSION; private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'globalState.json'); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); @@ -107,10 +125,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return [{ localResource: this.localResource, - localContent: this.format(localGloablState), + localContent: formatAndStringify(localGloablState), localUserData: localGloablState, remoteResource: this.remoteResource, - remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, + remoteContent: remoteGlobalState ? formatAndStringify(remoteGlobalState) : null, previewResource: this.previewResource, previewResult, localChange: previewResult.localChange, @@ -215,7 +233,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) { const localGlobalState = await this.getLocalGlobalState(); - return this.format(localGlobalState); + return formatAndStringify(localGlobalState); } if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { @@ -233,7 +251,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (syncData) { switch (this.extUri.basename(uri)) { case 'globalState.json': - return this.format(JSON.parse(syncData.content)); + return formatAndStringify(JSON.parse(syncData.content)); } } } @@ -241,16 +259,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return null; } - private format(globalState: IGlobalState): string { - const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : []; - const storage: IStringDictionary = {}; - storageKeys.forEach(key => storage[key] = globalState.storage[key]); - globalState.storage = storage; - const content = JSON.stringify(globalState); - const edits = format(content, undefined, {}); - return applyEdits(content, edits); - } - async hasLocalData(): Promise { try { const { storage } = await this.getLocalGlobalState(); @@ -346,6 +354,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const machine = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE); const registered = [...user, ...machine]; const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && this.storageService.get(key, StorageScope.GLOBAL) !== undefined) : []; + + if (!isWeb) { + // Following keys are synced only in web. Do not sync these keys in other platforms + const keysSyncedOnlyInWeb = [...ALL_SYNC_RESOURCES.map(resource => getEnablementKey(resource)), SYNC_SERVICE_URL_TYPE]; + unregistered.push(...keysSyncedOnlyInWeb); + machine.push(...keysSyncedOnlyInWeb); + } + return { user, machine, unregistered }; } } @@ -401,3 +417,62 @@ export class GlobalStateInitializer extends AbstractInitializer { } +export class UserDataSyncStoreTypeSynchronizer { + + constructor( + private readonly userDataSyncStoreClient: UserDataSyncStoreClient, + @IStorageService private readonly storageService: IStorageService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + getSyncStoreType(userData: IUserData): UserDataSyncStoreType | undefined { + const remoteGlobalState = this.parseGlobalState(userData); + return remoteGlobalState?.storage[SYNC_SERVICE_URL_TYPE]?.value as UserDataSyncStoreType; + } + + async sync(userDataSyncStoreType: UserDataSyncStoreType): Promise { + const syncHeaders = createSyncHeaders(generateUuid()); + try { + return await this.doSync(userDataSyncStoreType, syncHeaders); + } catch (e) { + if (e instanceof UserDataSyncError) { + switch (e.code) { + case UserDataSyncErrorCode.PreconditionFailed: + this.logService.info(`Failed to synchronize UserDataSyncStoreType as there is a new remote version available. Synchronizing again...`); + return this.doSync(userDataSyncStoreType, syncHeaders); + } + } + throw e; + } + } + + private async doSync(userDataSyncStoreType: UserDataSyncStoreType, syncHeaders: IHeaders): Promise { + // Read the global state from remote + const globalStateUserData = await this.userDataSyncStoreClient.read(SyncResource.GlobalState, null, syncHeaders); + const remoteGlobalState = this.parseGlobalState(globalStateUserData) || { storage: {} }; + + // Update the sync store type + remoteGlobalState.storage[SYNC_SERVICE_URL_TYPE] = { value: userDataSyncStoreType, version: GLOBAL_STATE_DATA_VERSION }; + + // Write the global state to remote + const machineId = await getServiceMachineId(this.environmentService, this.fileService, this.storageService); + const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: formatAndStringify(remoteGlobalState) }; + await this.userDataSyncStoreClient.write(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, syncHeaders); + } + + private parseGlobalState({ content }: IUserData): IGlobalState | null { + if (!content) { + return null; + } + const syncData = JSON.parse(content); + if (isSyncData(syncData)) { + return syncData ? JSON.parse(syncData.content) : null; + } + throw new Error('Invalid remote data'); + } + +} + diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 0a4a2fded18..0d258c8e48c 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -18,6 +18,8 @@ import { localize } from 'vs/nls'; import { toLocalISOString } from 'vs/base/common/date'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; +import { isWeb } from 'vs/base/common/platform'; +import { IProductService } from 'vs/platform/product/common/productService'; type AutoSyncClassification = { sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -36,6 +38,7 @@ const enablementKey = 'sync.enable'; const disableMachineEventuallyKey = 'sync.disableMachineEventually'; const sessionIdKey = 'sync.sessionId'; const storeUrlKey = 'sync.storeUrl'; +const productQualityKey = 'sync.productQuality'; interface _IUserDataAutoSyncEnablementService extends IUserDataAutoSyncEnablementService { canToggleEnablement(): boolean; @@ -73,9 +76,10 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU } setEnablement(enabled: boolean): void { - if (this.canToggleEnablement()) { - this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE); + if (enabled && !this.canToggleEnablement()) { + return; } + this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE); } private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void { @@ -118,7 +122,20 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } } + private previousProductQuality: string | undefined; + private get productQuality(): string | undefined { + return this.storageService.get(productQualityKey, StorageScope.GLOBAL); + } + private set productQuality(productQuality: string | undefined) { + if (productQuality) { + this.storageService.store(productQualityKey, productQuality, StorageScope.GLOBAL, StorageTarget.MACHINE); + } else { + this.storageService.remove(productQualityKey, StorageScope.GLOBAL); + } + } + constructor( + @IProductService productService: IProductService, @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @@ -137,6 +154,9 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this.lastSyncUrl = this.syncUrl; this.syncUrl = userDataSyncStoreManagementService.userDataSyncStore?.url; + this.previousProductQuality = this.productQuality; + this.productQuality = productService.quality; + if (this.syncUrl) { this.logService.info('Using settings sync service', this.syncUrl.toString()); @@ -165,6 +185,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync())); this._register(Event.debounce(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false, false))); this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false, false))); + this._register(this.userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.triggerSync(['userDataSyncStoreChanged'], false, false))); } } @@ -256,6 +277,10 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } } + private hasProductQualityChanged(): boolean { + return !!this.previousProductQuality && !!this.productQuality && this.previousProductQuality !== this.productQuality; + } + private async onDidFinishSync(error: Error | undefined): Promise { if (!error) { // Sync finished without errors @@ -313,9 +338,22 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto // Service changed else if (userDataSyncError.code === UserDataSyncErrorCode.ServiceChanged || userDataSyncError.code === UserDataSyncErrorCode.DefaultServiceChanged) { - await this.turnOff(false, true /* force soft turnoff on error */, true /* do not disable machine */); - await this.turnOn(); - this.logService.info('Auto Sync: Sync Service changed. Turned off auto sync, reset local state and turned on auto sync.'); + + // Check if default settings sync service has changed in web without changing the product quality + // Then turn off settings sync and ask user to turn on again + if (isWeb && userDataSyncError.code === UserDataSyncErrorCode.DefaultServiceChanged && !this.hasProductQualityChanged()) { + await this.turnOff(false, true /* force soft turnoff on error */); + this.logService.info('Auto Sync: Turned off sync because default sync service is changed.'); + } + + // Service has changed by the user. So turn off and turn on sync. + // Show a prompt to the user about service change. + else { + await this.turnOff(false, true /* force soft turnoff on error */, true /* do not disable machine */); + await this.turnOn(); + this.logService.info('Auto Sync: Sync Service changed. Turned off auto sync, reset local state and turned on auto sync.'); + } + } else { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 310fea185f2..d34583109a8 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -108,6 +108,7 @@ export type IAuthenticationProvider = { id: string, scopes: string[] }; export interface IUserDataSyncStore { readonly url: URI; + readonly type: UserDataSyncStoreType; readonly defaultUrl: URI; readonly stableUrl: URI; readonly insidersUrl: URI; @@ -192,6 +193,12 @@ export interface IUserDataSyncBackupStoreService { export const HEADER_OPERATION_ID = 'x-operation-id'; export const HEADER_EXECUTION_ID = 'X-Execution-Id'; +export function createSyncHeaders(executionId: string): IHeaders { + const headers: IHeaders = {}; + headers[HEADER_EXECUTION_ID] = executionId; + return headers; +} + //#endregion // #region User Data Sync Error @@ -385,6 +392,13 @@ export interface IUserDataSynchroniser { //#endregion +// #region keys synced only in web + +export const SYNC_SERVICE_URL_TYPE = 'sync.store.url.type'; +export function getEnablementKey(resource: SyncResource) { return `sync.enable.${resource}`; } + +// #endregion + // #region User Data Sync Services export const IUserDataSyncResourceEnablementService = createDecorator('IUserDataSyncResourceEnablementService'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 21f82c43a6d..fc9acf98945 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -157,6 +157,7 @@ export class UserDataSyncStoreManagementServiceChannelClient extends Disposable private revive(userDataSyncStore: IUserDataSyncStore): IUserDataSyncStore { return { url: URI.revive(userDataSyncStore.url), + type: userDataSyncStore.type, defaultUrl: URI.revive(userDataSyncStore.defaultUrl), insidersUrl: URI.revive(userDataSyncStore.insidersUrl), stableUrl: URI.revive(userDataSyncStore.stableUrl), diff --git a/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts index 4fd2b3c339e..c96fbad455b 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts @@ -3,19 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncResourceEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncResourceEnablementService, ALL_SYNC_RESOURCES, SyncResource, getEnablementKey } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { isWeb } from 'vs/base/common/platform'; type SyncEnablementClassification = { enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; -const enablementKey = 'sync.enable'; -function getEnablementKey(resource: SyncResource) { return `${enablementKey}.${resource}`; } - export class UserDataSyncResourceEnablementService extends Disposable implements IUserDataSyncResourceEnablementService { _serviceBrand: any; @@ -39,10 +37,14 @@ export class UserDataSyncResourceEnablementService extends Disposable implements if (this.isResourceEnabled(resource) !== enabled) { const resourceEnablementKey = getEnablementKey(resource); this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); - this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE); + this.storeResourceEnablement(resourceEnablementKey, enabled); } } + private storeResourceEnablement(resourceEnablementKey: string, enabled: boolean): void { + this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL, isWeb ? StorageTarget.USER /* sync in web */ : StorageTarget.MACHINE); + } + private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void { if (storageChangeEvent.scope === StorageScope.GLOBAL) { const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === storageChangeEvent.key)[0]; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index cf0d022f8ca..b059b81ee40 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -5,7 +5,7 @@ import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode, - UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change, IUserDataSyncStoreManagementService, UserDataSyncStoreError + UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, MergeState, Change, IUserDataSyncStoreManagementService, UserDataSyncStoreError, createSyncHeaders } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -37,12 +37,6 @@ type SyncErrorClassification = { const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime'; -function createSyncHeaders(executionId: string): IHeaders { - const headers: IHeaders = {}; - headers[HEADER_EXECUTION_ID] = executionId; - return headers; -} - export class UserDataSyncService extends Disposable implements IUserDataSyncService { _serviceBrand: any; diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index a4bde40f314..c12dedbad75 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, toDisposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient, SYNC_SERVICE_URL_TYPE } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess as isSuccessContext, asJson } from 'vs/platform/request/common/request'; import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -23,7 +23,6 @@ import { isString, isObject, isArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; -const SYNC_SERVICE_URL_TYPE = 'sync.store.url.type'; const SYNC_PREVIOUS_STORE = 'sync.previous.store'; const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until'; const USER_SESSION_ID_KEY = 'sync.user-session-id'; @@ -31,7 +30,7 @@ const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id'; const REQUEST_SESSION_LIMIT = 100; const REQUEST_SESSION_INTERVAL = 1000 * 60 * 5; /* 5 minutes */ -type UserDataSyncStore = IUserDataSyncStore & { defaultType?: UserDataSyncStoreType; type?: UserDataSyncStoreType }; +type UserDataSyncStore = IUserDataSyncStore & { defaultType: UserDataSyncStoreType; }; export abstract class AbstractUserDataSyncStoreManagementService extends Disposable implements IUserDataSyncStoreManagementService { @@ -42,6 +41,13 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa private _userDataSyncStore: UserDataSyncStore | undefined; get userDataSyncStore(): UserDataSyncStore | undefined { return this._userDataSyncStore; } + protected get userDataSyncStoreType(): UserDataSyncStoreType | undefined { + return this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL) as UserDataSyncStoreType; + } + protected set userDataSyncStoreType(type: UserDataSyncStoreType | undefined) { + this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.GLOBAL, isWeb ? StorageTarget.USER /* sync in web */ : StorageTarget.MACHINE); + } + constructor( @IProductService protected readonly productService: IProductService, @IConfigurationService protected readonly configurationService: IConfigurationService, @@ -49,6 +55,7 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa ) { super(); this.updateUserDataSyncStore(); + this._register(Event.filter(storageService.onDidChangeValue, e => e.key === SYNC_SERVICE_URL_TYPE && e.scope === StorageScope.GLOBAL && this.userDataSyncStoreType !== this.userDataSyncStore?.type)(() => this.updateUserDataSyncStore())); } protected updateUserDataSyncStore(): void { @@ -56,8 +63,8 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa this._onDidChangeUserDataSyncStore.fire(); } - protected toUserDataSyncStore(productStore: ConfigurationSyncStore | undefined, configuredStore?: ConfigurationSyncStore): UserDataSyncStore | undefined { - // Web overrides + protected toUserDataSyncStore(productStore: ConfigurationSyncStore & { web?: ConfigurationSyncStore } | undefined, configuredStore?: ConfigurationSyncStore): UserDataSyncStore | undefined { + // Check for web overrides for backward compatibility while reading previous store productStore = isWeb && productStore?.web ? { ...productStore, ...productStore.web } : productStore; const value: Partial = { ...(productStore || {}), ...(configuredStore || {}) }; if (value @@ -67,7 +74,8 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa ) { const syncStore = value as ConfigurationSyncStore; const canSwitch = !!syncStore.canSwitch && !configuredStore?.url; - const type: UserDataSyncStoreType | undefined = canSwitch ? this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL) as UserDataSyncStoreType : undefined; + const defaultType: UserDataSyncStoreType = syncStore.url === syncStore.insidersUrl ? 'insiders' : 'stable'; + const type: UserDataSyncStoreType = (canSwitch ? this.userDataSyncStoreType : undefined) || defaultType; const url = configuredStore?.url || (type === 'insiders' ? syncStore.insidersUrl : type === 'stable' ? syncStore.stableUrl @@ -75,11 +83,11 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa return { url: URI.parse(url), type, - defaultType: syncStore.url === syncStore.insidersUrl ? 'insiders' : syncStore.url === syncStore.stableUrl ? 'stable' : undefined, + defaultType, defaultUrl: URI.parse(syncStore.url), stableUrl: URI.parse(syncStore.stableUrl), insidersUrl: URI.parse(syncStore.insidersUrl), - canSwitch: !!syncStore.canSwitch && !configuredStore?.url, + canSwitch, authenticationProviders: Object.keys(syncStore.authenticationProviders).reduce((result, id) => { result.push({ id, scopes: syncStore!.authenticationProviders[id].scopes }); return result; @@ -119,12 +127,8 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor } async switch(type: UserDataSyncStoreType): Promise { - if (this.userDataSyncStore?.canSwitch && type !== this.userDataSyncStore.type) { - if (type === this.userDataSyncStore.defaultType) { - this.storageService.remove(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL); - } else { - this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.GLOBAL, StorageTarget.MACHINE); - } + if (type !== this.userDataSyncStoreType) { + this.userDataSyncStoreType = type; this.updateUserDataSyncStore(); } } diff --git a/src/vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService.ts index 115f5ff011f..b2757dcd7b7 100644 --- a/src/vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService.ts @@ -11,10 +11,12 @@ import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/use import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; +import { IProductService } from 'vs/platform/product/common/productService'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { constructor( + @IProductService productService: IProductService, @IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService, @@ -27,7 +29,7 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @IStorageService storageService: IStorageService, @IUserDataAutoSyncEnablementService userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, ) { - super(userDataSyncStoreManagementService, userDataSyncStoreService, userDataSyncResourceEnablementService, userDataSyncService, logService, authTokenService, telemetryService, userDataSyncMachinesService, storageService, userDataAutoSyncEnablementService); + super(productService, userDataSyncStoreManagementService, userDataSyncStoreService, userDataSyncResourceEnablementService, userDataSyncService, logService, authTokenService, telemetryService, userDataSyncMachinesService, storageService, userDataAutoSyncEnablementService); this._register(Event.debounce(Event.any( Event.map(nativeHostService.onDidFocusWindow, () => 'windowFocus'), diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index 4550c939bb5..345fe859c44 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -51,6 +51,7 @@ suite('UserDataSyncStoreManagementService', () => { const expected: IUserDataSyncStore = { url: URI.parse('http://configureHost:3000'), + type: 'stable', defaultUrl: URI.parse('http://configureHost:3000'), stableUrl: URI.parse('http://configureHost:3000'), insidersUrl: URI.parse('http://configureHost:3000'), diff --git a/src/vs/platform/webview/electron-main/webviewMainService.ts b/src/vs/platform/webview/electron-main/webviewMainService.ts index dd3984d4ba5..ccce8561a33 100644 --- a/src/vs/platform/webview/electron-main/webviewMainService.ts +++ b/src/vs/platform/webview/electron-main/webviewMainService.ts @@ -35,12 +35,16 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer this.portMappingProvider = this._register(new WebviewPortMappingProvider(tunnelService)); const sess = session.fromPartition(webviewPartitionId); - sess.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => { + sess.setPermissionRequestHandler((_webContents, permission, callback) => { + if (permission === 'clipboard-read') { + return callback(true); + } + return callback(false); }); - sess.setPermissionCheckHandler((webContents, permission /* 'media' */) => { - return false; + sess.setPermissionCheckHandler((_webContents, permission /* 'media' */) => { + return permission === 'clipboard-read'; }); } diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index a78accbef8e..a68581665d7 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { getMarks, mark } from 'vs/base/common/performance'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, RenderProcessGoneDetails } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event } from 'electron'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,7 +19,7 @@ import product from 'vs/platform/product/common/product'; import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { defaultWindowState, ICodeWindow, ILoadEvent, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { defaultWindowState, ICodeWindow, ILoadEvent, IWindowState, WindowError, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -48,11 +48,6 @@ interface ITouchBarSegment extends SegmentedControlSegment { id: string; } -const enum WindowError { - UNRESPONSIVE = 1, - CRASHED = 2 -} - const enum ReadyState { /** @@ -269,7 +264,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Request handling this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentMainService, this.fileService, { get: key => storageMainService.globalStorage.get(key), - store: (key, value) => storageMainService.globalStorage.store(key, value) + store: (key, value) => storageMainService.globalStorage.set(key, value) }); // Eventing @@ -403,9 +398,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { private registerListeners(): void { // Crashes & Unrsponsive & Failed to load - this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details)); this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); - this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('Main: failed to load workbench window, ', errorDescription)); + this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details.reason)); + this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.onWindowError(WindowError.LOAD, errorDescription)); // Window close this._win.on('closed', () => { @@ -550,9 +545,21 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private async onWindowError(error: WindowError.UNRESPONSIVE): Promise; - private async onWindowError(error: WindowError.CRASHED, details: RenderProcessGoneDetails): Promise; - private async onWindowError(error: WindowError, details?: RenderProcessGoneDetails): Promise { - this.logService.error(error === WindowError.CRASHED ? `Main: renderer process crashed (detail: ${details?.reason})` : 'Main: detected unresponsive'); + private async onWindowError(error: WindowError.CRASHED, details: string): Promise; + private async onWindowError(error: WindowError.LOAD, details: string): Promise; + private async onWindowError(type: WindowError, details?: string): Promise { + + switch (type) { + case WindowError.CRASHED: + this.logService.error(`CodeWindow: renderer process crashed (detail: ${details})`); + break; + case WindowError.UNRESPONSIVE: + this.logService.error('CodeWindow: detected unresponsive'); + break; + case WindowError.LOAD: + this.logService.error(`CodeWindow: failed to load workbench window: ${details}`); + break; + } // If we run extension tests from CLI, showing a dialog is not // very helpful in this case. Rather, we bring down the test run @@ -569,10 +576,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { type WindowErrorEvent = { type: WindowError; }; - this.telemetryService.publicLog2('windowerror', { type: error }); + this.telemetryService.publicLog2('windowerror', { type }); // Unresponsive - if (error === WindowError.UNRESPONSIVE) { + if (type === WindowError.UNRESPONSIVE) { if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { // TODO@bpasero Workaround for https://github.com/microsoft/vscode/issues/56994 // In certain cases the window can report unresponsiveness because a breakpoint was hit @@ -606,12 +613,12 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Crashed - else { + else if (type === WindowError.CRASHED) { let message: string; - if (details && details.reason !== 'crashed') { - message = localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details?.reason); + if (details && details !== 'crashed') { + message = localize('appCrashedDetails', "The window has crashed (reason: '{0}')", details); } else { - message = localize('appCrashed', "The window has crashed", details?.reason); + message = localize('appCrashed', "The window has crashed", details); } const result = await this.dialogMainService.showMessageBox({ diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 5a1446b4c4f..96f92d28946 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -125,6 +125,24 @@ export interface ICodeWindow extends IDisposable { serializeWindowState(): IWindowState; } +export const enum WindowError { + + /** + * Maps to the `unresponsive` event on a `BrowserWindow`. + */ + UNRESPONSIVE = 1, + + /** + * Maps to the `render-proces-gone` event on a `WebContents`. + */ + CRASHED = 2, + + /** + * Maps to the `did-fail-load` event on a `WebContents`. + */ + LOAD = 3 +} + export const IWindowsMainService = createDecorator('windowsMainService'); export interface IWindowsCountChangedEvent { diff --git a/src/vs/platform/workspace/common/workspaceTrust.ts b/src/vs/platform/workspace/common/workspaceTrust.ts index 56f4fae82dc..629eddaf9a0 100644 --- a/src/vs/platform/workspace/common/workspaceTrust.ts +++ b/src/vs/platform/workspace/common/workspaceTrust.ts @@ -37,6 +37,11 @@ export interface IWorkspaceTrustModel { setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void; getFolderTrustState(folder: URI): WorkspaceTrustState; + + setTrustedFolders(folders: URI[]): void; + setUntrustedFolders(folders: URI[]): void; + + getTrustStateInfo(): IWorkspaceTrustStateInfo; } export interface IWorkspaceTrustRequest { diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index cb397ad60e8..06a682ab522 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -86,6 +86,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork if (!this.isWorkspacePath(uri)) { return null; // does not look like a valid workspace config file } + if (uri.scheme !== Schemas.file) { return null; } @@ -121,7 +122,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork return null; } - private doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { + /* TODO@bpasero make private again */ doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { // Parse workspace file const storedWorkspace: IStoredWorkspace = parse(contents); // use fault tolerant parser diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts index 55ad732b8b3..d782f7b94a9 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -11,7 +11,7 @@ import * as pfs from 'vs/base/node/pfs'; import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { WorkspacesManagementMainService, IStoredWorkspace, getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; -import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation, IWorkspaceIdentifier, IStoredWorkspaceFolder, UNTITLED_WORKSPACE_NAME, isUntitledWorkspace, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { NullLogService } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -60,10 +60,10 @@ suite('WorkspacesManagementMainService', () => { } function createWorkspace(workspaceConfigPath: string, folders: (string | URI)[], names?: string[]): void { - const ws: IStoredWorkspace = { folders: [] }; + for (let i = 0; i < folders.length; i++) { const f = folders[i]; const s: IStoredWorkspaceFolder = f instanceof URI ? { uri: f.toString() } : { path: f }; @@ -72,6 +72,7 @@ suite('WorkspacesManagementMainService', () => { } ws.folders.push(s); } + fs.writeFileSync(workspaceConfigPath, JSON.stringify(ws)); } @@ -84,14 +85,19 @@ suite('WorkspacesManagementMainService', () => { let environmentMainService: EnvironmentMainService; let service: WorkspacesManagementMainService; + const cwd = process.cwd(); + const tmpDir = os.tmpdir(); + setup(async () => { - testDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesmanagementmainservice'); + testDir = getRandomTestPath(tmpDir, 'vsctests', 'workspacesmanagementmainservice'); untitledWorkspacesHomePath = path.join(testDir, 'Workspaces'); environmentMainService = new class TestEnvironmentService extends EnvironmentMainService { + constructor() { super(parseArgs(process.argv, OPTIONS)); } + get untitledWorkspacesHome(): URI { return URI.file(untitledWorkspacesHomePath); } @@ -122,29 +128,29 @@ suite('WorkspacesManagementMainService', () => { } test('createWorkspace (folders)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); assert.strictEqual(ws.folders.length, 2); - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assertPathEquals((ws.folders[0]).path, cwd); + assertPathEquals((ws.folders[1]).path, tmpDir); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); }); test('createWorkspace (folders with name)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); + const workspace = await createUntitledWorkspace([cwd, tmpDir], ['currentworkingdirectory', 'tempdir']); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); assert.strictEqual(ws.folders.length, 2); - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assertPathEquals((ws.folders[0]).path, cwd); + assertPathEquals((ws.folders[1]).path, tmpDir); assert.strictEqual((ws.folders[0]).name, 'currentworkingdirectory'); assert.strictEqual((ws.folders[1]).name, 'tempdir'); }); @@ -168,30 +174,30 @@ suite('WorkspacesManagementMainService', () => { }); test('createWorkspaceSync (folders)', () => { - const workspace = createUntitledWorkspaceSync([process.cwd(), os.tmpdir()]); + const workspace = createUntitledWorkspaceSync([cwd, tmpDir]); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.strictEqual(ws.folders.length, 2); - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assertPathEquals((ws.folders[0]).path, cwd); + assertPathEquals((ws.folders[1]).path, tmpDir); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); }); test('createWorkspaceSync (folders with names)', () => { - const workspace = createUntitledWorkspaceSync([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); + const workspace = createUntitledWorkspaceSync([cwd, tmpDir], ['currentworkingdirectory', 'tempdir']); assert.ok(workspace); assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.strictEqual(ws.folders.length, 2); - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assertPathEquals((ws.folders[0]).path, cwd); + assertPathEquals((ws.folders[1]).path, tmpDir); assert.strictEqual((ws.folders[0]).name, 'currentworkingdirectory'); assert.strictEqual((ws.folders[1]).name, 'tempdir'); @@ -216,7 +222,7 @@ suite('WorkspacesManagementMainService', () => { }); test('resolveWorkspaceSync', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); // make it a valid workspace path @@ -235,7 +241,7 @@ suite('WorkspacesManagementMainService', () => { }); test('resolveWorkspaceSync (support relative paths)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -243,7 +249,7 @@ suite('WorkspacesManagementMainService', () => { }); test('resolveWorkspaceSync (support relative paths #2)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -251,7 +257,7 @@ suite('WorkspacesManagementMainService', () => { }); test('resolveWorkspaceSync (support relative paths #3)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -259,7 +265,7 @@ suite('WorkspacesManagementMainService', () => { }); test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); @@ -267,8 +273,7 @@ suite('WorkspacesManagementMainService', () => { }); test('rewriteWorkspaceFileForNewLocation', async () => { - const folder1 = process.cwd(); // absolute path because outside of tmpDir - const tmpDir = os.tmpdir(); + const folder1 = cwd; // absolute path because outside of tmpDir const tmpInsideDir = path.join(tmpDir, 'inside'); const firstConfigPath = path.join(tmpDir, 'myworkspace0.code-workspace'); @@ -315,8 +320,8 @@ suite('WorkspacesManagementMainService', () => { }); test('rewriteWorkspaceFileForNewLocation (preserves comments)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); - const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + const workspace = await createUntitledWorkspace([cwd, tmpDir, path.join(tmpDir, 'somefolder')]); + const workspaceConfigPath = URI.file(path.join(tmpDir, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); origContent = `// this is a comment\n${origContent}`; @@ -327,8 +332,8 @@ suite('WorkspacesManagementMainService', () => { }); test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); - const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + const workspace = await createUntitledWorkspace([cwd, tmpDir, path.join(tmpDir, 'somefolder')]); + const workspaceConfigPath = URI.file(path.join(tmpDir, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash @@ -340,7 +345,7 @@ suite('WorkspacesManagementMainService', () => { }); (!isWindows ? test.skip : test)('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { - const workspaceLocation = path.join(os.tmpdir(), 'wsloc'); + const workspaceLocation = path.join(tmpDir, 'wsloc'); const folder1Location = 'x:\\foo'; const folder2Location = '\\\\server\\share2\\some\\path'; const folder3Location = path.join(workspaceLocation, 'inner', 'more'); @@ -358,14 +363,14 @@ suite('WorkspacesManagementMainService', () => { }); test('deleteUntitledWorkspaceSync (untitled)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); assert.ok(fs.existsSync(workspace.configPath.fsPath)); service.deleteUntitledWorkspaceSync(workspace); assert.ok(!fs.existsSync(workspace.configPath.fsPath)); }); test('deleteUntitledWorkspaceSync (saved)', async () => { - const workspace = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const workspace = await createUntitledWorkspace([cwd, tmpDir]); service.deleteUntitledWorkspaceSync(workspace); }); @@ -373,24 +378,43 @@ suite('WorkspacesManagementMainService', () => { let untitled = service.getUntitledWorkspacesSync(); assert.strictEqual(untitled.length, 0); - const untitledOne = await createUntitledWorkspace([process.cwd(), os.tmpdir()]); + const untitledOne = await createUntitledWorkspace([cwd, tmpDir]); assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); untitled = service.getUntitledWorkspacesSync(); assert.strictEqual(1, untitled.length); assert.strictEqual(untitledOne.id, untitled[0].workspace.id); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected missing untitled workspace: ${untitledOne.configPath.fsPath} does not exist anymore?`); - const untitledTwo = await createUntitledWorkspace([os.tmpdir(), process.cwd()]); + const untitledTwo = await createUntitledWorkspace([tmpDir, cwd]); assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); - assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); + const untitledHome = dirname(dirname(untitledTwo.configPath)); - const beforeGettingUntitledWorkspaces = fs.readdirSync(untitledHome.fsPath).map(name => fs.readFileSync(joinPath(untitledHome, name, 'workspace.json').fsPath, 'utf8')); - untitled = service.getUntitledWorkspacesSync(); - assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); - if (untitled.length === 1) { - assert.fail(`Unexpected workspaces count of 1 (expected 2), all workspaces:\n ${fs.readdirSync(untitledHome.fsPath).map(name => fs.readFileSync(joinPath(untitledHome, name, 'workspace.json').fsPath, 'utf8'))}, before getUntitledWorkspacesSync: ${beforeGettingUntitledWorkspaces}`); + const beforeGettingUntitledWorkspaces = fs.readdirSync(untitledHome.fsPath).map(folder => fs.readFileSync(joinPath(untitledHome, folder, UNTITLED_WORKSPACE_NAME).fsPath, 'utf8')); + + assert.strictEqual(untitledHome.fsPath, dirname(dirname(untitledOne.configPath)).fsPath, `Unexpected different untitled workspace homes: ${untitledHome} vs ${dirname(dirname(untitledTwo.configPath))}`); + + for (const folder of fs.readdirSync(untitledHome.fsPath)) { + const untitledPath = joinPath(untitledHome, folder, UNTITLED_WORKSPACE_NAME); + assert.strictEqual(isUntitledWorkspace(untitledPath, environmentMainService), true); + assert.strictEqual(untitledPath.scheme, 'file'); + + const untitledContents = fs.readFileSync(untitledPath.fsPath, 'utf8'); + + const storedWorkspace = service.doParseStoredWorkspace(untitledPath, untitledContents); + assert.ok(storedWorkspace); + + const folders = toWorkspaceFolders(storedWorkspace.folders, untitledPath, extUriBiasedIgnorePathCase); + assert.strictEqual(folders.length, 2); + + const resolvedWorkspace = service.resolveLocalWorkspaceSync(untitledPath); + assert.notStrictEqual(resolvedWorkspace, null, `Untitled workspace unexpectedly did not resolve: ${untitledPath.fsPath}`); } - assert.strictEqual(2, untitled.length); + + untitled = service.getUntitledWorkspacesSync(); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected missing untitled workspace: ${untitledOne.configPath.fsPath} does not exist anymore?`); + assert.ok(fs.existsSync(untitledTwo.configPath.fsPath), `Unexpected missing untitled workspace: ${untitledTwo.configPath.fsPath} does not exist anymore?`); + assert.strictEqual(2, untitled.length, `Unexpected workspaces count (expected 2), all workspaces:\n ${fs.readdirSync(untitledHome.fsPath).map(folder => fs.readFileSync(joinPath(untitledHome, folder, UNTITLED_WORKSPACE_NAME).fsPath, 'utf8'))}, before getUntitledWorkspacesSync: ${beforeGettingUntitledWorkspaces}`); service.deleteUntitledWorkspaceSync(untitledOne); untitled = service.getUntitledWorkspacesSync(); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 6bd29a1c0f9..3d74824e6a2 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -204,6 +204,12 @@ declare module 'vscode' { elevationRequired?: boolean; } + export enum CandidatePortSource { + None = 0, + Process = 1, + Output = 2 + } + export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; export class RemoteAuthorityResolverError extends Error { @@ -238,6 +244,8 @@ declare module 'vscode' { elevation: boolean; public: boolean; }; + + candidatePortSource?: CandidatePortSource; } export namespace workspace { @@ -734,8 +742,156 @@ declare module 'vscode' { //#endregion + //#region inline value provider: https://github.com/microsoft/vscode/issues/105690 + + /** + * The inline values provider interface defines the contract between extensions and the VS Code debugger inline values feature. + * In this contract the provider returns inline value information for a given document range + * and VS Code shows this information in the editor at the end of lines. + */ + export interface InlineValuesProvider { + + /** + * An optional event to signal that inline values have changed. + * @see [EventEmitter](#EventEmitter) + */ + onDidChangeInlineValues?: Event | undefined; + + /** + * Provide "inline value" information for a given document and range. + * VS Code calls this method whenever debugging stops in the given document. + * The returned inline values information is rendered in the editor at the end of lines. + * + * @param document The document for which the inline values information is needed. + * @param viewPort The visible document range for which inline values should be computed. + * @param context A bag containing contextual information like the current location. + * @param token A cancellation token. + * @return An array of InlineValueDescriptors or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken): ProviderResult; + } + + /** + * A value-object that contains additional information when requesting inline values from a InlineValuesProvider. + * A minimal context containes just the document location where the debugger has stopped. + * Additional optional information might be scope information or variables and their values. + */ + export interface InlineValueContext { + /** + * The document range where execution has stopped. + * Typically the end position of the range denotes the line where the inline values are shown. + */ + stoppedLocation: Range; + + // ... more to come, e.g. Scope information or variable/value candidate information + } + + /** + * Inline value information can be provided by different means: + * - directly as a text value (class InlineValueText). + * - as a name to use for a variable lookup (class InlineValueVariableLookup) + * - as an evaluatable expression (class InlineValueEvaluatableExpression) + * The InlineValue types combines all inline value types into one type. + */ + export type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression; + + /** + * Provide inline value as text. + */ + export class InlineValueText { + /** + * The document range for which the inline value applies. + */ + readonly range: Range; + /** + * The text of the inline value. + */ + readonly text: string; + /** + * Creates a new InlineValueText object. + * + * @param range The document line where to show the inline value. + * @param text The value to be shown for the line. + */ + constructor(range: Range, text: string); + } + + /** + * Provide inline value through a variable lookup. + * If only a range is specified, the variable name will be extracted from the underlying document. + * An optional variable name can be used to override the extracted name. + */ + export class InlineValueVariableLookup { + /** + * The document range for which the inline value applies. + * The range is used to extract the variable name from the underlying document. + */ + readonly range: Range; + /** + * If specified the name of the variable to look up. + */ + readonly variableName?: string; + /** + * How to perform the lookup. + */ + readonly caseSensitiveLookup: boolean; + /** + * Creates a new InlineValueVariableLookup object. + * + * @param range The document line where to show the inline value. + * @param variableName The name of the variable to look up. + * @param caseSensitiveLookup How to perform the lookup. If missing lookup is case sensitive. + */ + constructor(range: Range, variableName?: string, caseSensitiveLookup?: boolean); + } + + /** + * Provide an inline value through an expression evaluation. + * If only a range is specified, the expression will be extracted from the underlying document. + * An optional expression can be used to override the extracted expression. + */ + export class InlineValueEvaluatableExpression { + /** + * The document range for which the inline value applies. + * The range is used to extract the evaluatable expression from the underlying document. + */ + readonly range: Range; + /** + * If specified the expression overrides the extracted expression. + */ + readonly expression?: string; + /** + * Creates a new InlineValueEvaluatableExpression object. + * + * @param range The range in the underlying document from which the evaluatable expression is extracted. + * @param expression If specified overrides the extracted expression. + */ + constructor(range: Range, expression?: string); + } + + export namespace languages { + + /** + * Register a provider that returns data for the debugger's 'inline value' feature. + * Whenever the generic VS Code debugger has stopped in a source file, providers registered for the language of the file + * are called to return textual data that will be shown in the editor at the end of lines. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An inline values provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerInlineValuesProvider(selector: DocumentSelector, provider: InlineValuesProvider): Disposable; + } + + //#endregion + // eslint-disable-next-line vscode-dts-region-comments - //#region debug + //#region @weinand: variables view action contributions /** * A DebugProtocolVariableContainer is an opaque stand-in type for the intersection of the Scope and Variable types defined in the Debug Adapter Protocol. @@ -1013,34 +1169,44 @@ declare module 'vscode' { Idle = 2 } - // TODO@API - // make this a class, allow modified using with-pattern - export interface NotebookCellMetadata { + export class NotebookCellMetadata { /** * Controls whether a cell's editor is editable/readonly. */ - editable?: boolean; - + readonly editable?: boolean; /** * Controls if the cell has a margin to support the breakpoint UI. * This metadata is ignored for markdown cell. */ - breakpointMargin?: boolean; - + readonly breakpointMargin?: boolean; /** * Whether a code cell's editor is collapsed */ - inputCollapsed?: boolean; - + readonly outputCollapsed?: boolean; /** * Whether a code cell's outputs are collapsed */ - outputCollapsed?: boolean; - + readonly inputCollapsed?: boolean; /** * Additional attributes of a cell metadata. */ - custom?: { [key: string]: any; }; + readonly custom?: Record; + + // todo@API duplicates status bar API + readonly statusMessage?: string; + + // run related API, will be removed + readonly runnable?: boolean; + readonly hasExecutionOrder?: boolean; + readonly executionOrder?: number; + readonly runState?: NotebookCellRunState; + readonly runStartTime?: number; + readonly lastRunDuration?: number; + + constructor(editable?: boolean, breakpointMargin?: boolean, runnable?: boolean, hasExecutionOrder?: boolean, executionOrder?: number, runState?: NotebookCellRunState, runStartTime?: number, statusMessage?: string, lastRunDuration?: number, inputCollapsed?: boolean, outputCollapsed?: boolean, custom?: Record) + + // todo@API write a proper signature + with(change: Partial>): NotebookCellMetadata; } // todo@API support ids https://github.com/jupyter/enhancement-proposals/blob/master/62-cell-id/cell-id.md @@ -1052,39 +1218,48 @@ declare module 'vscode' { readonly document: TextDocument; readonly language: string; readonly outputs: readonly NotebookCellOutput[]; - readonly metadata: NotebookCellMetadata; - /** @deprecated use WorkspaceEdit.replaceCellOutput */ - // outputs: CellOutput[]; - // readonly outputs2: NotebookCellOutput[]; - /** @deprecated use WorkspaceEdit.replaceCellMetadata */ - // metadata: NotebookCellMetadata; + readonly metadata: NotebookCellMetadata } + export class NotebookDocumentMetadata { - export interface NotebookDocumentMetadata { /** * Controls if users can add or delete cells * Defaults to true */ - editable?: boolean; - + readonly editable: boolean; /** * Default value for [cell editable metadata](#NotebookCellMetadata.editable). * Defaults to true. */ - cellEditable?: boolean; - displayOrder?: GlobPattern[]; - + readonly cellEditable: boolean; /** * Additional attributes of the document metadata. */ - custom?: { [key: string]: any; }; - + readonly custom: { [key: string]: any; }; /** * Whether the document is trusted, default to true * When false, insecure outputs like HTML, JavaScript, SVG will not be rendered. */ - trusted?: boolean; + readonly trusted: boolean; + + // todo@API how does glob apply to mime times? + readonly displayOrder: GlobPattern[]; + + // todo@API is this a kernel property? + readonly cellHasExecutionOrder: boolean; + + // run related, remove infer from kernel, exec + // todo@API infer from kernel + // todo@API remove + readonly runnable: boolean; + readonly cellRunnable: boolean; + readonly runState: NotebookRunState; + + constructor(editable?: boolean, runnable?: boolean, cellEditable?: boolean, cellRunnable?: boolean, cellHasExecutionOrder?: boolean, displayOrder?: GlobPattern[], custom?: { [key: string]: any; }, runState?: NotebookRunState, trusted?: boolean); + + // TODO@API make this a proper signature + with(change: Partial>): NotebookDocumentMetadata; } export interface NotebookDocumentContentOptions { @@ -1113,6 +1288,15 @@ declare module 'vscode' { readonly cells: ReadonlyArray; readonly contentOptions: NotebookDocumentContentOptions; readonly metadata: NotebookDocumentMetadata; + + /** + * Save the document. The saving will be handled by the corresponding content provider + * + * @return A promise that will resolve to true when the document + * has been saved. If the file was not dirty or the save failed, + * will return false. + */ + save(): Thenable; } // todo@API maybe have a NotebookCellPosition sibling @@ -1358,9 +1542,9 @@ declare module 'vscode' { readonly mime: string; readonly value: unknown; - readonly metadata?: Record; + readonly metadata?: Record; - constructor(mime: string, value: unknown, metadata?: Record); + constructor(mime: string, value: unknown, metadata?: Record); } // @jrieken @@ -1368,7 +1552,11 @@ declare module 'vscode' { export class NotebookCellOutput { readonly id: string; readonly outputs: NotebookCellOutputItem[]; - constructor(outputs: NotebookCellOutputItem[], id?: string); + readonly metadata?: Record; + + constructor(outputs: NotebookCellOutputItem[], metadata?: Record); + + constructor(outputs: NotebookCellOutputItem[], id: string, metadata?: Record); } //#endregion @@ -1487,34 +1675,6 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/106744, NotebookKernel - export interface NotebookDocumentMetadata { - - /** - * Controls whether the full notebook can be run at once. - * Defaults to true - */ - // todo@API infer from kernel - // todo@API remove - runnable?: boolean; - - /** - * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). - * Defaults to true. - */ - cellRunnable?: boolean; - - /** - * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). - * Defaults to true. - */ - cellHasExecutionOrder?: boolean; - - /** - * The document's current run state - */ - runState?: NotebookRunState; - } - // todo@API use the NotebookCellExecution-object as a container to model and enforce // the flow of a cell execution @@ -1536,49 +1696,6 @@ declare module 'vscode' { // export const onDidStartNotebookCellExecution: Event; // export const onDidStopNotebookCellExecution: Event; - export interface NotebookCellMetadata { - - /** - * Controls if the cell is executable. - * This metadata is ignored for markdown cell. - */ - // todo@API infer from kernel - runnable?: boolean; - - /** - * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. - * Defaults to true. - */ - hasExecutionOrder?: boolean; - - /** - * The order in which this cell was executed. - */ - executionOrder?: number; - - /** - * A status message to be shown in the cell's status bar - */ - // todo@API duplicates status bar API - statusMessage?: string; - - /** - * The cell's current run state - */ - runState?: NotebookCellRunState; - - /** - * If the cell is running, the time at which the cell started running - */ - runStartTime?: number; - - /** - * The total duration of the cell's last run - */ - // todo@API depends on having output - lastRunDuration?: number; - } - export interface NotebookKernel { readonly id?: string; label: string; @@ -2055,6 +2172,15 @@ declare module 'vscode' { //#endregion + //#region https://github.com/microsoft/vscode/issues/116906 + + export interface ExtensionContext { + readonly extensionId: string; + readonly extensionVersion: string; + } + + //#endregion + //#region https://github.com/microsoft/vscode/issues/102091 export interface TextDocument { @@ -2102,24 +2228,33 @@ declare module 'vscode' { export function createDocumentTestObserver(document: TextDocument): TestObserver; /** - * The last or selected test run. Cleared when a new test run starts. + * Inserts custom test results into the VS Code UI. The results are + * inserted and sorted based off the `completedAt` timestamp. If the + * results are being read from a file, for example, the `completedAt` + * time should generally be the modified time of the file if not more + * specific time is available. + * + * This will no-op if the inserted results are deeply equal to an + * existing result. + * + * @param results test results + * @param persist whether the test results should be saved by VS Code + * and persisted across reloads. Defaults to true. */ - export const testResults: TestResults | undefined; + export function publishTestResult(results: TestResults, persist?: boolean): void; /** - * Event that fires when the testResults are updated. - */ + * List of test results stored by VS Code, sorted in descnding + * order by their `completedAt` time. + */ + export const testResults: ReadonlyArray; + + /** + * Event that fires when the {@link testResults} array is updated. + */ export const onDidChangeTestResults: Event; } - export interface TestResults { - /** - * The results from the latest test run. The array contains a snapshot of - * all tests involved in the run at the moment when it completed. - */ - readonly tests: ReadonlyArray | undefined; - } - export interface TestObserver { /** * List of tests returned by test provider for files in the workspace. @@ -2426,6 +2561,51 @@ declare module 'vscode' { location?: Location; } + /** + * TestResults can be provided to VS Code, or read from it. + * + * The results contain a 'snapshot' of the tests at the point when the test + * run is complete. Therefore, information such as {@link Location} instances + * may be out of date. If the test still exists in the workspace, consumers + * can use its `id` to correlate the result instance with the living test. + * + * @todo coverage and other info may eventually be provided here + */ + export interface TestResults { + /** + * Unix milliseconds timestamp at which the tests were completed. + */ + completedAt: number; + + /** + * List of test results. The items in this array are the items that + * were passed in the {@link test.runTests} method. + */ + results: ReadonlyArray>; + } + + /** + * A {@link TestItem} with an associated result, which appear or can be + * provided in {@link TestResult} interfaces. + */ + export interface TestItemWithResults extends TestItem { + /** + * ID of the test result, this is required in order to correlate the result + * with the live test item. + */ + readonly id: string; + + /** + * Current result of the test. + */ + result: TestState; + + /** + * Optional list of nested tests for this item. + */ + children?: Readonly[]; + } + //#endregion //#region Opener service (https://github.com/microsoft/vscode/issues/109277) diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 64f7d14880f..4dd3b8f4001 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -53,7 +53,7 @@ export class MainThreadAuthenticationProvider extends Disposable { }); quickPick.items = items; - quickPick.selectedItems = items; + quickPick.selectedItems = items.filter(item => item.extension.allowed === undefined || item.extension.allowed); quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); quickPick.placeholder = nls.localize('manageExensions', "Choose which extensions can access this account"); @@ -120,7 +120,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); this._register(this.authenticationService.onDidChangeSessions(e => { - this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label, e.event); + this._proxy.$onDidChangeAuthenticationSessions(e.providerId, e.label); })); this._register(this.authenticationService.onDidRegisterAuthenticationProvider(info => { @@ -172,12 +172,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } private async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - if (!allowList.find(allowed => allowed.id === extensionId)) { - allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); - } - + this.authenticationService.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true); this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE); } @@ -225,8 +220,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu if (!didAcceptPrompt) { throw new Error('User did not consent to login.'); } - } else { + } else if (allowed !== false) { this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, [session]); + return undefined; + } else { + return undefined; } } } else { @@ -234,6 +232,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu session = await this.selectSession(providerId, extensionId, extensionName, sessions, !!options.clearSessionPreference); } else { this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, sessions); + return undefined; } } } else { diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index abdd99cd816..ca3afccd5e4 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -13,7 +13,6 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IFileService, FileOperation } from 'vs/platform/files/common/files'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ITextEditorModel } from 'vs/workbench/common/editor'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources'; @@ -47,8 +46,8 @@ export class BoundModelReferenceCollection { } } - add(uri: URI, ref: IReference): void { - const length = ref.object.textEditorModel.getValueLength(); + add(uri: URI, ref: IReference, length: number = 0): void { + // const length = ref.object.textEditorModel.getValueLength(); let handle: any; let entry: { uri: URI, length: number, dispose(): void }; const dispose = () => { @@ -267,7 +266,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen private _handleAsResourceInput(uri: URI): Promise { return this._textModelResolverService.createModelReference(uri).then(ref => { - this._modelReferenceCollection.add(uri, ref); + this._modelReferenceCollection.add(uri, ref, ref.object.textEditorModel.getValueLength()); return ref.object.textEditorModel.uri; }); } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 04d6dfed752..fd83c6e81a0 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -250,6 +250,31 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- inline values + + $registerInlineValuesProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { + const provider = { + provideInlineValues: (model: ITextModel, viewPort: EditorRange, context: modes.InlineValueContext, token: CancellationToken): Promise => { + return this._proxy.$provideInlineValues(handle, model.uri, viewPort, context, token); + } + }; + + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations.set(eventHandle, emitter); + provider.onDidChangeInlineValues = emitter.event; + } + + this._registrations.set(handle, modes.InlineValuesProviderRegistry.register(selector, provider)); + } + + $emitInlineValuesEvent(eventHandle: number, event?: any): void { + const obj = this._registrations.get(eventHandle); + if (obj instanceof Emitter) { + obj.fire(event); + } + } + // --- occurrences $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void { diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 5866a81d96a..1e8aa1b9a5b 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -3,28 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { diffMaps, diffSets } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { IRelativePattern } from 'vs/base/common/glob'; -import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; -import { IExtUri, isEqual } from 'vs/base/common/resources'; +import { isEqual } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { viewColumnToEditorGroup } from 'vs/workbench/common/editor'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookEditorModel, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -32,31 +32,20 @@ import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWi import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; class DocumentAndEditorState { static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): INotebookDocumentsAndEditorsDelta { if (!before) { - const apiEditors = []; - for (let id in after.textEditors) { - const editor = after.textEditors.get(id)!; - apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.getSelectionHandles(), visibleRanges: editor.visibleRanges }); - } - return { - addedDocuments: [], - addedEditors: apiEditors, + addedDocuments: [...after.documents].map(DocumentAndEditorState._asModelAddData), + addedEditors: [...after.textEditors.values()].map(DocumentAndEditorState._asEditorAddData), visibleEditors: [...after.visibleEditors].map(editor => editor[0]) }; } const documentDelta = diffSets(before.documents, after.documents); const editorDelta = diffMaps(before.textEditors, after.textEditors); - const addedAPIEditors = editorDelta.added.map(add => ({ - id: add.getId(), - documentUri: add.uri!, - selections: add.getSelectionHandles(), - visibleRanges: add.visibleRanges - })); + const addedAPIEditors = editorDelta.added.map(DocumentAndEditorState._asEditorAddData); const removedAPIEditors = editorDelta.removed.map(removed => removed.getId()); @@ -66,29 +55,7 @@ class DocumentAndEditorState { const visibleEditorDelta = diffMaps(before.visibleEditors, after.visibleEditors); return { - addedDocuments: documentDelta.added.map((e: NotebookTextModel): INotebookModelAddedData => { - return { - viewType: e.viewType, - uri: e.uri, - metadata: e.metadata, - versionId: e.versionId, - cells: e.cells.map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - })), - contentOptions: e.transientOptions, - // attachedEditor: editorId ? { - // id: editorId, - // selections: document.textModel.selections - // } : undefined - }; - }), + addedDocuments: documentDelta.added.map(DocumentAndEditorState._asModelAddData), removedDocuments: documentDelta.removed.map(e => e.uri), addedEditors: addedAPIEditors, removedEditors: removedAPIEditors, @@ -107,20 +74,51 @@ class DocumentAndEditorState { ) { // } + + private static _asModelAddData(e: NotebookTextModel): INotebookModelAddedData { + return { + viewType: e.viewType, + uri: e.uri, + metadata: e.metadata, + versionId: e.versionId, + cells: e.cells.map(cell => ({ + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + })), + contentOptions: e.transientOptions, + }; + } + + private static _asEditorAddData(add: IEditor): INotebookEditorAddData { + return { + id: add.getId(), + documentUri: add.uri!, + selections: add.getSelectionHandles(), + visibleRanges: add.visibleRanges + }; + } } @extHostNamedCustomer(MainContext.MainThreadNotebook) export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { + + private readonly _proxy: ExtHostNotebookShape; private readonly _notebookProviders = new Map(); private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); - private readonly _proxy: ExtHostNotebookShape; private readonly _toDisposeOnEditorRemove = new Map(); - private _currentState?: DocumentAndEditorState; private readonly _editorEventListenersMapping: Map = new Map(); private readonly _documentEventListenersMapping: ResourceMap = new ResourceMap(); private readonly _cellStatusBarEntries: Map = new Map(); private readonly _modelReferenceCollection: BoundModelReferenceCollection; + private _currentState?: DocumentAndEditorState; + constructor( extHostContext: IExtHostContext, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @@ -138,13 +136,14 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri); - this._register(this._modelReferenceCollection); this.registerListeners(); } dispose(): void { super.dispose(); + this._modelReferenceCollection.dispose(); + // remove all notebook providers for (let item of this._notebookProviders.values()) { item.disposable.dispose(); @@ -155,9 +154,10 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo item.emitter.dispose(); item.provider.dispose(); } - dispose(this._editorEventListenersMapping.values()); dispose(this._documentEventListenersMapping.values()); + dispose(this._toDisposeOnEditorRemove.values()); + dispose(this._cellStatusBarEntries.values()); } async $tryApplyEdits(_viewType: string, resource: UriComponents, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise { @@ -458,7 +458,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // if (!isEmptyChange) { this._currentState = newState; - await this._emitDelta(delta); + this._emitDelta(delta); // } } @@ -479,23 +479,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo contentOptions.transientOutputs = newOptions.transientOutputs; }, viewOptions: options.viewOptions, - reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => { - const data = await this._proxy.$resolveNotebookData(viewType, mainthreadTextModel.uri); - mainthreadTextModel.metadata = data.metadata; - mainthreadTextModel.transientOptions = contentOptions; - - const edits: ICellEditOperation[] = [ - { editType: CellEditType.Replace, index: 0, count: mainthreadTextModel.cells.length, cells: data.cells } - ]; - await new Promise(resolve => { - DOM.scheduleAtNextAnimationFrame(() => { - const ret = mainthreadTextModel!.applyEdits(mainthreadTextModel!.versionId, edits, true, undefined, () => undefined, undefined); - resolve(ret); - }); - }); - }, - resolveNotebookDocument: async (viewType: string, uri: URI, backupId?: string) => { - const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId); + openNotebook: async (viewType: string, uri: URI, backupId?: string) => { + const data = await this._proxy.$openNotebook(viewType, uri, backupId); return { data, transientOptions: contentOptions @@ -556,7 +541,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const result: INotebookKernel[] = []; const kernelsDto = await that._proxy.$provideNotebookKernels(handle, uri, token); for (const dto of kernelsDto) { - result.push({ id: dto.id, friendlyId: dto.friendlyId, @@ -672,9 +656,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } if (statusBarEntry.visible) { - this._cellStatusBarEntries.set( - id, - this._cellStatusBarService.addEntry(statusBarEntry)); + this._cellStatusBarEntries.set(id, this._cellStatusBarService.addEntry(statusBarEntry)); } } @@ -687,6 +669,15 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return uri; } + async $trySaveDocument(uriComponents: UriComponents) { + const uri = URI.revive(uriComponents); + + const ref = await this._notebookModelResolverService.resolve(uri); + const saveResult = await ref.object.save(); + ref.dispose(); + return saveResult; + } + async $tryShowNotebookDocument(resource: UriComponents, viewType: string, options: INotebookDocumentShowOptions): Promise { const editorOptions: ITextEditorOptions = { preserveFocus: options.preserveFocus, @@ -732,45 +723,3 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } } - - -export class BoundModelReferenceCollection { - - private _data = new Array<{ uri: URI, dispose(): void }>(); - - constructor( - private readonly _extUri: IExtUri, - private readonly _maxAge: number = 1000 * 60 * 3, - ) { - // - } - - dispose(): void { - this._data = dispose(this._data); - } - - remove(uri: URI): void { - for (const entry of [...this._data] /* copy array because dispose will modify it */) { - if (this._extUri.isEqualOrParent(entry.uri, uri)) { - entry.dispose(); - } - } - } - - add(uri: URI, ref: IReference): void { - let handle: any; - let entry: { uri: URI, dispose(): void }; - const dispose = () => { - const idx = this._data.indexOf(entry); - if (idx >= 0) { - ref.dispose(); - clearTimeout(handle); - this._data.splice(idx, 1); - } - }; - handle = setTimeout(dispose, this._maxAge); - entry = { uri, dispose }; - - this._data.push(entry); - } -} diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 8dad46c6a88..bba54eda321 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalConfiguration, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; @@ -17,6 +17,7 @@ import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IShellLaunchConfig, ITerminalDimensions } from 'vs/platform/terminal/common/terminal'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index 110becb9583..b33a86eae8e 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -5,11 +5,12 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { getTestSubscriptionKey, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; -import { ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService'; +import { getTestSubscriptionKey, ISerializedTestResults, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { HydratedTestResult, ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol'; @@ -40,11 +41,19 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh this._register(this.testService.onShouldSubscribe(args => this.proxy.$subscribeToTests(args.resource, args.uri))); this._register(this.testService.onShouldUnsubscribe(args => this.proxy.$unsubscribeFromTests(args.resource, args.uri))); - // const testCompleteListener = this._register(new MutableDisposable()); - // todo(@connor4312): reimplement, maybe - // this._register(resultService.onResultsChanged(results => { - // testCompleteListener.value = results.onComplete(() => this.proxy.$publishTestResults({ tests: [] })); - // })); + + const prevResults = resultService.results.map(r => r.toJSON()).filter(isDefined); + if (prevResults.length) { + this.proxy.$publishTestResults(prevResults); + } + + this._register(resultService.onResultsChanged(evt => { + const results = 'completed' in evt ? evt.completed : ('inserted' in evt ? evt.inserted : undefined); + const serialized = results?.toJSON(); + if (serialized) { + this.proxy.$publishTestResults([serialized]); + } + })); testService.updateRootProviderCount(1); @@ -53,6 +62,13 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh } } + /** + * @inheritdoc + */ + $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void { + this.resultService.push(new HydratedTestResult(results, persist)); + } + /** * @inheritdoc */ diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index cfe9d842e8d..40b6794bfae 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -31,7 +31,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void { + async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): Promise { this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options); this.extensionService.whenInstalledExtensionsRegistered().then(() => { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index bc7c653b981..052f5a24516 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { PORT_AUTO_FORWARD_SETTING } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape { @@ -27,7 +27,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun @ITunnelService private readonly tunnelService: ITunnelService, @INotificationService private readonly notificationService: INotificationService, @IConfigurationService private readonly configurationService: IConfigurationService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService); @@ -132,6 +133,32 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun }); } + async $setCandidatePortSource(source: CandidatePortSource): Promise { + // Must wait for the remote environment before trying to set settings there. + this.remoteAgentService.getEnvironment().then(() => { + switch (source) { + case CandidatePortSource.None: { + const autoDetectionEnablement = this.configurationService.inspect(PORT_AUTO_FORWARD_SETTING); + if (autoDetectionEnablement.userRemote === undefined) { + // Only update the remote setting if the user hasn't already set it. + this.configurationService.updateValue(PORT_AUTO_FORWARD_SETTING, false, ConfigurationTarget.USER_REMOTE); + } + break; + } + case CandidatePortSource.Output: { + const candidatePortSourceSetting = this.configurationService.inspect(PORT_AUTO_SOURCE_SETTING); + if (candidatePortSourceSetting.userRemote === undefined) { + // Only update the remote setting if the user hasn't already set it. + this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, ConfigurationTarget.USER_REMOTE); + } + break; + } + default: // Do nothing, the defaults for these settings should be used. + } + }).catch(() => { + // The remote failed to get setup. Errors from that area will already be surfaced to the user. + }); + } dispose(): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b9bacd2d7f6..5f83c9abe57 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -15,7 +15,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, MainContext, ExtHostLogServiceShape, UIKind } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainContext, ExtHostLogServiceShape, UIKind, CandidatePortSource } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -198,10 +198,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } else if (typeof selector === 'string') { informOnce(selector); } else { - if (typeof selector.scheme === 'undefined') { + const filter = selector as vscode.DocumentFilter; // TODO: microsoft/TypeScript#42768 + if (typeof filter.scheme === 'undefined') { informOnce(selector); } - if (!extension.enableProposedApi && typeof selector.exclusive === 'boolean') { + if (!extension.enableProposedApi && typeof filter.exclusive === 'boolean') { throwProposedApiError(extension); } } @@ -338,13 +339,17 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTesting.runTests(provider); }, + publishTestResult(results, persist = true) { + checkProposedApiEnabled(extension); + return extHostTesting.publishExtensionProvidedResults(results, persist); + }, get onDidChangeTestResults() { checkProposedApiEnabled(extension); - return extHostTesting.onLastResultsChanged; + return extHostTesting.onResultsChanged; }, get testResults() { checkProposedApiEnabled(extension); - return extHostTesting.lastResults; + return extHostTesting.results; }, }; @@ -409,6 +414,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable { return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier); }, + registerInlineValuesProvider(selector: vscode.DocumentSelector, provider: vscode.InlineValuesProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerInlineValuesProvider(extension, checkSelector(selector), provider, extension.identifier); + }, registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, @@ -1129,6 +1138,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, CancellationError: errors.CancellationError, CancellationTokenSource: CancellationTokenSource, + CandidatePortSource: CandidatePortSource, CodeAction: extHostTypes.CodeAction, CodeActionKind: extHostTypes.CodeActionKind, CodeActionTrigger: extHostTypes.CodeActionTrigger, @@ -1165,6 +1175,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I EndOfLine: extHostTypes.EndOfLine, EnvironmentVariableMutatorType: extHostTypes.EnvironmentVariableMutatorType, EvaluatableExpression: extHostTypes.EvaluatableExpression, + InlineValueText: extHostTypes.InlineValueText, + InlineValueVariableLookup: extHostTypes.InlineValueVariableLookup, + InlineValueEvaluatableExpression: extHostTypes.InlineValueEvaluatableExpression, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, ExtensionMode: extHostTypes.ExtensionMode, @@ -1264,6 +1277,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // checkProposedApiEnabled(extension); return extHostTypes.NotebookCellRunState; }, + get NotebookDocumentMetadata() { + return extHostTypes.NotebookDocumentMetadata; + }, + get NotebookCellMetadata() { + return extHostTypes.NotebookCellMetadata; + }, get NotebookRunState() { // checkProposedApiEnabled(extension); return extHostTypes.NotebookRunState; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d8dea657c6f..eba04409256 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -41,7 +41,6 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { ITerminalDimensions, IShellLaunchConfig, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; import { ActivationKind, ExtensionActivationError, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; @@ -58,9 +57,10 @@ import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; -import { InternalTestItem, InternalTestResults, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { InternalTestItem, ITestState, RunTestForProviderRequest, RunTestsRequest, TestIdWithProvider, TestsDiff, ISerializedTestResults } from 'vs/workbench/contrib/testing/common/testCollection'; import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust'; +import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -285,7 +285,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): void; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): Promise; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; @@ -378,6 +378,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerInlineValuesProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; + $emitInlineValuesEvent(eventHandle: number, event?: any): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; @@ -788,6 +790,7 @@ export interface MainThreadNotebookShape extends IDisposable { $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; $unregisterNotebookKernelProvider(handle: number): Promise; $onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void; + $trySaveDocument(uri: UriComponents): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; @@ -990,6 +993,12 @@ export interface MainThreadWindowShape extends IDisposable { $asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise; } +export enum CandidatePortSource { + None = 0, + Process = 1, + Output = 2 +} + export interface MainThreadTunnelServiceShape extends IDisposable { $openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise; $closeTunnel(remote: { host: string, port: number }): Promise; @@ -998,6 +1007,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $setRemoteTunnelService(processId: number): Promise; $setCandidateFilter(): Promise; $onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise; + $setCandidatePortSource(source: CandidatePortSource): Promise; } export interface MainThreadTimelineShape extends IDisposable { @@ -1128,7 +1138,7 @@ export interface ExtHostAuthenticationShape { $getSessions(id: string, scopes?: string[]): Promise>; $createSession(id: string, scopes: string[]): Promise; $removeSession(id: string, sessionId: string): Promise; - $onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise; + $onDidChangeAuthenticationSessions(id: string, label: string): Promise; $onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise; $setProviders(providers: modes.AuthenticationProviderInformation[]): Promise; } @@ -1466,6 +1476,10 @@ export interface ILinkedEditingRangesDto { wordPattern?: IRegExpDto; } +export interface IInlineValueContextDto { + stoppedLocation: IRange; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1477,6 +1491,7 @@ export interface ExtHostLanguageFeaturesShape { $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideInlineValues(handle: number, resource: UriComponents, range: IRange, context: modes.InlineValueContext, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideLinkedEditingRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; @@ -1750,7 +1765,6 @@ export interface INotebookModelAddedData { cells: IMainCellDto[], viewType: string; metadata?: NotebookDocumentMetadata; - attachedEditor?: { id: string; selections: number[]; visibleRanges: ICellRange[] } contentOptions: { transientOutputs: boolean; transientMetadata: TransientMetadata; } } @@ -1785,7 +1799,7 @@ export interface INotebookKernelInfoDto2 { } export interface ExtHostNotebookShape { - $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise; + $openNotebook(viewType: string, uri: UriComponents, backupId?: string): Promise; $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise; $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; @@ -1839,7 +1853,7 @@ export interface ExtHostTestingShape { $unsubscribeFromTests(resource: ExtHostTestingResource, uri: UriComponents): void; $lookupTest(test: TestIdWithProvider): Promise; $acceptDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; - $publishTestResults(results: InternalTestResults): void; + $publishTestResults(results: ISerializedTestResults[]): void; } export interface MainThreadTestingShape { @@ -1850,6 +1864,7 @@ export interface MainThreadTestingShape { $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void; $updateTestStateInRun(runId: string, testId: string, state: ITestState): void; $runTests(req: RunTestsRequest, token: CancellationToken): Promise; + $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void; $retireTest(extId: string): void; } diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 93f77a9576d..6bdfa6fb2b4 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -359,6 +359,14 @@ const newCommands: ApiCommand[] = [ }; })) ), + // --- debug support + new ApiCommand( + 'vscode.executeInlineValueProvider', '_executeInlineValueProvider', 'Execute inline value provider', + [ApiCommandArgument.Uri, ApiCommandArgument.Range], + new ApiCommandResult('A promise that resolves to an array of InlineValue objects', result => { + return result.map(typeConverters.InlineValue.to); + }) + ), // --- open'ish commands new ApiCommand( 'vscode.open', '_workbench.open', 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.', diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index b4445864a7c..ebce2edf567 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -156,8 +156,8 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { throw new Error(`Unable to find authentication provider with handle: ${providerId}`); } - $onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent) { - this._onDidChangeSessions.fire({ provider: { id, label }, ...event }); + $onDidChangeAuthenticationSessions(id: string, label: string) { + this._onDidChangeSessions.fire({ provider: { id, label } }); return Promise.resolve(); } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 2274a4f12e7..47641e7993c 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -412,6 +412,14 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme get storageUri() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStorageUri() { return that._storagePath.globalValue(extensionDescription); }, get extensionMode() { return extensionMode; }, + get extensionId() { + checkProposedApiEnabled(extensionDescription); + return extensionDescription.identifier.value; + }, + get extensionVersion() { + checkProposedApiEnabled(extensionDescription); + return extensionDescription.version; + }, get extensionRuntime() { checkProposedApiEnabled(extensionDescription); return that.extensionRuntime; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 34ca106504d..5d96d713260 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -291,6 +291,24 @@ class EvaluatableExpressionAdapter { } } +class InlineValuesAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.InlineValuesProvider, + ) { } + + public provideInlineValues(resource: URI, viewPort: IRange, context: extHostProtocol.IInlineValueContextDto, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + return asPromise(() => this._provider.provideInlineValues(doc, typeConvert.Range.to(viewPort), typeConvert.InlineValueContext.to(context), token)).then(value => { + if (Array.isArray(value)) { + return value.map(iv => typeConvert.InlineValue.from(iv)); + } + return undefined; + }); + } +} + class DocumentHighlightAdapter { constructor( @@ -1334,7 +1352,8 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter - | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter + | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter + | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlineHintsAdapter; class AdapterData { @@ -1568,6 +1587,27 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined); } + // --- debug inline values + + registerInlineValuesProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineValuesProvider, extensionId?: ExtensionIdentifier): vscode.Disposable { + + const eventHandle = typeof provider.onDidChangeInlineValues === 'function' ? this._nextHandle() : undefined; + const handle = this._addNewAdapter(new InlineValuesAdapter(this._documents, provider), extension); + + this._proxy.$registerInlineValuesProvider(handle, this._transformDocumentSelector(selector), eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle !== undefined) { + const subscription = provider.onDidChangeInlineValues!(_ => this._proxy.$emitInlineValuesEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + return result; + } + + $provideInlineValues(handle: number, resource: UriComponents, range: IRange, context: extHostProtocol.IInlineValueContextDto, token: CancellationToken): Promise { + return this._withAdapter(handle, InlineValuesAdapter, adapter => adapter.provideInlineValues(URI.revive(resource), range, context, token), undefined); + } + // --- occurrences registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 6b995a33914..fbad27732be 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -123,6 +123,7 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable { label: kernel.label, extension: this._extension.identifier, extensionLocation: this._extension.extensionLocation, + providerHandle: this._handle, description: kernel.description, detail: kernel.detail, isPreferred: kernel.isPreferred, @@ -442,7 +443,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } - async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise { + async $openNotebook(viewType: string, uri: UriComponents, backupId?: string): Promise { const provider = this._notebookContentProviders.get(viewType); if (!provider) { throw new Error(`NO provider for '${viewType}'`); @@ -713,23 +714,32 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const that = this; - const document = new ExtHostNotebookDocument(this._documentsAndEditors, { - emitModelChange(event: vscode.NotebookCellsChangeEvent): void { - that._onDidChangeNotebookCells.fire(event); + const document = new ExtHostNotebookDocument( + this._proxy, + this._documentsAndEditors, + { + emitModelChange(event: vscode.NotebookCellsChangeEvent): void { + that._onDidChangeNotebookCells.fire(event); + }, + emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { + that._onDidChangeCellOutputs.fire(event); + }, + emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { + that._onDidChangeCellLanguage.fire(event); + }, + emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { + that._onDidChangeCellMetadata.fire(event); + }, + emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void { + that._onDidChangeNotebookDocumentMetadata.fire(event); + } }, - emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { - that._onDidChangeCellOutputs.fire(event); - }, - emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { - that._onDidChangeCellLanguage.fire(event); - }, - emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { - that._onDidChangeCellMetadata.fire(event); - }, - emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void { - that._onDidChangeNotebookDocumentMetadata.fire(event); - } - }, viewType, modelData.contentOptions, typeConverters.NotebookDocumentMetadata.to(modelData.metadata ?? {}), uri, storageRoot); + viewType, + modelData.contentOptions, + modelData.metadata ? typeConverters.NotebookDocumentMetadata.to(modelData.metadata) : new extHostTypes.NotebookDocumentMetadata(), + uri, + storageRoot + ); document.acceptModelChanged({ versionId: modelData.versionId, @@ -750,13 +760,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._documents.get(uri)?.dispose(); this._documents.set(uri, document); - - // create editor if populated - if (modelData.attachedEditor) { - this._createExtHostEditor(document, modelData.attachedEditor.id, modelData.attachedEditor.selections, modelData.attachedEditor.visibleRanges.map(typeConverters.NotebookCellRange.to)); - editorChanged = true; - } - this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments }); this._onDidOpenNotebookDocument.fire(document.notebookDocument); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index e9e715885c4..3c3c7913b7f 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -9,11 +9,11 @@ import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { CellKind, INotebookDocumentPropertiesChangeData } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { IMainCellDto, IOutputDto, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IMainCellDto, IOutputDto, IOutputItemDto, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; class RawContentChangeEvent { @@ -47,7 +47,7 @@ export class ExtHostCell { private _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; - private _outputs: IOutputDto[]; + private _outputs: extHostTypes.NotebookCellOutput[]; private _metadata: extHostTypes.NotebookCellMetadata; readonly handle: number; @@ -64,7 +64,7 @@ export class ExtHostCell { this.handle = _cellData.handle; this.uri = URI.revive(_cellData.uri); this.cellKind = _cellData.cellKind; - this._outputs = _cellData.outputs; + this._outputs = _cellData.outputs.map(extHostTypeConverters.NotebookCellOutput.to); this._metadata = extHostTypeConverters.NotebookCellMetadata.to(_cellData.metadata ?? {}); } @@ -87,7 +87,7 @@ export class ExtHostCell { cellKind: extHostTypeConverters.NotebookCellKind.to(this._cellData.cellKind), document: data.document, get language() { return data!.document.languageId; }, - get outputs() { return that._outputs.map(extHostTypeConverters.NotebookCellOutput.to); }, + get outputs() { return that._outputs.slice(0); }, set outputs(_value) { throw new Error('Use WorkspaceEdit to update cell outputs.'); }, get metadata() { return that._metadata; }, set metadata(_value) { throw new Error('Use WorkspaceEdit to update cell metadata.'); }, @@ -97,7 +97,18 @@ export class ExtHostCell { } setOutputs(newOutputs: IOutputDto[]): void { - this._outputs = newOutputs; + this._outputs = newOutputs.map(extHostTypeConverters.NotebookCellOutput.to); + } + + setOutputItems(outputId: string, append: boolean, newOutputItems: IOutputItemDto[]) { + const newItems = newOutputItems.map(extHostTypeConverters.NotebookCellOutputItem.to); + const output = this._outputs.find(op => op.id === outputId); + if (output) { + if (!append) { + output.outputs.length = 0; + } + output.outputs.push(...newItems); + } } setMetadata(newMetadata: NotebookCellMetadata): void { @@ -135,6 +146,7 @@ export class ExtHostNotebookDocument extends Disposable { private _disposed = false; constructor( + private readonly _proxy: MainThreadNotebookShape, private readonly _documentsAndEditors: ExtHostDocumentsAndEditors, private readonly _emitter: INotebookEventEmitter, private readonly _viewType: string, @@ -166,7 +178,8 @@ export class ExtHostNotebookDocument extends Disposable { get cells(): ReadonlyArray { return that._cells.map(cell => cell.cell); }, get metadata() { return that._metadata; }, set metadata(_value: Required) { throw new Error('Use WorkspaceEdit to update metadata.'); }, - get contentOptions() { return that._contentOptions; } + get contentOptions() { return that._contentOptions; }, + save() { return that._save(); } }); } return this._notebook; @@ -211,6 +224,8 @@ export class ExtHostNotebookDocument extends Disposable { this._moveCell(e.index, e.newIdx); } else if (e.kind === NotebookCellsChangeType.Output) { this._setCellOutputs(e.index, e.outputs); + } else if (e.kind === NotebookCellsChangeType.OutputItem) { + this._setCellOutputItems(e.index, e.outputId, e.append, e.outputItems); } else if (e.kind === NotebookCellsChangeType.ChangeLanguage) { this._changeCellLanguage(e.index, e.language); } else if (e.kind === NotebookCellsChangeType.ChangeCellMetadata) { @@ -219,6 +234,13 @@ export class ExtHostNotebookDocument extends Disposable { }); } + private async _save(): Promise { + if (this._disposed) { + return Promise.reject(new Error('Document has been closed')); + } + return this._proxy.$trySaveDocument(this.uri); + } + private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void { if (this._disposed) { return; @@ -301,6 +323,12 @@ export class ExtHostNotebookDocument extends Disposable { this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); } + private _setCellOutputItems(index: number, outputId: string, append: boolean, outputItems: IOutputItemDto[]): void { + const cell = this._cells[index]; + cell.setOutputItems(outputId, append, outputItems); + this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); + } + private _changeCellLanguage(index: number, language: string): void { const cell = this._cells[index]; const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language }; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index babcce145eb..393fdb64cfb 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -9,7 +9,7 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ITerminalChildProcess, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalChildProcess, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/platform/terminal/common/terminal'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index e1c86d4975c..78f4d26bc8e 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { deepFreeze } from 'vs/base/common/objects'; import { isDefined } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -16,11 +17,11 @@ import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTes import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { TestItem, TestState } from 'vs/workbench/api/common/extHostTypeConverters'; +import { TestItem, TestResults, TestState } from 'vs/workbench/api/common/extHostTypeConverters'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection'; -import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; +import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, RunTestForProviderRequest, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; import type * as vscode from 'vscode'; const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`; @@ -39,8 +40,8 @@ export class ExtHostTesting implements ExtHostTestingShape { private workspaceObservers: WorkspaceFolderTestObserverFactory; private textDocumentObservers: TextDocumentTestObserverFactory; - public onLastResultsChanged = this.resultsChangedEmitter.event; - public lastResults?: vscode.TestResults; + public onResultsChanged = this.resultsChangedEmitter.event; + public results: ReadonlyArray = []; constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) { this.proxy = rpc.getProxy(MainContext.MainThreadTesting); @@ -100,16 +101,26 @@ export class ExtHostTesting implements ExtHostTestingShape { }, token); } + /** + * Implements vscode.test.publishTestResults + */ + public publishExtensionProvidedResults(results: vscode.TestResults, persist: boolean): void { + this.proxy.$publishExtensionProvidedResults(TestResults.from(generateUuid(), results), persist); + } /** * Updates test results shown to extensions. * @override */ - public $publishTestResults(results: InternalTestResults): void { - const convert = (item: InternalTestItemWithChildren): vscode.RequiredTestItem => - ({ ...TestItem.toShallow(item.item), children: item.children.map(convert) }); + public $publishTestResults(results: ISerializedTestResults[]): void { + this.results = Object.freeze( + results + .map(r => deepFreeze(TestResults.to(r))) + .concat(this.results) + .sort((a, b) => b.completedAt - a.completedAt) + .slice(0, 32), + ); - this.lastResults = { tests: results.tests.map(convert) }; this.resultsChangedEmitter.fire(); } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index ca1914d2e38..df51908e74a 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -84,7 +84,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { if (!options || !options.treeDataProvider) { throw new Error('Options with treeDataProvider is mandatory'); } - + const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany }); const treeView = this.createExtHostTreeView(viewId, options, extension); return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, @@ -110,7 +110,9 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { reveal: (element: T, options?: IRevealOptions): Promise => { return treeView.reveal(element, options); }, - dispose: () => { + dispose: async () => { + // Wait for the registration promise to finish before doing the dispose. + await registerPromise; this.treeViews.delete(viewId); treeView.dispose(); } @@ -240,7 +242,6 @@ class ExtHostTreeView extends Disposable { } } this.dataProvider = options.treeDataProvider; - this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany }); if (this.dataProvider.onDidChangeTreeData) { this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element }))); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 910683b56be..d0403e39625 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -22,7 +22,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { MarkerSeverity, IRelatedInformation, IMarkerData, MarkerTag } from 'vs/platform/markers/common/markers'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { isString, isNumber } from 'vs/base/common/types'; +import { isString, isNumber, isDefined } from 'vs/base/common/types'; import * as marked from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -32,7 +32,7 @@ import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ITestItem, ITestState } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ISerializedTestResults, ITestItem, ITestState, SerializedTestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; export interface PositionLike { line: number; @@ -596,7 +596,7 @@ export namespace WorkspaceEdit { editType: notebooks.CellEditType.OutputItems, index: entry.index, outputId: entry.outputId, - items: entry.newOutputItems?.map(item => ({ mime: item.mime, value: item.value, metadata: item.metadata })) || [], + items: entry.newOutputItems?.map(NotebookCellOutputItem.from) || [], append: entry.append } }); @@ -850,6 +850,66 @@ export namespace EvaluatableExpression { } } +export namespace InlineValue { + export function from(inlineValue: vscode.InlineValue): modes.InlineValue { + if (inlineValue instanceof types.InlineValueText) { + return { + type: 'text', + range: Range.from(inlineValue.range), + text: inlineValue.text + }; + } else if (inlineValue instanceof types.InlineValueVariableLookup) { + return { + type: 'variable', + range: Range.from(inlineValue.range), + variableName: inlineValue.variableName, + caseSensitiveLookup: inlineValue.caseSensitiveLookup + }; + } else if (inlineValue instanceof types.InlineValueEvaluatableExpression) { + return { + type: 'expression', + range: Range.from(inlineValue.range), + expression: inlineValue.expression + }; + } else { + throw new Error(`Unknown 'InlineValue' type`); + } + } + + export function to(inlineValue: modes.InlineValue): vscode.InlineValue { + switch (inlineValue.type) { + case 'text': + return { + range: Range.to(inlineValue.range), + text: inlineValue.text + }; + case 'variable': + return { + range: Range.to(inlineValue.range), + variableName: inlineValue.variableName, + caseSensitiveLookup: inlineValue.caseSensitiveLookup + }; + case 'expression': + return { + range: Range.to(inlineValue.range), + expression: inlineValue.expression + }; + } + } +} + +export namespace InlineValueContext { + export function from(inlineValueContext: vscode.InlineValueContext): extHostProtocol.IInlineValueContextDto { + return { + stoppedLocation: Range.from(inlineValueContext.stoppedLocation) + }; + } + + export function to(inlineValueContext: extHostProtocol.IInlineValueContextDto): types.InlineValueContext { + return new types.InlineValueContext(Range.to(inlineValueContext.stoppedLocation)); + } +} + export namespace DocumentHighlight { export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight { return { @@ -1334,11 +1394,12 @@ export namespace LanguageSelector { } else if (typeof selector === 'string') { return selector; } else { + const filter = selector as vscode.DocumentFilter; // TODO: microsoft/TypeScript#42768 return { - language: selector.language, - scheme: selector.scheme, - pattern: typeof selector.pattern === 'undefined' ? undefined : GlobPattern.from(selector.pattern), - exclusive: selector.exclusive + language: filter.language, + scheme: filter.scheme, + pattern: typeof filter.pattern === 'undefined' ? undefined : GlobPattern.from(filter.pattern), + exclusive: filter.exclusive }; } } @@ -1357,8 +1418,8 @@ export namespace NotebookCellRange { export namespace NotebookCellMetadata { - export function to(data: vscode.NotebookCellMetadata): types.NotebookCellMetadata { - return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.runnable, data.hasExecutionOrder, data.executionOrder, data.runStartTime, data.runStartTime, data.statusMessage, data.lastRunDuration, data.inputCollapsed, data.outputCollapsed, data.custom); + export function to(data: notebooks.NotebookCellMetadata): types.NotebookCellMetadata { + return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.runnable, data.hasExecutionOrder, data.executionOrder, data.runState, data.runStartTime, data.statusMessage, data.lastRunDuration, data.inputCollapsed, data.outputCollapsed, data.custom); } } @@ -1368,7 +1429,7 @@ export namespace NotebookDocumentMetadata { return data; } - export function to(data: vscode.NotebookDocumentMetadata): types.NotebookDocumentMetadata { + export function to(data: notebooks.NotebookDocumentMetadata): types.NotebookDocumentMetadata { return new types.NotebookDocumentMetadata(data.editable, data.runnable, data.cellEditable, data.cellRunnable, data.cellHasExecutionOrder, data.displayOrder, data.custom, data.runState, data.trusted); } @@ -1415,31 +1476,32 @@ export namespace NotebookCellData { } } +export namespace NotebookCellOutputItem { + export function from(item: types.NotebookCellOutputItem): notebooks.IOutputItemDto { + return { + mime: item.mime, + value: item.value, + metadata: item.metadata + }; + } + + export function to(item: notebooks.IOutputItemDto): types.NotebookCellOutputItem { + return new types.NotebookCellOutputItem(item.mime, item.value, item.metadata); + } +} + export namespace NotebookCellOutput { export function from(output: types.NotebookCellOutput): notebooks.IOutputDto { - - const data = Object.create(null); - const custom = Object.create(null); - - for (let item of output.outputs) { - data[item.mime] = item.value; - custom[item.mime] = item.metadata; - } - return { outputId: output.id, - outputs: (output.outputs || []).map(op => ({ - mime: op.mime, - value: op.value, - metadata: op.metadata - })) || [], - // metadata: isEmptyObject(custom) ? undefined : { custom } + outputs: output.outputs.map(NotebookCellOutputItem.from), + metadata: output.metadata }; } export function to(output: notebooks.IOutputDto): vscode.NotebookCellOutput { - const items: types.NotebookCellOutputItem[] = output.outputs.map(op => new types.NotebookCellOutputItem(op.mime, op.value, op.metadata)); - return new types.NotebookCellOutput(items, output.outputId); + const items = output.outputs.map(NotebookCellOutputItem.to); + return new types.NotebookCellOutput(items, output.outputId, output.metadata); } } @@ -1560,7 +1622,6 @@ export namespace TestState { } } - export namespace TestItem { export function from(item: vscode.TestItem, parentExtId?: string): ITestItem { return { @@ -1587,3 +1648,67 @@ export namespace TestItem { }; } } + +export namespace TestResults { + export function from(id: string, results: vscode.TestResults): ISerializedTestResults { + const serialized: ISerializedTestResults = { + completedAt: results.completedAt, + id, + items: [], + }; + + const queue: [parent: SerializedTestResultItem | null, children: Iterable][] = [ + [null, results.results], + ]; + + let counter = 0; + while (queue.length) { + const [parent, children] = queue.pop()!; + for (const item of children) { + const serializedItem: SerializedTestResultItem = { + children: item.children?.map(c => c.id) ?? [], + computedState: item.result.state, + id: `${id}-${counter++}`, + item: TestItem.from(item, parent?.item.extId), + state: TestState.from(item.result), + retired: undefined, + parent: parent?.id ?? null, + providerId: '', + direct: !parent, + }; + + serialized.items.push(serializedItem); + if (item.children) { + queue.push([serializedItem, item.children]); + } + } + } + + return serialized; + } + + const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map): vscode.TestItemWithResults => ({ + ...TestItem.toShallow(item.item), + result: TestState.to(item.state), + children: item.children + .map(c => byInternalId.get(c)) + .filter(isDefined) + .map(c => convertTestResultItem(c, byInternalId)), + }); + + export function to(serialized: ISerializedTestResults): vscode.TestResults { + const roots: SerializedTestResultItem[] = []; + const byInternalId = new Map(); + for (const item of serialized.items) { + byInternalId.set(item.id, item); + if (item.direct) { + roots.push(item); + } + } + + return { + completedAt: serialized.completedAt, + results: roots.map(r => convertTestResultItem(r, byInternalId)), + }; + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9bc85fa2ace..83a6151812e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -703,8 +703,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } private _editNotebookCellOutput(uri: URI, index: number, append: boolean, outputs: vscode.NotebookCellOutput[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { - let newOutputs: NotebookCellOutput[] = outputs; - this._edits.push({ _type: FileEditType.CellOutput, metadata, uri, index, append, newOutputs, newMetadata: undefined }); + this._edits.push({ _type: FileEditType.CellOutput, metadata, uri, index, append, newOutputs: outputs, newMetadata: undefined }); } replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -2439,6 +2438,51 @@ export class EvaluatableExpression implements vscode.EvaluatableExpression { } } +@es5ClassCompat +export class InlineValueText implements vscode.InlineValueText { + readonly range: Range; + readonly text: string; + + constructor(range: Range, text: string) { + this.range = range; + this.text = text; + } +} + +@es5ClassCompat +export class InlineValueVariableLookup implements vscode.InlineValueVariableLookup { + readonly range: Range; + readonly variableName?: string; + readonly caseSensitiveLookup: boolean; + + constructor(range: Range, variableName?: string, caseSensitiveLookup: boolean = true) { + this.range = range; + this.variableName = variableName; + this.caseSensitiveLookup = caseSensitiveLookup; + } +} + +@es5ClassCompat +export class InlineValueEvaluatableExpression implements vscode.InlineValueEvaluatableExpression { + readonly range: Range; + readonly expression?: string; + + constructor(range: Range, expression?: string) { + this.range = range; + this.expression = expression; + } +} + +@es5ClassCompat +export class InlineValueContext implements vscode.InlineValueContext { + + readonly stoppedLocation: vscode.Range; + + constructor(range: vscode.Range) { + this.stoppedLocation = range; + } +} + //#region file api export enum FileChangeType { @@ -2948,15 +2992,30 @@ export class NotebookCellOutputItem { constructor( readonly mime: string, readonly value: unknown, // JSON'able - readonly metadata?: any + readonly metadata?: Record ) { } } export class NotebookCellOutput { + + readonly outputs: NotebookCellOutputItem[]; + readonly id: string; + readonly metadata?: Record; + constructor( - readonly outputs: NotebookCellOutputItem[], - readonly id: string = generateUuid() - ) { } + outputs: NotebookCellOutputItem[], + idOrMetadata?: string | Record, + metadata?: Record + ) { + this.outputs = outputs; + if (typeof idOrMetadata === 'string') { + this.id = idOrMetadata; + this.metadata = metadata; + } else { + this.id = generateUuid(); + this.metadata = idOrMetadata ?? metadata; + } + } } export enum NotebookCellKind { diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 81527dad3af..5f0723ff7c6 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -46,8 +46,7 @@ const apiMenus: IAPIMenu[] = [ { key: 'editor/title/run', id: MenuId.EditorTitleRun, - description: localize('menus.editorTitleRun', "Run submenu inside the editor title menu"), - proposed: true + description: localize('menus.editorTitleRun', "Run submenu inside the editor title menu") }, { key: 'editor/context', @@ -167,6 +166,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('notebook.cell.title', "The contributed notebook cell title menu"), proposed: true }, + { + key: 'testing/item/context', + id: MenuId.TestItem, + description: localize('testing.item.title', "The contributed test item menu"), + proposed: true + }, { key: 'extension/context', id: MenuId.ExtensionContext, diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index f34c09b84ab..87ea9940af0 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -11,8 +11,7 @@ import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/termi import { IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration, ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IShellLaunchConfig, ITerminalEnvironment, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; +import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; @@ -26,6 +25,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { withNullAsUndefined } from 'vs/base/common/types'; import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell'; import { generateUuid } from 'vs/base/common/uuid'; +import { IShellLaunchConfig, ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; export class ExtHostTerminalService extends BaseExtHostTerminalService { diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index c0cc5d43940..9b9dd79dc9c 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -196,6 +196,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { if (provider) { + if (provider.candidatePortSource !== undefined) { + await this._proxy.$setCandidatePortSource(provider.candidatePortSource); + } if (provider.showCandidatePort) { this._showCandidatePort = provider.showCandidatePort; await this._proxy.$setCandidateFilter(); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 9eb0d3ea714..94160516029 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/actions'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { domEvent } from 'vs/base/browser/event'; import { Color } from 'vs/base/common/color'; @@ -34,7 +34,7 @@ class InspectContextKeysAction extends Action2 { constructor() { super({ id: 'workbench.action.inspectContextKeys', - title: { value: nls.localize('inspect context keys', "Inspect Context Keys"), original: 'Inspect Context Keys' }, + title: { value: localize('inspect context keys', "Inspect Context Keys"), original: 'Inspect Context Keys' }, category: CATEGORIES.Developer, f1: true }); @@ -96,7 +96,7 @@ class ToggleScreencastModeAction extends Action2 { constructor() { super({ id: 'workbench.action.toggleScreencastMode', - title: { value: nls.localize('toggle screencast mode', "Toggle Screencast Mode"), original: 'Toggle Screencast Mode' }, + title: { value: localize('toggle screencast mode', "Toggle Screencast Mode"), original: 'Toggle Screencast Mode' }, category: CATEGORIES.Developer, f1: true }); @@ -241,7 +241,7 @@ class LogStorageAction extends Action2 { constructor() { super({ id: 'workbench.action.logStorage', - title: { value: nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"), original: 'Log Storage Database Contents' }, + title: { value: localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"), original: 'Log Storage Database Contents' }, category: CATEGORIES.Developer, f1: true }); @@ -257,7 +257,7 @@ class LogWorkingCopiesAction extends Action2 { constructor() { super({ id: 'workbench.action.logWorkingCopies', - title: { value: nls.localize({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"), original: 'Log Working Copies' }, + title: { value: localize({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"), original: 'Log Working Copies' }, category: CATEGORIES.Developer, f1: true }); @@ -291,7 +291,7 @@ const configurationRegistry = Registry.as(ConfigurationE configurationRegistry.registerConfiguration({ id: 'screencastMode', order: 9, - title: nls.localize('screencastModeConfigurationTitle', "Screencast Mode"), + title: localize('screencastModeConfigurationTitle', "Screencast Mode"), type: 'object', properties: { 'screencastMode.verticalOffset': { @@ -299,18 +299,18 @@ configurationRegistry.registerConfiguration({ default: 20, minimum: 0, maximum: 90, - description: nls.localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.") + description: localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.") }, 'screencastMode.fontSize': { type: 'number', default: 56, minimum: 20, maximum: 100, - description: nls.localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.") + description: localize('screencastMode.fontSize', "Controls the font size (in pixels) of the screencast mode keyboard.") }, 'screencastMode.onlyKeyboardShortcuts': { type: 'boolean', - description: nls.localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in screencast mode."), + description: localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in screencast mode."), default: false }, 'screencastMode.keyboardOverlayTimeout': { @@ -318,20 +318,20 @@ configurationRegistry.registerConfiguration({ default: 800, minimum: 500, maximum: 5000, - description: nls.localize('screencastMode.keyboardOverlayTimeout', "Controls how long (in milliseconds) the keyboard overlay is shown in screencast mode.") + description: localize('screencastMode.keyboardOverlayTimeout', "Controls how long (in milliseconds) the keyboard overlay is shown in screencast mode.") }, 'screencastMode.mouseIndicatorColor': { type: 'string', format: 'color-hex', default: '#FF0000', - description: nls.localize('screencastMode.mouseIndicatorColor', "Controls the color in hex (#RGB, #RGBA, #RRGGBB or #RRGGBBAA) of the mouse indicator in screencast mode.") + description: localize('screencastMode.mouseIndicatorColor', "Controls the color in hex (#RGB, #RGBA, #RRGGBB or #RRGGBBAA) of the mouse indicator in screencast mode.") }, 'screencastMode.mouseIndicatorSize': { type: 'number', default: 20, minimum: 20, maximum: 100, - description: nls.localize('screencastMode.mouseIndicatorSize', "Controls the size (in pixels) of the mouse indicator in screencast mode.") + description: localize('screencastMode.mouseIndicatorSize', "Controls the size (in pixels) of the mouse indicator in screencast mode.") }, } }); diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index e12b61219f2..fccf6072614 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import product from 'vs/platform/product/common/product'; import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -24,7 +24,7 @@ class KeybindingsReferenceAction extends Action2 { constructor() { super({ id: KeybindingsReferenceAction.ID, - title: { value: nls.localize('keybindingsReference', "Keyboard Shortcuts Reference"), original: 'Keyboard Shortcuts Reference' }, + title: { value: localize('keybindingsReference', "Keyboard Shortcuts Reference"), original: 'Keyboard Shortcuts Reference' }, category: CATEGORIES.Help, f1: true, keybinding: { @@ -54,7 +54,7 @@ class OpenDocumentationUrlAction extends Action2 { constructor() { super({ id: OpenDocumentationUrlAction.ID, - title: { value: nls.localize('openDocumentationUrl', "Documentation"), original: 'Documentation' }, + title: { value: localize('openDocumentationUrl', "Documentation"), original: 'Documentation' }, category: CATEGORIES.Help, f1: true }); @@ -78,7 +78,7 @@ class OpenIntroductoryVideosUrlAction extends Action2 { constructor() { super({ id: OpenIntroductoryVideosUrlAction.ID, - title: { value: nls.localize('openIntroductoryVideosUrl', "Introductory Videos"), original: 'Introductory Videos' }, + title: { value: localize('openIntroductoryVideosUrl', "Introductory Videos"), original: 'Introductory Videos' }, category: CATEGORIES.Help, f1: true }); @@ -102,7 +102,7 @@ class OpenTipsAndTricksUrlAction extends Action2 { constructor() { super({ id: OpenTipsAndTricksUrlAction.ID, - title: { value: nls.localize('openTipsAndTricksUrl', "Tips and Tricks"), original: 'Tips and Tricks' }, + title: { value: localize('openTipsAndTricksUrl', "Tips and Tricks"), original: 'Tips and Tricks' }, category: CATEGORIES.Help, f1: true }); @@ -126,7 +126,7 @@ class OpenNewsletterSignupUrlAction extends Action2 { constructor() { super({ id: OpenNewsletterSignupUrlAction.ID, - title: { value: nls.localize('newsletterSignup', "Signup for the VS Code Newsletter"), original: 'Signup for the VS Code Newsletter' }, + title: { value: localize('newsletterSignup', "Signup for the VS Code Newsletter"), original: 'Signup for the VS Code Newsletter' }, category: CATEGORIES.Help, f1: true }); @@ -151,7 +151,7 @@ class OpenTwitterUrlAction extends Action2 { constructor() { super({ id: OpenTwitterUrlAction.ID, - title: { value: nls.localize('openTwitterUrl', "Join Us on Twitter"), original: 'Join Us on Twitter' }, + title: { value: localize('openTwitterUrl', "Join Us on Twitter"), original: 'Join Us on Twitter' }, category: CATEGORIES.Help, f1: true }); @@ -175,7 +175,7 @@ class OpenRequestFeatureUrlAction extends Action2 { constructor() { super({ id: OpenRequestFeatureUrlAction.ID, - title: { value: nls.localize('openUserVoiceUrl', "Search Feature Requests"), original: 'Search Feature Requests' }, + title: { value: localize('openUserVoiceUrl', "Search Feature Requests"), original: 'Search Feature Requests' }, category: CATEGORIES.Help, f1: true }); @@ -199,7 +199,7 @@ class OpenLicenseUrlAction extends Action2 { constructor() { super({ id: OpenLicenseUrlAction.ID, - title: { value: nls.localize('openLicenseUrl', "View License"), original: 'View License' }, + title: { value: localize('openLicenseUrl', "View License"), original: 'View License' }, category: CATEGORIES.Help, f1: true }); @@ -228,7 +228,7 @@ class OpenPrivacyStatementUrlAction extends Action2 { constructor() { super({ id: OpenPrivacyStatementUrlAction.ID, - title: { value: nls.localize('openPrivacyStatement', "Privacy Statement"), original: 'Privacy Statement' }, + title: { value: localize('openPrivacyStatement', "Privacy Statement"), original: 'Privacy Statement' }, category: CATEGORIES.Help, f1: true }); @@ -296,7 +296,7 @@ if (OpenDocumentationUrlAction.AVAILABLE) { group: '1_welcome', command: { id: OpenDocumentationUrlAction.ID, - title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") + title: localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") }, order: 3 }); @@ -308,7 +308,7 @@ if (KeybindingsReferenceAction.AVAILABLE) { group: '2_reference', command: { id: KeybindingsReferenceAction.ID, - title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") + title: localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") }, order: 1 }); @@ -319,7 +319,7 @@ if (OpenIntroductoryVideosUrlAction.AVAILABLE) { group: '2_reference', command: { id: OpenIntroductoryVideosUrlAction.ID, - title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") + title: localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") }, order: 2 }); @@ -330,7 +330,7 @@ if (OpenTipsAndTricksUrlAction.AVAILABLE) { group: '2_reference', command: { id: OpenTipsAndTricksUrlAction.ID, - title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") + title: localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") }, order: 3 }); @@ -342,7 +342,7 @@ if (OpenTwitterUrlAction.AVAILABLE) { group: '3_feedback', command: { id: OpenTwitterUrlAction.ID, - title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") + title: localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") }, order: 1 }); @@ -353,7 +353,7 @@ if (OpenRequestFeatureUrlAction.AVAILABLE) { group: '3_feedback', command: { id: OpenRequestFeatureUrlAction.ID, - title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") + title: localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") }, order: 2 }); @@ -365,7 +365,7 @@ if (OpenLicenseUrlAction.AVAILABLE) { group: '4_legal', command: { id: OpenLicenseUrlAction.ID, - title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") + title: localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") }, order: 1 }); @@ -376,7 +376,7 @@ if (OpenPrivacyStatementUrlAction.AVAILABE) { group: '4_legal', command: { id: OpenPrivacyStatementUrlAction.ID, - title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") + title: localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") }, order: 2 }); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 9bb28af606c..2f3c7c31c6a 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; @@ -33,7 +33,7 @@ class CloseSidebarAction extends Action2 { constructor() { super({ id: 'workbench.action.closeSidebar', - title: { value: nls.localize('closeSidebar', "Close Side Bar"), original: 'Close Side Bar' }, + title: { value: localize('closeSidebar', "Close Side Bar"), original: 'Close Side Bar' }, category: CATEGORIES.View, f1: true }); @@ -51,7 +51,7 @@ registerAction2(CloseSidebarAction); export class ToggleActivityBarVisibilityAction extends Action2 { static readonly ID = 'workbench.action.toggleActivityBarVisibility'; - static readonly LABEL = nls.localize('toggleActivityBar', "Toggle Activity Bar Visibility"); + static readonly LABEL = localize('toggleActivityBar', "Toggle Activity Bar Visibility"); private static readonly activityBarVisibleKey = 'workbench.activityBar.visible'; @@ -81,7 +81,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleActivityBarVisibilityAction.ID, - title: nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"), + title: localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"), toggled: ContextKeyExpr.equals('config.workbench.activityBar.visible', true) }, order: 4 @@ -96,7 +96,7 @@ class ToggleCenteredLayout extends Action2 { constructor() { super({ id: ToggleCenteredLayout.ID, - title: { value: nls.localize('toggleCenteredLayout', "Toggle Centered Layout"), original: 'Toggle Centered Layout' }, + title: { value: localize('toggleCenteredLayout', "Toggle Centered Layout"), original: 'Toggle Centered Layout' }, category: CATEGORIES.View, f1: true }); @@ -115,7 +115,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', command: { id: ToggleCenteredLayout.ID, - title: nls.localize({ key: 'miToggleCenteredLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered Layout"), + title: localize({ key: 'miToggleCenteredLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered Layout"), toggled: IsCenteredLayoutContext }, order: 3 @@ -126,7 +126,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export class ToggleSidebarPositionAction extends Action { static readonly ID = 'workbench.action.toggleSidebarPosition'; - static readonly LABEL = nls.localize('toggleSidebarPosition', "Toggle Side Bar Position"); + static readonly LABEL = localize('toggleSidebarPosition', "Toggle Side Bar Position"); private static readonly sidebarPositionConfigurationKey = 'workbench.sideBar.location'; @@ -147,7 +147,7 @@ export class ToggleSidebarPositionAction extends Action { } static getLabel(layoutService: IWorkbenchLayoutService): string { - return layoutService.getSideBarPosition() === Position.LEFT ? nls.localize('moveSidebarRight', "Move Side Bar Right") : nls.localize('moveSidebarLeft', "Move Side Bar Left"); + return layoutService.getSideBarPosition() === Position.LEFT ? localize('moveSidebarRight', "Move Side Bar Right") : localize('moveSidebarLeft', "Move Side Bar Left"); } } @@ -155,7 +155,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: ToggleSidebarPositionAction.ID, - title: { value: nls.localize('toggleSidebarPosition', "Toggle Side Bar Position"), original: 'Toggle Side Bar Position' }, + title: { value: localize('toggleSidebarPosition', "Toggle Side Bar Position"), original: 'Toggle Side Bar Position' }, category: CATEGORIES.View, f1: true }); @@ -170,7 +170,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize('move sidebar right', "Move Side Bar Right") + title: localize('move sidebar right', "Move Side Bar Right") }, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 1 @@ -181,7 +181,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize('move sidebar right', "Move Side Bar Right") + title: localize('move sidebar right', "Move Side Bar Right") }, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 1 @@ -192,7 +192,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize('move sidebar left', "Move Side Bar Left") + title: localize('move sidebar left', "Move Side Bar Left") }, when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 1 @@ -203,7 +203,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize('move sidebar left', "Move Side Bar Left") + title: localize('move sidebar left', "Move Side Bar Left") }, when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 1 @@ -214,7 +214,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right") + title: localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right") }, when: ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), order: 2 @@ -224,7 +224,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left") + title: localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left") }, when: ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), order: 2 @@ -234,7 +234,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export class ToggleEditorVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleEditorVisibility'; - static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area Visibility"); + static readonly LABEL = localize('toggleEditor', "Toggle Editor Area Visibility"); constructor( id: string, @@ -255,7 +255,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleEditorVisibilityAction.ID, - title: nls.localize({ key: 'miShowEditorArea', comment: ['&& denotes a mnemonic'] }, "Show &&Editor Area"), + title: localize({ key: 'miShowEditorArea', comment: ['&& denotes a mnemonic'] }, "Show &&Editor Area"), toggled: EditorAreaVisibleContext }, order: 5 @@ -263,7 +263,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '2_appearance', - title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), + title: localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), submenu: MenuId.MenubarAppearanceMenu, order: 1 }); @@ -273,7 +273,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, - title: { value: nls.localize('toggleSidebar', "Toggle Side Bar Visibility"), original: 'Toggle Side Bar Visibility' }, + title: { value: localize('toggleSidebar', "Toggle Side Bar Visibility"), original: 'Toggle Side Bar Visibility' }, category: CATEGORIES.View, f1: true, keybinding: { @@ -293,7 +293,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, - title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), + title: localize('compositePart.hideSideBarLabel', "Hide Side Bar"), }, when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 2 @@ -304,7 +304,7 @@ MenuRegistry.appendMenuItems([{ group: '3_workbench_layout_move', command: { id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, - title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), + title: localize('compositePart.hideSideBarLabel', "Hide Side Bar"), }, when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))), order: 2 @@ -315,7 +315,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID, - title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"), + title: localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"), toggled: SideBarVisibleContext }, order: 1 @@ -326,7 +326,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export class ToggleStatusbarVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleStatusbarVisibility'; - static readonly LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility"); + static readonly LABEL = localize('toggleStatusbar', "Toggle Status Bar Visibility"); private static readonly statusbarVisibleKey = 'workbench.statusBar.visible'; @@ -353,7 +353,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleStatusbarVisibilityAction.ID, - title: nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "Show S&&tatus Bar"), + title: localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "Show S&&tatus Bar"), toggled: ContextKeyExpr.equals('config.workbench.statusBar.visible', true) }, order: 3 @@ -364,7 +364,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { class ToggleTabsVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleTabsVisibility'; - static readonly LABEL = nls.localize('toggleTabs', "Toggle Tab Visibility"); + static readonly LABEL = localize('toggleTabs', "Toggle Tab Visibility"); private static readonly tabsVisibleKey = 'workbench.editor.showTabs'; @@ -395,7 +395,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleTabsVisibilityA class ToggleZenMode extends Action { static readonly ID = 'workbench.action.toggleZenMode'; - static readonly LABEL = nls.localize('toggleZenMode', "Toggle Zen Mode"); + static readonly LABEL = localize('toggleZenMode', "Toggle Zen Mode"); constructor( id: string, @@ -416,7 +416,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', command: { id: ToggleZenMode.ID, - title: nls.localize('miToggleZenMode', "Zen Mode"), + title: localize('miToggleZenMode', "Zen Mode"), toggled: InEditorZenModeContext }, order: 2 @@ -438,7 +438,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ export class ToggleMenuBarAction extends Action { static readonly ID = 'workbench.action.toggleMenuBar'; - static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar"); + static readonly LABEL = localize('toggleMenuBar', "Toggle Menu Bar"); constructor( id: string, @@ -461,7 +461,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleMenuBarAction.ID, - title: nls.localize({ key: 'miShowMenuBar', comment: ['&& denotes a mnemonic'] }, "Show Menu &&Bar"), + title: localize({ key: 'miShowMenuBar', comment: ['&& denotes a mnemonic'] }, "Show Menu &&Bar"), toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) }, when: IsMacNativeContext.toNegated(), @@ -472,7 +472,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export class ResetViewLocationsAction extends Action { static readonly ID = 'workbench.action.resetViewLocations'; - static readonly LABEL = nls.localize('resetViewLocations', "Reset View Locations"); + static readonly LABEL = localize('resetViewLocations', "Reset View Locations"); constructor( id: string, @@ -492,7 +492,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetViewLocationsAct // --- Move View with Command export class MoveViewAction extends Action { static readonly ID = 'workbench.action.moveView'; - static readonly LABEL = nls.localize('moveView', "Move View"); + static readonly LABEL = localize('moveView', "Move View"); constructor( id: string, @@ -521,7 +521,7 @@ export class MoveViewAction extends Action { if (!hasAddedView) { results.push({ type: 'separator', - label: nls.localize('sidebarContainer', "Side Bar / {0}", containerModel.title) + label: localize('sidebarContainer', "Side Bar / {0}", containerModel.title) }); hasAddedView = true; } @@ -545,7 +545,7 @@ export class MoveViewAction extends Action { if (!hasAddedView) { results.push({ type: 'separator', - label: nls.localize('panelContainer', "Panel / {0}", containerModel.title) + label: localize('panelContainer', "Panel / {0}", containerModel.title) }); hasAddedView = true; } @@ -563,7 +563,7 @@ export class MoveViewAction extends Action { private async getView(viewId?: string): Promise { const quickPick = this.quickInputService.createQuickPick(); - quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Select a View to Move"); + quickPick.placeholder = localize('moveFocusedView.selectView', "Select a View to Move"); quickPick.items = this.getViewItems(); quickPick.selectedItems = quickPick.items.filter(item => (item as IQuickPickItem).id === viewId) as IQuickPickItem[]; @@ -608,7 +608,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveViewAction), 'Vie // --- Move Focused View with Command export class MoveFocusedViewAction extends Action { static readonly ID = 'workbench.action.moveFocusedView'; - static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View"); + static readonly LABEL = localize('moveFocusedView', "Move Focused View"); constructor( id: string, @@ -628,19 +628,19 @@ export class MoveFocusedViewAction extends Action { const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService); if (focusedViewId === undefined || focusedViewId.trim() === '') { - this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); + this.notificationService.error(localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); return; } const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(focusedViewId); if (!viewDescriptor || !viewDescriptor.canMoveView) { - this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable.")); + this.notificationService.error(localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable.")); return; } const quickPick = this.quickInputService.createQuickPick(); - quickPick.placeholder = nls.localize('moveFocusedView.selectDestination', "Select a Destination for the View"); - quickPick.title = nls.localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name); + quickPick.placeholder = localize('moveFocusedView.selectDestination', "Select a Destination for the View"); + quickPick.title = localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name); const items: Array = []; const currentContainer = this.viewDescriptorService.getViewContainerByViewId(focusedViewId)!; @@ -650,20 +650,20 @@ export class MoveFocusedViewAction extends Action { if (!(isViewSolo && currentLocation === ViewContainerLocation.Panel)) { items.push({ id: '_.panel.newcontainer', - label: nls.localize({ key: 'moveFocusedView.newContainerInPanel', comment: ['Creates a new top-level tab in the panel.'] }, "New Panel Entry"), + label: localize({ key: 'moveFocusedView.newContainerInPanel', comment: ['Creates a new top-level tab in the panel.'] }, "New Panel Entry"), }); } if (!(isViewSolo && currentLocation === ViewContainerLocation.Sidebar)) { items.push({ id: '_.sidebar.newcontainer', - label: nls.localize('moveFocusedView.newContainerInSidebar', "New Side Bar Entry") + label: localize('moveFocusedView.newContainerInSidebar', "New Side Bar Entry") }); } items.push({ type: 'separator', - label: nls.localize('sidebar', "Side Bar") + label: localize('sidebar', "Side Bar") }); const pinnedViewlets = this.activityBarService.getVisibleViewContainerIds(); @@ -684,7 +684,7 @@ export class MoveFocusedViewAction extends Action { items.push({ type: 'separator', - label: nls.localize('panel', "Panel") + label: localize('panel', "Panel") }); const pinnedPanels = this.panelService.getPinnedPanels(); @@ -731,7 +731,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveFocusedViewAction // --- Reset View Location with Command export class ResetFocusedViewLocationAction extends Action { static readonly ID = 'workbench.action.resetFocusedViewLocation'; - static readonly LABEL = nls.localize('resetFocusedViewLocation', "Reset Focused View Location"); + static readonly LABEL = localize('resetFocusedViewLocation', "Reset Focused View Location"); constructor( id: string, @@ -753,7 +753,7 @@ export class ResetFocusedViewLocationAction extends Action { } if (!viewDescriptor) { - this.notificationService.error(nls.localize('resetFocusedView.error.noFocusedView', "There is no view currently focused.")); + this.notificationService.error(localize('resetFocusedView.error.noFocusedView', "There is no view currently focused.")); return; } @@ -807,7 +807,7 @@ export class IncreaseViewSizeAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.increaseViewSize', - title: { value: nls.localize('increaseViewSize', "Increase Current View Size"), original: 'Increase Current View Size' }, + title: { value: localize('increaseViewSize', "Increase Current View Size"), original: 'Increase Current View Size' }, f1: true }); } @@ -822,7 +822,7 @@ export class IncreaseViewWidthAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.increaseViewWidth', - title: { value: nls.localize('increaseEditorWidth', "Increase Editor Width"), original: 'Increase Editor Width' }, + title: { value: localize('increaseEditorWidth', "Increase Editor Width"), original: 'Increase Editor Width' }, f1: true }); } @@ -837,7 +837,7 @@ export class IncreaseViewHeightAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.increaseViewHeight', - title: { value: nls.localize('increaseEditorHeight', "Increase Editor Height"), original: 'Increase Editor Height' }, + title: { value: localize('increaseEditorHeight', "Increase Editor Height"), original: 'Increase Editor Height' }, f1: true }); } @@ -851,7 +851,7 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.decreaseViewSize', - title: { value: nls.localize('decreaseViewSize', "Decrease Current View Size"), original: 'Decrease Current View Size' }, + title: { value: localize('decreaseViewSize', "Decrease Current View Size"), original: 'Decrease Current View Size' }, f1: true }); } @@ -865,7 +865,7 @@ export class DecreaseViewWidthAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.decreaseViewWidth', - title: { value: nls.localize('decreaseEditorWidth', "Decrease Editor Width"), original: 'Decrease Editor Width' }, + title: { value: localize('decreaseEditorWidth', "Decrease Editor Width"), original: 'Decrease Editor Width' }, f1: true }); } @@ -881,7 +881,7 @@ export class DecreaseViewHeightAction extends BaseResizeViewAction { constructor() { super({ id: 'workbench.action.decreaseViewHeight', - title: { value: nls.localize('decreaseEditorHeight', "Decrease Editor Height"), original: 'Decrease Editor Height' }, + title: { value: localize('decreaseEditorHeight', "Decrease Editor Height"), original: 'Decrease Editor Height' }, f1: true }); } diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 01831676a97..6122d65d983 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -137,7 +137,7 @@ abstract class BaseNavigationAction extends Action { class NavigateLeftAction extends BaseNavigationAction { static readonly ID = 'workbench.action.navigateLeft'; - static readonly LABEL = nls.localize('navigateLeft', "Navigate to the View on the Left"); + static readonly LABEL = localize('navigateLeft', "Navigate to the View on the Left"); constructor( id: string, @@ -154,7 +154,7 @@ class NavigateLeftAction extends BaseNavigationAction { class NavigateRightAction extends BaseNavigationAction { static readonly ID = 'workbench.action.navigateRight'; - static readonly LABEL = nls.localize('navigateRight', "Navigate to the View on the Right"); + static readonly LABEL = localize('navigateRight', "Navigate to the View on the Right"); constructor( id: string, @@ -171,7 +171,7 @@ class NavigateRightAction extends BaseNavigationAction { class NavigateUpAction extends BaseNavigationAction { static readonly ID = 'workbench.action.navigateUp'; - static readonly LABEL = nls.localize('navigateUp', "Navigate to the View Above"); + static readonly LABEL = localize('navigateUp', "Navigate to the View Above"); constructor( id: string, @@ -188,7 +188,7 @@ class NavigateUpAction extends BaseNavigationAction { class NavigateDownAction extends BaseNavigationAction { static readonly ID = 'workbench.action.navigateDown'; - static readonly LABEL = nls.localize('navigateDown', "Navigate to the View Below"); + static readonly LABEL = localize('navigateDown', "Navigate to the View Below"); constructor( id: string, @@ -229,7 +229,7 @@ function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorS export class FocusNextPart extends Action { static readonly ID = 'workbench.action.focusNextPart'; - static readonly LABEL = nls.localize('focusNextPart', "Focus Next Part"); + static readonly LABEL = localize('focusNextPart', "Focus Next Part"); constructor( id: string, @@ -247,7 +247,7 @@ export class FocusNextPart extends Action { export class FocusPreviousPart extends Action { static readonly ID = 'workbench.action.focusPreviousPart'; - static readonly LABEL = nls.localize('focusPreviousPart', "Focus Previous Part"); + static readonly LABEL = localize('focusPreviousPart', "Focus Previous Part"); constructor( id: string, diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 926527871e8..758a5b12d3c 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -46,18 +46,18 @@ abstract class BaseOpenRecentAction extends Action { private readonly removeFromRecentlyOpened: IQuickInputButton = { iconClass: Codicon.removeClose.classNames, - tooltip: nls.localize('remove', "Remove from Recently Opened") + tooltip: localize('remove', "Remove from Recently Opened") }; private readonly dirtyRecentlyOpenedFolder: IQuickInputButton = { iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames, - tooltip: nls.localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"), + tooltip: localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"), alwaysVisible: true }; private readonly dirtyRecentlyOpenedWorkspace: IQuickInputButton = { ...this.dirtyRecentlyOpenedFolder, - tooltip: nls.localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"), + tooltip: localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"), }; constructor( @@ -133,14 +133,14 @@ abstract class BaseOpenRecentAction extends Action { let keyMods: IKeyMods | undefined; - const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? nls.localize('workspacesAndFolders', "folders & workspaces") : nls.localize('folders', "folders") }; - const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") }; + const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? localize('workspacesAndFolders', "folders & workspaces") : localize('folders', "folders") }; + const fileSeparator: IQuickPickSeparator = { type: 'separator', label: localize('files', "files") }; const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks]; const pick = await this.quickInputService.pick(picks, { contextKey: inRecentFilesPickerContextKey, activeItem: [...workspacePicks, ...filePicks][autoFocusSecondEntry ? 1 : 0], - placeHolder: isMacintosh ? nls.localize('openRecentPlaceholderMac', "Select to open (hold Cmd-key to force new window or Alt-key for same window)") : nls.localize('openRecentPlaceholder', "Select to open (hold Ctrl-key to force new window or Alt-key for same window)"), + placeHolder: isMacintosh ? localize('openRecentPlaceholderMac', "Select to open (hold Cmd-key to force new window or Alt-key for same window)") : localize('openRecentPlaceholder', "Select to open (hold Ctrl-key to force new window or Alt-key for same window)"), matchOnDescription: true, onKeyMods: mods => keyMods = mods, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, @@ -157,9 +157,9 @@ abstract class BaseOpenRecentAction extends Action { const isDirtyWorkspace = context.button === this.dirtyRecentlyOpenedWorkspace; const result = await this.dialogService.confirm({ type: 'question', - title: isDirtyWorkspace ? nls.localize('dirtyWorkspace', "Workspace with Unsaved Files") : nls.localize('dirtyFolder', "Folder with Unsaved Files"), - message: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : nls.localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"), - detail: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : nls.localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.") + title: isDirtyWorkspace ? localize('dirtyWorkspace', "Workspace with Unsaved Files") : localize('dirtyFolder', "Folder with Unsaved Files"), + message: isDirtyWorkspace ? localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"), + detail: isDirtyWorkspace ? localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.") }); if (result.confirmed) { @@ -212,7 +212,7 @@ abstract class BaseOpenRecentAction extends Action { return { iconClasses, label: name, - ariaLabel: isDirty ? isWorkspace ? nls.localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : nls.localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name, + ariaLabel: isDirty ? isWorkspace ? localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name, description: parentPath, buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened], openable, @@ -224,7 +224,7 @@ abstract class BaseOpenRecentAction extends Action { export class OpenRecentAction extends BaseOpenRecentAction { static readonly ID = 'workbench.action.openRecent'; - static readonly LABEL = nls.localize('openRecent', "Open Recent..."); + static readonly LABEL = localize('openRecent', "Open Recent..."); constructor( id: string, @@ -250,7 +250,7 @@ export class OpenRecentAction extends BaseOpenRecentAction { class QuickPickRecentAction extends BaseOpenRecentAction { static readonly ID = 'workbench.action.quickOpenRecent'; - static readonly LABEL = nls.localize('quickOpenRecent', "Quick Open Recent..."); + static readonly LABEL = localize('quickOpenRecent', "Quick Open Recent..."); constructor( id: string, @@ -276,7 +276,7 @@ class QuickPickRecentAction extends BaseOpenRecentAction { class ToggleFullScreenAction extends Action { static readonly ID = 'workbench.action.toggleFullScreen'; - static readonly LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); + static readonly LABEL = localize('toggleFullScreen', "Toggle Full Screen"); constructor( id: string, @@ -294,7 +294,7 @@ class ToggleFullScreenAction extends Action { export class ReloadWindowAction extends Action { static readonly ID = 'workbench.action.reloadWindow'; - static readonly LABEL = nls.localize('reloadWindow', "Reload Window"); + static readonly LABEL = localize('reloadWindow', "Reload Window"); constructor( id: string, @@ -314,7 +314,7 @@ export class ReloadWindowAction extends Action { class ShowAboutDialogAction extends Action { static readonly ID = 'workbench.action.showAboutDialog'; - static readonly LABEL = nls.localize('about', "About"); + static readonly LABEL = localize('about', "About"); constructor( id: string, @@ -332,7 +332,7 @@ class ShowAboutDialogAction extends Action { export class NewWindowAction extends Action { static readonly ID = 'workbench.action.newWindow'; - static readonly LABEL = nls.localize('newWindow', "New Window"); + static readonly LABEL = localize('newWindow', "New Window"); constructor( id: string, @@ -352,7 +352,7 @@ class BlurAction extends Action2 { constructor() { super({ id: 'workbench.action.blur', - title: nls.localize('blur', "Remove keyboard focus from focused element") + title: localize('blur', "Remove keyboard focus from focused element") }); } @@ -369,7 +369,7 @@ const registry = Registry.as(Extensions.WorkbenchActio // --- Actions Registration -const fileCategory = nls.localize('file', "File"); +const fileCategory = localize('file', "File"); registry.registerWorkbenchAction(SyncActionDescriptor.from(NewWindowAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickPickRecentAction), 'File: Quick Open Recent...', fileCategory); registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenRecentAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); @@ -426,7 +426,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: 'z_ConfirmClose', command: { id: 'workbench.action.toggleConfirmBeforeClose', - title: nls.localize('miConfirmClose', "Confirm Before Close"), + title: localize('miConfirmClose', "Confirm Before Close"), toggled: ContextKeyExpr.notEquals('config.window.confirmBeforeClose', 'never') }, order: 1, @@ -437,13 +437,13 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '1_new', command: { id: NewWindowAction.ID, - title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") + title: localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") }, order: 2 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), + title: localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), submenu: MenuId.MenubarRecentMenu, group: '2_open', order: 4 @@ -453,7 +453,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { group: 'y_more', command: { id: OpenRecentAction.ID, - title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") + title: localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") }, order: 1 }); @@ -462,7 +462,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', command: { id: ToggleFullScreenAction.ID, - title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"), + title: localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"), toggled: IsFullscreenContext }, order: 1 @@ -472,7 +472,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: 'z_about', command: { id: ShowAboutDialogAction.ID, - title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") + title: localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") }, order: 1, when: IsMacNativeContext.toNegated() diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 713e088b96f..03628a38b89 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -23,12 +23,12 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; -import { WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { WORKSPACE_TRUST_ENABLED } from 'vs/workbench/services/workspaces/common/workspaceTrust'; export class OpenFileAction extends Action { static readonly ID = 'workbench.action.files.openFile'; - static readonly LABEL = nls.localize('openFile', "Open File..."); + static readonly LABEL = localize('openFile', "Open File..."); constructor( id: string, @@ -46,7 +46,7 @@ export class OpenFileAction extends Action { export class OpenFolderAction extends Action { static readonly ID = 'workbench.action.files.openFolder'; - static readonly LABEL = nls.localize('openFolder', "Open Folder..."); + static readonly LABEL = localize('openFolder', "Open Folder..."); constructor( id: string, @@ -64,7 +64,7 @@ export class OpenFolderAction extends Action { export class OpenFileFolderAction extends Action { static readonly ID = 'workbench.action.files.openFileFolder'; - static readonly LABEL = nls.localize('openFileFolder', "Open..."); + static readonly LABEL = localize('openFileFolder', "Open..."); constructor( id: string, @@ -82,7 +82,7 @@ export class OpenFileFolderAction extends Action { export class OpenWorkspaceAction extends Action { static readonly ID = 'workbench.action.openWorkspace'; - static readonly LABEL = nls.localize('openWorkspaceAction', "Open Workspace..."); + static readonly LABEL = localize('openWorkspaceAction', "Open Workspace..."); constructor( id: string, @@ -100,7 +100,7 @@ export class OpenWorkspaceAction extends Action { export class CloseWorkspaceAction extends Action { static readonly ID = 'workbench.action.closeFolder'; - static readonly LABEL = nls.localize('closeWorkspace', "Close Workspace"); + static readonly LABEL = localize('closeWorkspace', "Close Workspace"); constructor( id: string, @@ -115,7 +115,7 @@ export class CloseWorkspaceAction extends Action { async run(): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - this.notificationService.info(nls.localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close.")); + this.notificationService.info(localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close.")); return; } @@ -126,7 +126,7 @@ export class CloseWorkspaceAction extends Action { export class OpenWorkspaceConfigFileAction extends Action { static readonly ID = 'workbench.action.openWorkspaceConfigFile'; - static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File"); + static readonly LABEL = localize('openWorkspaceConfigFile', "Open Workspace Configuration File"); constructor( id: string, @@ -168,7 +168,7 @@ export class AddRootFolderAction extends Action { export class GlobalRemoveRootFolderAction extends Action { static readonly ID = 'workbench.action.removeRootFolder'; - static readonly LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); + static readonly LABEL = localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); constructor( id: string, @@ -196,7 +196,7 @@ export class GlobalRemoveRootFolderAction extends Action { export class SaveWorkspaceAsAction extends Action { static readonly ID = 'workbench.action.saveWorkspaceAs'; - static readonly LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); + static readonly LABEL = localize('saveWorkspaceAsAction', "Save Workspace As..."); constructor( id: string, @@ -226,7 +226,7 @@ export class SaveWorkspaceAsAction extends Action { export class DuplicateWorkspaceInNewWindowAction extends Action { static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; - static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window"); + static readonly LABEL = localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window"); constructor( id: string, @@ -255,16 +255,16 @@ class WorkspaceTrustManageAction extends Action2 { constructor() { super({ id: 'workbench.action.manageTrust', - title: { value: nls.localize('manageTrustAction', "Manage Workspace Trust"), original: 'Manage Workspace Trust' }, + title: { value: localize('manageTrustAction', "Manage Workspace Trust"), original: 'Manage Workspace Trust' }, precondition: ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true), - category: nls.localize('workspacesCategory', "Workspaces"), - f1: true, + category: localize('workspacesCategory', "Workspaces"), + f1: true }); } - run(accessor: ServicesAccessor) { - const editorService = accessor.get(IEditorService); - editorService.openEditor({ resource: WORKSPACE_TRUST_URI, mode: 'jsonc', options: { pinned: true } }); + async run(accessor: ServicesAccessor) { + const commandService = accessor.get(ICommandService); + await commandService.executeCommand('workbench.trust.manage'); } } @@ -273,7 +273,7 @@ registerAction2(WorkspaceTrustManageAction); // --- Actions Registration const registry = Registry.as(Extensions.WorkbenchActions); -const workspacesCategory = nls.localize('workspaces', "Workspaces"); +const workspacesCategory = localize('workspaces', "Workspaces"); registry.registerWorkbenchAction(SyncActionDescriptor.from(AddRootFolderAction), 'Workspaces: Add Folder to Workspace...', workspacesCategory); registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalRemoveRootFolderAction), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); @@ -291,7 +291,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '3_workspace', command: { id: ADD_ROOT_FOLDER_COMMAND_ID, - title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") + title: localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") }, order: 1 }); @@ -300,7 +300,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '3_workspace', command: { id: SaveWorkspaceAsAction.ID, - title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") + title: localize('miSaveWorkspaceAs', "Save Workspace As...") }, order: 2, when: EmptyWorkspaceSupportContext @@ -318,7 +318,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), + title: localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), precondition: WorkspaceFolderCountContext.notEqualsTo('0') }, order: 3, @@ -329,7 +329,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") + title: localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") }, order: 3, when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), EmptyWorkspaceSupportContext) diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 7f43e0fb0e5..2f6b8df6f13 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import * as resources from 'vs/base/common/resources'; +import { dirname, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; @@ -24,7 +24,7 @@ import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/ import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; -export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); +export const ADD_ROOT_FOLDER_LABEL = localize('addFolderToWorkspace', "Add Folder to Workspace..."); export const PICK_WORKSPACE_FOLDER_COMMAND_ID = '_workbench.pickWorkspaceFolder'; @@ -61,8 +61,8 @@ CommandsRegistry.registerCommand({ const workspaceEditingService = accessor.get(IWorkspaceEditingService); const dialogsService = accessor.get(IFileDialogService); const folders = await dialogsService.showOpenDialog({ - openLabel: mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), - title: nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), + openLabel: mnemonicButtonLabel(localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), + title: localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), canSelectFolders: true, canSelectMany: true, defaultUri: await dialogsService.defaultFolderPath() @@ -72,7 +72,7 @@ CommandsRegistry.registerCommand({ return; } - await workspaceEditingService.addFolders(folders.map(folder => ({ uri: resources.removeTrailingPathSeparator(folder) }))); + await workspaceEditingService.addFolders(folders.map(folder => ({ uri: removeTrailingPathSeparator(folder) }))); } }); @@ -91,7 +91,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async functio const folderPicks: IQuickPickItem[] = folders.map(folder => { return { label: folder.name, - description: labelService.getUriLabel(resources.dirname(folder.uri), { relative: true }), + description: labelService.getUriLabel(dirname(folder.uri), { relative: true }), folder, iconClasses: getIconClasses(modelService, modeService, folder.uri, FileKind.ROOT_FOLDER) }; @@ -104,7 +104,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, async functio } if (!options.placeHolder) { - options.placeHolder = nls.localize('workspaceFolderPickerPlaceholder', "Select workspace folder"); + options.placeHolder = localize('workspaceFolderPickerPlaceholder', "Select workspace folder"); } if (typeof options.matchOnDescription !== 'boolean') { diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index c0087e858f9..ff991b74b94 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner, ActionRunner, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { Component } from 'vs/workbench/common/component'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IComposite, ICompositeControl } from 'vs/workbench/common/composite'; @@ -14,6 +14,7 @@ import { trackFocus, Dimension } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Disposable } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; /** * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 9113801e2a0..38e33e2bee8 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -15,8 +15,9 @@ import { Composite } from 'vs/workbench/browser/composite'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ViewPaneContainer, ViewsSubMenu } from './parts/views/viewPaneContainer'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; -import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export abstract class PaneComposite extends Composite implements IPaneComposite { diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 94fccfcb646..d96f65dff94 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IAction, toAction } from 'vs/base/common/actions'; import { illegalArgument } from 'vs/base/common/errors'; -import * as arrays from 'vs/base/common/arrays'; +import { equals } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -224,7 +224,7 @@ export class CompositeBar extends Widget implements ICompositeBar { ); }, orientation: this.options.orientation, - ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"), + ariaLabel: localize('activityBarAriaLabel', "Active View Switcher"), animated: false, preventLoopNavigation: this.options.preventLoopNavigation, triggerKeys: { keyDown: true } @@ -538,7 +538,7 @@ export class CompositeBar extends Widget implements ICompositeBar { compositesToShow.length ? compositesToShow.splice(compositesToShow.length - 2, 1) : compositesToShow.pop(); } - const visibleCompositesChange = !arrays.equals(compositesToShow, this.visibleComposites); + const visibleCompositesChange = !equals(compositesToShow, this.visibleComposites); // Pull out overflow action if there is a composite change so that we can add it to the end later if (this.compositeOverflowAction && visibleCompositesChange) { diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 3aac3df41ee..ee3b38958e1 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Action, IAction, Separator } from 'vs/base/common/actions'; -import * as dom from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getDomNodePagePosition, hide, show } from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -208,11 +208,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { } // Try hard to prevent keyboard only focus feedback when using mouse - this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_DOWN, () => { + this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, () => { this.container.classList.add('clicked'); })); - this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_UP, () => { + this._register(addDisposableListener(this.container, EventType.MOUSE_UP, () => { if (this.mouseUpTimeout) { clearTimeout(this.mouseUpTimeout); } @@ -223,19 +223,19 @@ export class ActivityActionViewItem extends BaseActionViewItem { })); // Label - this.label = dom.append(container, dom.$('a')); + this.label = append(container, $('a')); // Badge - this.badge = dom.append(container, dom.$('.badge')); - this.badgeContent = dom.append(this.badge, dom.$('.badge-content')); + this.badge = append(container, $('.badge')); + this.badgeContent = append(this.badge, $('.badge-content')); // Activity bar active border + background const isActivityBarItem = this.options.icon; if (isActivityBarItem) { - dom.append(container, dom.$('.active-item-indicator')); + append(container, $('.active-item-indicator')); } - dom.hide(this.badge); + hide(this.badge); this.updateActivity(); this.updateStyles(); @@ -263,8 +263,8 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.badgeDisposable.clear(); - dom.clearNode(this.badgeContent); - dom.hide(this.badge); + clearNode(this.badgeContent); + hide(this.badge); if (badge) { @@ -282,26 +282,26 @@ export class ActivityActionViewItem extends BaseActionViewItem { } } this.badgeContent.textContent = number; - dom.show(this.badge); + show(this.badge); } } // Text else if (badge instanceof TextBadge) { this.badgeContent.textContent = badge.text; - dom.show(this.badge); + show(this.badge); } // Icon else if (badge instanceof IconBadge) { const clazzList = ThemeIcon.asClassNameArray(badge.icon); this.badgeContent.classList.add(...clazzList); - dom.show(this.badge); + show(this.badge); } // Progress else if (badge instanceof ProgressBadge) { - dom.show(this.badge); + show(this.badge); } if (clazz) { @@ -314,7 +314,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { let title: string; if (badge?.getDescription()) { if (this.activity.name) { - title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription()); + title = localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription()); } else { title = badge.getDescription(); } @@ -369,7 +369,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { ) { super({ id: 'additionalComposites.action', - name: nls.localize('additionalViews', "Additional Views"), + name: localize('additionalViews', "Additional Views"), cssClass: Codicon.more.classNames }); } @@ -424,7 +424,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI } if (suffix) { - action.label = nls.localize('numberBadge', "{0} ({1})", composite.name, suffix); + action.label = localize('numberBadge', "{0} ({1})", composite.name, suffix); } else { action.label = composite.name || ''; } @@ -447,7 +447,7 @@ class ManageExtensionAction extends Action { constructor( @ICommandService private readonly commandService: ICommandService ) { - super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension")); + super('activitybar.manage.extension', localize('manageExtension', "Manage Extension")); } run(id: string): Promise { @@ -513,7 +513,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { } const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null; - return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name; + return keybinding ? localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name; } render(container: HTMLElement): void { @@ -522,8 +522,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.updateChecked(); this.updateEnabled(); - this._register(dom.addDisposableListener(this.container, dom.EventType.CONTEXT_MENU, e => { - dom.EventHelper.stop(e, true); + this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, e => { + EventHelper.stop(e, true); this.showContextMenu(container); })); @@ -546,7 +546,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { }, onDrop: e => { - dom.EventHelper.stop(e.eventData, true); + EventHelper.stop(e.eventData, true); this.dndHandler.drop(e.dragAndDropData, this.activity.id, e.eventData, insertDropBefore); insertDropBefore = this.updateFromDragging(container, false, e.eventData); }, @@ -626,10 +626,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem { const isPinned = this.compositeBar.isPinned(this.activity.id); if (isPinned) { - this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide '{0}'", this.getActivtyName(true)); + this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.getActivtyName(true)); this.toggleCompositePinnedAction.checked = false; } else { - this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep '{0}'", this.getActivtyName(true)); + this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.getActivtyName(true)); } const otherActions = this.contextMenuActionsProvider(); @@ -638,7 +638,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { actions.push(...otherActions); } - const elementPosition = dom.getDomNodePagePosition(container); + const elementPosition = getDomNodePagePosition(container); const anchor = { x: Math.floor(elementPosition.left + (elementPosition.width / 2)), y: elementPosition.top + elementPosition.height @@ -690,7 +690,7 @@ export class ToggleCompositePinnedAction extends Action { private activity: IActivity | undefined, private compositeBar: ICompositeBar ) { - super('show.toggleCompositePinned', activity ? activity.name : nls.localize('toggle', "Toggle View Pinned")); + super('show.toggleCompositePinned', activity ? activity.name : localize('toggle', "Toggle View Pinned")); this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id); } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 3e7f9fb96cf..1ead62e5b8e 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/compositepart'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, dispose, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import * as errors from 'vs/base/common/errors'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; @@ -258,7 +258,7 @@ export abstract class CompositePart extends Part { this.telemetryActionsListener.value = toolBar.actionRunner.onDidRun(e => { // Check for Error - if (e.error && !errors.isPromiseCanceledError(e.error)) { + if (e.error && !isPromiseCanceledError(e.error)) { this.notificationService.error(e.error); } @@ -318,7 +318,7 @@ export abstract class CompositePart extends Part { this.titleLabel.updateTitle(compositeId, compositeTitle, withNullAsUndefined(keybinding?.getLabel())); const toolBar = assertIsDefined(this.toolBar); - toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle)); + toolBar.setAriaLabel(localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle)); } private collectCompositeActions(composite?: Composite): () => void { @@ -394,7 +394,7 @@ export abstract class CompositePart extends Part { orientation: ActionsOrientation.HORIZONTAL, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), - toggleMenuTitle: nls.localize('viewsAndMoreActions', "Views and More Actions...") + toggleMenuTitle: localize('viewsAndMoreActions', "Views and More Actions...") })); this.collectCompositeActions()(); @@ -413,7 +413,7 @@ export abstract class CompositePart extends Part { // The title label is shared for all composites in the base CompositePart if (!this.activeComposite || this.activeComposite.getId() === id) { titleLabel.innerText = title; - titleLabel.title = keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title; + titleLabel.title = keybinding ? localize('titleTooltip', "{0} ({1})", title, keybinding) : title; } }, diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 83550c00b8e..babf68f0dfa 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler } from 'vs/platform/dialogs/common/dialogs'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -46,13 +46,13 @@ export class BrowserDialogHandler implements IDialogHandler { if (confirmation.primaryButton) { buttons.push(confirmation.primaryButton); } else { - buttons.push(nls.localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes")); + buttons.push(localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes")); } if (confirmation.secondaryButton) { buttons.push(confirmation.secondaryButton); } else if (typeof confirmation.secondaryButton === 'undefined') { - buttons.push(nls.localize('cancelButton', "Cancel")); + buttons.push(localize('cancelButton', "Cancel")); } const result = await this.doShow(confirmation.type, confirmation.message, buttons, confirmation.detail, 1, confirmation.checkbox); @@ -121,7 +121,7 @@ export class BrowserDialogHandler implements IDialogHandler { async about(): Promise { const detailString = (useAgo: boolean): string => { - return nls.localize('aboutDetail', + return localize('aboutDetail', "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", this.productService.version || 'Unknown', this.productService.commit || 'Unknown', @@ -134,7 +134,7 @@ export class BrowserDialogHandler implements IDialogHandler { const detailToCopy = detailString(false); - const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 }); + const { choice } = await this.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail, cancelId: 1 }); if (choice === 0) { this.clipboardService.writeText(detailToCopy); diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index 9d285cea910..1e3ebf38f8f 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -33,7 +33,7 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { const secondary = this.secondaryEditorPane; if (primary instanceof BaseBinaryResourceEditor && secondary instanceof BaseBinaryResourceEditor) { - return nls.localize('metadataDiff', "{0} ↔ {1}", secondary.getMetadata(), primary.getMetadata()); + return localize('metadataDiff', "{0} ↔ {1}", secondary.getMetadata(), primary.getMetadata()); } return undefined; diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 0883f8f85ce..5129069e370 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/binaryeditor'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { EditorInput, EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; @@ -58,7 +58,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane { } getTitle(): string { - return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); + return this.input ? this.input.getName() : localize('binaryEditor', "Binary Viewer"); } protected createEditor(parent: HTMLElement): void { @@ -217,7 +217,7 @@ class FileTooLargeFileView { clearNode(container); const label = document.createElement('span'); - label.textContent = nls.localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size); + label.textContent = localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size); container.appendChild(label); scrollbar.scanDomNode(); @@ -240,12 +240,12 @@ class FileSeemsBinaryFileView { const disposables = new DisposableStore(); const label = document.createElement('p'); - label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); + label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); container.appendChild(label); const link = append(label, $('a.embedded-link')); link.setAttribute('role', 'button'); - link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); + link.textContent = localize('openAsText', "Do you want to open it anyway?"); disposables.add(addDisposableListener(link, EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 78d51af076a..a1dff0866d2 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext } from 'vs/workbench/common/editor'; @@ -15,6 +15,7 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; +import { UntitledHintContribution } from 'vs/workbench/browser/parts/editor/untitledHint'; import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; @@ -35,7 +36,12 @@ import { NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction } from 'vs/workbench/browser/parts/editor/editorActions'; -import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; +import { + CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, + CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, + PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, + TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup +} from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -63,7 +69,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( TextResourceEditor, TextResourceEditor.ID, - nls.localize('textEditor', "Text Editor"), + localize('textEditor', "Text Editor"), ), [ new SyncDescriptor(UntitledTextEditorInput), @@ -76,7 +82,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( TextDiffEditor, TextDiffEditor.ID, - nls.localize('textDiffEditor', "Text Diff Editor") + localize('textDiffEditor', "Text Diff Editor") ), [ new SyncDescriptor(DiffEditorInput) @@ -88,7 +94,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( BinaryResourceDiffEditor, BinaryResourceDiffEditor.ID, - nls.localize('binaryDiffEditor', "Binary Diff Editor") + localize('binaryDiffEditor', "Binary Diff Editor") ), [ new SyncDescriptor(DiffEditorInput) @@ -99,7 +105,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( SideBySideEditor, SideBySideEditor.ID, - nls.localize('sideBySideEditor', "Side by Side Editor") + localize('sideBySideEditor', "Side by Side Editor") ), [ new SyncDescriptor(SideBySideEditorInput) @@ -276,6 +282,9 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Register Editor Auto Save Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorAutoSave, LifecyclePhase.Ready); +// Register Untitled Hint +registerEditorContribution(UntitledHintContribution.ID, UntitledHintContribution); + // Register Status Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.from(ChangeModeAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); @@ -291,24 +300,24 @@ quickAccessRegistry.registerQuickAccessProvider({ ctor: ActiveGroupEditorsByMostRecentlyUsedQuickAccess, prefix: ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, contextKey: editorPickerContextKey, - placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), - helpEntries: [{ description: nls.localize('activeGroupEditorsByMostRecentlyUsedQuickAccess', "Show Editors in Active Group by Most Recently Used"), needsEditor: false }] + placeholder: localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: localize('activeGroupEditorsByMostRecentlyUsedQuickAccess', "Show Editors in Active Group by Most Recently Used"), needsEditor: false }] }); quickAccessRegistry.registerQuickAccessProvider({ ctor: AllEditorsByAppearanceQuickAccess, prefix: AllEditorsByAppearanceQuickAccess.PREFIX, contextKey: editorPickerContextKey, - placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), - helpEntries: [{ description: nls.localize('allEditorsByAppearanceQuickAccess', "Show All Opened Editors By Appearance"), needsEditor: false }] + placeholder: localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: localize('allEditorsByAppearanceQuickAccess', "Show All Opened Editors By Appearance"), needsEditor: false }] }); quickAccessRegistry.registerQuickAccessProvider({ ctor: AllEditorsByMostRecentlyUsedQuickAccess, prefix: AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, contextKey: editorPickerContextKey, - placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), - helpEntries: [{ description: nls.localize('allEditorsByMostRecentlyUsedQuickAccess', "Show All Opened Editors By Most Recently Used"), needsEditor: false }] + placeholder: localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: localize('allEditorsByMostRecentlyUsedQuickAccess', "Show All Opened Editors By Most Recently Used"), needsEditor: false }] }); // Register Editor Actions @@ -326,7 +335,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(ReopenClosedEditorAct registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowAllEditorsByAppearanceAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors By Appearance', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowAllEditorsByMostRecentlyUsedAction), 'View: Show All Editors By Most Recently Used', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowEditorsInActiveGroupByMostRecentlyUsedAction), 'View: Show Editors in Active Group By Most Recently Used', CATEGORIES.View.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ClearRecentFilesAction), 'File: Clear Recently Opened', nls.localize('file', "File")); +registry.registerWorkbenchAction(SyncActionDescriptor.from(ClearRecentFilesAction), 'File: Clear Recently Opened', localize('file', "File")); registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseAllEditorsAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseAllEditorGroupsAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseLeftEditorsInGroupAction), 'View: Close Editors to the Left in Group', CATEGORIES.View.value); @@ -424,7 +433,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); // Editor Commands -editorCommands.setup(); +setup(); // Touch Bar if (isMacintosh) { @@ -442,34 +451,34 @@ if (isMacintosh) { } // Empty Editor Group Context Menu -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '2_split', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '2_split', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '2_split', order: 30 }); -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '2_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.CLOSE_EDITOR_GROUP_COMMAND_ID, title: nls.localize('close', "Close") }, group: '3_close', order: 10, when: ContextKeyExpr.has('multipleEditorGroups') }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: SPLIT_EDITOR_UP, title: localize('splitUp', "Split Up") }, group: '2_split', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '2_split', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '2_split', order: 30 }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '2_split', order: 40 }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: CLOSE_EDITOR_GROUP_COMMAND_ID, title: localize('close', "Close") }, group: '3_close', order: 10, when: ContextKeyExpr.has('multipleEditorGroups') }); // Editor Title Context Menu -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: localize('closeRight', "Close to the Right"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '1_close', order: 50 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: ReopenResourcesAction.ID, title: ReopenResourcesAction.LABEL }, group: '1_open', order: 10, when: ActiveEditorAvailableEditorIdsContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: ActiveEditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: nls.localize('pin', "Pin") }, group: '3_preview', order: 20, when: ActiveEditorStickyContext.toNegated() }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin") }, group: '3_preview', order: 20, when: ActiveEditorStickyContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '5_split', order: 40 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: KEEP_EDITOR_COMMAND_ID, title: localize('keepOpen', "Keep Open"), precondition: ActiveEditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: PIN_EDITOR_COMMAND_ID, title: localize('pin', "Pin") }, group: '3_preview', order: 20, when: ActiveEditorStickyContext.toNegated() }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: UNPIN_EDITOR_COMMAND_ID, title: localize('unpin', "Unpin") }, group: '3_preview', order: 20, when: ActiveEditorStickyContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_UP, title: localize('splitUp', "Split Up") }, group: '5_split', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '5_split', order: 40 }); // Editor Title Menu -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_SIDE_BY_SIDE, title: nls.localize('inlineView', "Inline View"), toggled: ContextKeyExpr.equals('config.diffEditor.renderSideBySide', false) }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_KEEP_EDITORS_COMMAND_ID, title: nls.localize('toggleKeepEditors', "Keep Editors Open"), toggled: ContextKeyExpr.not('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, title: { value: nls.localize('run', "Run"), original: 'Run', }, icon: Codicon.run, group: 'navigation', order: -1 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, title: localize('inlineView', "Inline View"), toggled: ContextKeyExpr.equals('config.diffEditor.renderSideBySide', false) }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SHOW_EDITORS_IN_GROUP, title: localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '5_close', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, title: localize('toggleKeepEditors', "Keep Editors Open"), toggled: ContextKeyExpr.not('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, title: { value: localize('run', "Run"), original: 'Run', }, icon: Codicon.run, group: 'navigation', order: -1 }); interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; } @@ -501,14 +510,14 @@ function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpressi appendEditorToolItem( { id: SplitEditorAction.ID, - title: nls.localize('splitEditorRight', "Split Editor Right"), + title: localize('splitEditorRight', "Split Editor Right"), icon: Codicon.splitHorizontal }, ContextKeyExpr.not('splitEditorsVertically'), 100000, // towards the end { - id: editorCommands.SPLIT_EDITOR_DOWN, - title: nls.localize('splitEditorDown', "Split Editor Down"), + id: SPLIT_EDITOR_DOWN, + title: localize('splitEditorDown', "Split Editor Down"), icon: Codicon.splitVertical } ); @@ -516,14 +525,14 @@ appendEditorToolItem( appendEditorToolItem( { id: SplitEditorAction.ID, - title: nls.localize('splitEditorDown', "Split Editor Down"), + title: localize('splitEditorDown', "Split Editor Down"), icon: Codicon.splitVertical }, ContextKeyExpr.has('splitEditorsVertically'), 100000, // towards the end { - id: editorCommands.SPLIT_EDITOR_RIGHT, - title: nls.localize('splitEditorRight', "Split Editor Right"), + id: SPLIT_EDITOR_RIGHT, + title: localize('splitEditorRight', "Split Editor Right"), icon: Codicon.splitHorizontal } ); @@ -531,15 +540,15 @@ appendEditorToolItem( // Editor Title Menu: Close (tabs disabled, normal editor) appendEditorToolItem( { - id: editorCommands.CLOSE_EDITOR_COMMAND_ID, - title: nls.localize('close', "Close"), + id: CLOSE_EDITOR_COMMAND_ID, + title: localize('close', "Close"), icon: Codicon.close }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext.toNegated()), 1000000, // towards the far end { - id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, - title: nls.localize('closeAll', "Close All"), + id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, + title: localize('closeAll', "Close All"), icon: Codicon.closeAll } ); @@ -547,15 +556,15 @@ appendEditorToolItem( // Editor Title Menu: Close (tabs disabled, dirty editor) appendEditorToolItem( { - id: editorCommands.CLOSE_EDITOR_COMMAND_ID, - title: nls.localize('close', "Close"), + id: CLOSE_EDITOR_COMMAND_ID, + title: localize('close', "Close"), icon: Codicon.closeDirty }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext, ActiveEditorStickyContext.toNegated()), 1000000, // towards the far end { - id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, - title: nls.localize('closeAll', "Close All"), + id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, + title: localize('closeAll', "Close All"), icon: Codicon.closeAll } ); @@ -563,15 +572,15 @@ appendEditorToolItem( // Editor Title Menu: Close (tabs disabled, sticky editor) appendEditorToolItem( { - id: editorCommands.UNPIN_EDITOR_COMMAND_ID, - title: nls.localize('unpin', "Unpin"), + id: UNPIN_EDITOR_COMMAND_ID, + title: localize('unpin', "Unpin"), icon: Codicon.pinned }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext), 1000000, // towards the far end { - id: editorCommands.CLOSE_EDITOR_COMMAND_ID, - title: nls.localize('close', "Close"), + id: CLOSE_EDITOR_COMMAND_ID, + title: localize('close', "Close"), icon: Codicon.close } ); @@ -579,29 +588,29 @@ appendEditorToolItem( // Editor Title Menu: Close (tabs disabled, dirty & sticky editor) appendEditorToolItem( { - id: editorCommands.UNPIN_EDITOR_COMMAND_ID, - title: nls.localize('unpin', "Unpin"), + id: UNPIN_EDITOR_COMMAND_ID, + title: localize('unpin', "Unpin"), icon: Codicon.pinnedDirty }, ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext, ActiveEditorStickyContext), 1000000, // towards the far end { - id: editorCommands.CLOSE_EDITOR_COMMAND_ID, - title: nls.localize('close', "Close"), + id: CLOSE_EDITOR_COMMAND_ID, + title: localize('close', "Close"), icon: Codicon.close } ); -const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, nls.localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); -const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, nls.localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); -const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, nls.localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); +const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); +const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); +const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); // Diff Editor Title Menu: Previous Change appendEditorToolItem( { - id: editorCommands.GOTO_PREVIOUS_CHANGE, - title: nls.localize('navigate.prev.label', "Previous Change"), + id: GOTO_PREVIOUS_CHANGE, + title: localize('navigate.prev.label', "Previous Change"), icon: previousChangeIcon }, TextCompareEditorActiveContext, @@ -611,8 +620,8 @@ appendEditorToolItem( // Diff Editor Title Menu: Next Change appendEditorToolItem( { - id: editorCommands.GOTO_NEXT_CHANGE, - title: nls.localize('navigate.next.label', "Next Change"), + id: GOTO_NEXT_CHANGE, + title: localize('navigate.next.label', "Next Change"), icon: nextChangeIcon }, TextCompareEditorActiveContext, @@ -622,8 +631,8 @@ appendEditorToolItem( // Diff Editor Title Menu: Toggle Ignore Trim Whitespace (Enabled) appendEditorToolItem( { - id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, - title: nls.localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"), + id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, + title: localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"), icon: toggleWhitespace }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)), @@ -633,8 +642,8 @@ appendEditorToolItem( // Diff Editor Title Menu: Toggle Ignore Trim Whitespace (Disabled) appendEditorToolItem( { - id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, - title: nls.localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), + id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, + title: localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), icon: ThemeIcon.modify(toggleWhitespace, 'disabled') }, ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)), @@ -642,23 +651,23 @@ appendEditorToolItem( ); // Editor Commands for Command Palette -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: { value: nls.localize('keepEditor', "Keep Editor"), original: 'Keep Editor' }, category: CATEGORIES.View }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: { value: nls.localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: { value: nls.localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_PINNED_EDITOR_COMMAND_ID, title: { value: nls.localize('closePinnedEditor', "Close Pinned Editor"), original: 'Close Pinned Editor' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'Close All Editors in Group' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'Close Saved Editors in Group' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeOtherEditors', "Close Other Editors in Group"), original: 'Close Other Editors in Group' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: { value: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), original: 'Close Editors to the Right in Group' }, category: CATEGORIES.View } }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_AND_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorGroup', "Close Editor Group"), original: 'Close Editor Group' }, category: CATEGORIES.View }, when: MultipleEditorGroupsContext }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: KEEP_EDITOR_COMMAND_ID, title: { value: localize('keepEditor', "Keep Editor"), original: 'Keep Editor' }, category: CATEGORIES.View }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: PIN_EDITOR_COMMAND_ID, title: { value: localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: UNPIN_EDITOR_COMMAND_ID, title: { value: localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: { value: localize('closeEditor', "Close Editor"), original: 'Close Editor' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_PINNED_EDITOR_COMMAND_ID, title: { value: localize('closePinnedEditor', "Close Pinned Editor"), original: 'Close Pinned Editor' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'Close All Editors in Group' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'Close Saved Editors in Group' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: localize('closeOtherEditors', "Close Other Editors in Group"), original: 'Close Other Editors in Group' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: { value: localize('closeRightEditors', "Close Editors to the Right in Group"), original: 'Close Editors to the Right in Group' }, category: CATEGORIES.View } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLOSE_EDITORS_AND_GROUP_COMMAND_ID, title: { value: localize('closeEditorGroup', "Close Editor Group"), original: 'Close Editor Group' }, category: CATEGORIES.View }, when: MultipleEditorGroupsContext }); // File menu MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { group: '1_editor', command: { id: ReopenClosedEditorAction.ID, - title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), + title: localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), precondition: ContextKeyExpr.has('canReopenClosedEditor') }, order: 1 @@ -668,7 +677,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { group: 'z_clear', command: { id: ClearRecentFilesAction.ID, - title: nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened") + title: localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened") }, order: 1 }); @@ -676,7 +685,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { // Layout menu MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '2_appearance', - title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"), + title: localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"), submenu: MenuId.MenubarLayoutMenu, order: 2 }); @@ -684,8 +693,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '1_split', command: { - id: editorCommands.SPLIT_EDITOR_UP, - title: nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up") + id: SPLIT_EDITOR_UP, + title: localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up") }, order: 1 }); @@ -693,8 +702,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '1_split', command: { - id: editorCommands.SPLIT_EDITOR_DOWN, - title: nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down") + id: SPLIT_EDITOR_DOWN, + title: localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down") }, order: 2 }); @@ -702,8 +711,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '1_split', command: { - id: editorCommands.SPLIT_EDITOR_LEFT, - title: nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left") + id: SPLIT_EDITOR_LEFT, + title: localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left") }, order: 3 }); @@ -711,8 +720,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '1_split', command: { - id: editorCommands.SPLIT_EDITOR_RIGHT, - title: nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right") + id: SPLIT_EDITOR_RIGHT, + title: localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right") }, order: 4 }); @@ -721,7 +730,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutSingleAction.ID, - title: nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single") + title: localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single") }, order: 1 }); @@ -730,7 +739,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutTwoColumnsAction.ID, - title: nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns") + title: localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns") }, order: 3 }); @@ -739,7 +748,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutThreeColumnsAction.ID, - title: nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns") + title: localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns") }, order: 4 }); @@ -748,7 +757,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutTwoRowsAction.ID, - title: nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows") + title: localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows") }, order: 5 }); @@ -757,7 +766,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutThreeRowsAction.ID, - title: nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows") + title: localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows") }, order: 6 }); @@ -766,7 +775,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutTwoByTwoGridAction.ID, - title: nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)") + title: localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)") }, order: 7 }); @@ -775,7 +784,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutTwoRowsRightAction.ID, - title: nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right") + title: localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right") }, order: 8 }); @@ -784,7 +793,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { group: '2_layouts', command: { id: EditorLayoutTwoColumnsBottomAction.ID, - title: nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom") + title: localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom") }, order: 9 }); @@ -796,7 +805,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { group: '1_history_nav', command: { id: 'workbench.action.navigateBack', - title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), + title: localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), precondition: ContextKeyExpr.has('canNavigateBack') }, order: 1 @@ -806,7 +815,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { group: '1_history_nav', command: { id: 'workbench.action.navigateForward', - title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), + title: localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), precondition: ContextKeyExpr.has('canNavigateForward') }, order: 2 @@ -816,7 +825,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { group: '1_history_nav', command: { id: 'workbench.action.navigateToLastEditLocation', - title: nls.localize({ key: 'miLastEditLocation', comment: ['&& denotes a mnemonic'] }, "&&Last Edit Location"), + title: localize({ key: 'miLastEditLocation', comment: ['&& denotes a mnemonic'] }, "&&Last Edit Location"), precondition: ContextKeyExpr.has('canNavigateToLastEditLocation') }, order: 3 @@ -827,7 +836,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '1_any', command: { id: 'workbench.action.nextEditor', - title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor") + title: localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor") }, order: 1 }); @@ -836,7 +845,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '1_any', command: { id: 'workbench.action.previousEditor', - title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor") + title: localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor") }, order: 2 }); @@ -845,7 +854,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '2_any_used', command: { id: 'workbench.action.openNextRecentlyUsedEditor', - title: nls.localize({ key: 'miNextRecentlyUsedEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor") + title: localize({ key: 'miNextRecentlyUsedEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor") }, order: 1 }); @@ -854,7 +863,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '2_any_used', command: { id: 'workbench.action.openPreviousRecentlyUsedEditor', - title: nls.localize({ key: 'miPreviousRecentlyUsedEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor") + title: localize({ key: 'miPreviousRecentlyUsedEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor") }, order: 2 }); @@ -863,7 +872,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '3_group', command: { id: 'workbench.action.nextEditorInGroup', - title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Editor in Group") + title: localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Editor in Group") }, order: 1 }); @@ -872,7 +881,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '3_group', command: { id: 'workbench.action.previousEditorInGroup', - title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor in Group") + title: localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor in Group") }, order: 2 }); @@ -881,7 +890,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '4_group_used', command: { id: 'workbench.action.openNextRecentlyUsedEditorInGroup', - title: nls.localize({ key: 'miNextUsedEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group") + title: localize({ key: 'miNextUsedEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group") }, order: 1 }); @@ -890,14 +899,14 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { group: '4_group_used', command: { id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - title: nls.localize({ key: 'miPreviousUsedEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group") + title: localize({ key: 'miPreviousUsedEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group") }, order: 2 }); MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { group: '2_editor_nav', - title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"), + title: localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"), submenu: MenuId.MenubarSwitchEditorMenu, order: 1 }); @@ -907,7 +916,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusFirstEditorGroup', - title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1") + title: localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1") }, order: 1 }); @@ -916,7 +925,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusSecondEditorGroup', - title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2") + title: localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2") }, order: 2 }); @@ -925,7 +934,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusThirdEditorGroup', - title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3"), + title: localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 3 @@ -935,7 +944,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusFourthEditorGroup', - title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4"), + title: localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 4 @@ -945,7 +954,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '1_focus_index', command: { id: 'workbench.action.focusFifthEditorGroup', - title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5"), + title: localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 5 @@ -955,7 +964,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '2_next_prev', command: { id: 'workbench.action.focusNextGroup', - title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), + title: localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 1 @@ -965,7 +974,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '2_next_prev', command: { id: 'workbench.action.focusPreviousGroup', - title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), + title: localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 2 @@ -975,7 +984,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusLeftGroup', - title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left"), + title: localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 1 @@ -985,7 +994,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusRightGroup', - title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right"), + title: localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 2 @@ -995,7 +1004,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusAboveGroup', - title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above"), + title: localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 3 @@ -1005,7 +1014,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { group: '3_directional', command: { id: 'workbench.action.focusBelowGroup', - title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below"), + title: localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below"), precondition: ContextKeyExpr.has('multipleEditorGroups') }, order: 4 @@ -1013,7 +1022,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { group: '2_editor_nav', - title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"), + title: localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"), submenu: MenuId.MenubarSwitchGroupMenu, order: 2 }); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index db48f417148..df7238d6ef2 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IEditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -79,7 +79,7 @@ export class BaseSplitEditorAction extends Action { export class SplitEditorAction extends BaseSplitEditorAction { static readonly ID = 'workbench.action.splitEditor'; - static readonly LABEL = nls.localize('splitEditor', "Split Editor"); + static readonly LABEL = localize('splitEditor', "Split Editor"); constructor( id: string, @@ -94,7 +94,7 @@ export class SplitEditorAction extends BaseSplitEditorAction { export class SplitEditorOrthogonalAction extends BaseSplitEditorAction { static readonly ID = 'workbench.action.splitEditorOrthogonal'; - static readonly LABEL = nls.localize('splitEditorOrthogonal', "Split Editor Orthogonal"); + static readonly LABEL = localize('splitEditorOrthogonal', "Split Editor Orthogonal"); constructor( id: string, @@ -115,7 +115,7 @@ export class SplitEditorOrthogonalAction extends BaseSplitEditorAction { export class SplitEditorLeftAction extends ExecuteCommandAction { static readonly ID = SPLIT_EDITOR_LEFT; - static readonly LABEL = nls.localize('splitEditorGroupLeft', "Split Editor Left"); + static readonly LABEL = localize('splitEditorGroupLeft', "Split Editor Left"); constructor( id: string, @@ -129,7 +129,7 @@ export class SplitEditorLeftAction extends ExecuteCommandAction { export class SplitEditorRightAction extends ExecuteCommandAction { static readonly ID = SPLIT_EDITOR_RIGHT; - static readonly LABEL = nls.localize('splitEditorGroupRight', "Split Editor Right"); + static readonly LABEL = localize('splitEditorGroupRight', "Split Editor Right"); constructor( id: string, @@ -143,7 +143,7 @@ export class SplitEditorRightAction extends ExecuteCommandAction { export class SplitEditorUpAction extends ExecuteCommandAction { static readonly ID = SPLIT_EDITOR_UP; - static readonly LABEL = nls.localize('splitEditorGroupUp', "Split Editor Up"); + static readonly LABEL = localize('splitEditorGroupUp', "Split Editor Up"); constructor( id: string, @@ -157,7 +157,7 @@ export class SplitEditorUpAction extends ExecuteCommandAction { export class SplitEditorDownAction extends ExecuteCommandAction { static readonly ID = SPLIT_EDITOR_DOWN; - static readonly LABEL = nls.localize('splitEditorGroupDown', "Split Editor Down"); + static readonly LABEL = localize('splitEditorGroupDown', "Split Editor Down"); constructor( id: string, @@ -171,7 +171,7 @@ export class SplitEditorDownAction extends ExecuteCommandAction { export class JoinTwoGroupsAction extends Action { static readonly ID = 'workbench.action.joinTwoGroups'; - static readonly LABEL = nls.localize('joinTwoGroups', "Join Editor Group with Next Group"); + static readonly LABEL = localize('joinTwoGroups', "Join Editor Group with Next Group"); constructor( id: string, @@ -206,7 +206,7 @@ export class JoinTwoGroupsAction extends Action { export class JoinAllGroupsAction extends Action { static readonly ID = 'workbench.action.joinAllGroups'; - static readonly LABEL = nls.localize('joinAllGroups', "Join All Editor Groups"); + static readonly LABEL = localize('joinAllGroups', "Join All Editor Groups"); constructor( id: string, @@ -224,7 +224,7 @@ export class JoinAllGroupsAction extends Action { export class NavigateBetweenGroupsAction extends Action { static readonly ID = 'workbench.action.navigateEditorGroups'; - static readonly LABEL = nls.localize('navigateEditorGroups', "Navigate Between Editor Groups"); + static readonly LABEL = localize('navigateEditorGroups', "Navigate Between Editor Groups"); constructor( id: string, @@ -243,7 +243,7 @@ export class NavigateBetweenGroupsAction extends Action { export class FocusActiveGroupAction extends Action { static readonly ID = 'workbench.action.focusActiveEditorGroup'; - static readonly LABEL = nls.localize('focusActiveEditorGroup', "Focus Active Editor Group"); + static readonly LABEL = localize('focusActiveEditorGroup', "Focus Active Editor Group"); constructor( id: string, @@ -280,7 +280,7 @@ export abstract class BaseFocusGroupAction extends Action { export class FocusFirstGroupAction extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusFirstEditorGroup'; - static readonly LABEL = nls.localize('focusFirstEditorGroup', "Focus First Editor Group"); + static readonly LABEL = localize('focusFirstEditorGroup', "Focus First Editor Group"); constructor( id: string, @@ -294,7 +294,7 @@ export class FocusFirstGroupAction extends BaseFocusGroupAction { export class FocusLastGroupAction extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusLastEditorGroup'; - static readonly LABEL = nls.localize('focusLastEditorGroup', "Focus Last Editor Group"); + static readonly LABEL = localize('focusLastEditorGroup', "Focus Last Editor Group"); constructor( id: string, @@ -308,7 +308,7 @@ export class FocusLastGroupAction extends BaseFocusGroupAction { export class FocusNextGroup extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusNextGroup'; - static readonly LABEL = nls.localize('focusNextGroup', "Focus Next Editor Group"); + static readonly LABEL = localize('focusNextGroup', "Focus Next Editor Group"); constructor( id: string, @@ -322,7 +322,7 @@ export class FocusNextGroup extends BaseFocusGroupAction { export class FocusPreviousGroup extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusPreviousGroup'; - static readonly LABEL = nls.localize('focusPreviousGroup', "Focus Previous Editor Group"); + static readonly LABEL = localize('focusPreviousGroup', "Focus Previous Editor Group"); constructor( id: string, @@ -336,7 +336,7 @@ export class FocusPreviousGroup extends BaseFocusGroupAction { export class FocusLeftGroup extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusLeftGroup'; - static readonly LABEL = nls.localize('focusLeftGroup', "Focus Left Editor Group"); + static readonly LABEL = localize('focusLeftGroup', "Focus Left Editor Group"); constructor( id: string, @@ -350,7 +350,7 @@ export class FocusLeftGroup extends BaseFocusGroupAction { export class FocusRightGroup extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusRightGroup'; - static readonly LABEL = nls.localize('focusRightGroup', "Focus Right Editor Group"); + static readonly LABEL = localize('focusRightGroup', "Focus Right Editor Group"); constructor( id: string, @@ -364,7 +364,7 @@ export class FocusRightGroup extends BaseFocusGroupAction { export class FocusAboveGroup extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusAboveGroup'; - static readonly LABEL = nls.localize('focusAboveGroup', "Focus Above Editor Group"); + static readonly LABEL = localize('focusAboveGroup', "Focus Above Editor Group"); constructor( id: string, @@ -378,7 +378,7 @@ export class FocusAboveGroup extends BaseFocusGroupAction { export class FocusBelowGroup extends BaseFocusGroupAction { static readonly ID = 'workbench.action.focusBelowGroup'; - static readonly LABEL = nls.localize('focusBelowGroup', "Focus Below Editor Group"); + static readonly LABEL = localize('focusBelowGroup', "Focus Below Editor Group"); constructor( id: string, @@ -392,7 +392,7 @@ export class FocusBelowGroup extends BaseFocusGroupAction { export class CloseEditorAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; - static readonly LABEL = nls.localize('closeEditor', "Close Editor"); + static readonly LABEL = localize('closeEditor', "Close Editor"); constructor( id: string, @@ -410,7 +410,7 @@ export class CloseEditorAction extends Action { export class UnpinEditorAction extends Action { static readonly ID = 'workbench.action.unpinActiveEditor'; - static readonly LABEL = nls.localize('unpinEditor', "Unpin Editor"); + static readonly LABEL = localize('unpinEditor', "Unpin Editor"); constructor( id: string, @@ -428,7 +428,7 @@ export class UnpinEditorAction extends Action { export class CloseOneEditorAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; - static readonly LABEL = nls.localize('closeOneEditor', "Close"); + static readonly LABEL = localize('closeOneEditor', "Close"); constructor( id: string, @@ -471,7 +471,7 @@ export class CloseOneEditorAction extends Action { export class RevertAndCloseEditorAction extends Action { static readonly ID = 'workbench.action.revertAndCloseActiveEditor'; - static readonly LABEL = nls.localize('revertAndCloseActiveEditor', "Revert and Close Editor"); + static readonly LABEL = localize('revertAndCloseActiveEditor', "Revert and Close Editor"); constructor( id: string, @@ -506,7 +506,7 @@ export class RevertAndCloseEditorAction extends Action { export class CloseLeftEditorsInGroupAction extends Action { static readonly ID = 'workbench.action.closeEditorsToTheLeft'; - static readonly LABEL = nls.localize('closeEditorsToTheLeft', "Close Editors to the Left in Group"); + static readonly LABEL = localize('closeEditorsToTheLeft', "Close Editors to the Left in Group"); constructor( id: string, @@ -649,7 +649,7 @@ abstract class BaseCloseAllAction extends Action { export class CloseAllEditorsAction extends BaseCloseAllAction { static readonly ID = 'workbench.action.closeAllEditors'; - static readonly LABEL = nls.localize('closeAllEditors', "Close All Editors"); + static readonly LABEL = localize('closeAllEditors', "Close All Editors"); constructor( id: string, @@ -675,7 +675,7 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { export class CloseAllEditorGroupsAction extends BaseCloseAllAction { static readonly ID = 'workbench.action.closeAllGroups'; - static readonly LABEL = nls.localize('closeAllGroups', "Close All Editor Groups"); + static readonly LABEL = localize('closeAllGroups', "Close All Editor Groups"); constructor( id: string, @@ -703,7 +703,7 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { export class CloseEditorsInOtherGroupsAction extends Action { static readonly ID = 'workbench.action.closeEditorsInOtherGroups'; - static readonly LABEL = nls.localize('closeEditorsInOtherGroups', "Close Editors in Other Groups"); + static readonly LABEL = localize('closeEditorsInOtherGroups', "Close Editors in Other Groups"); constructor( id: string, @@ -728,7 +728,7 @@ export class CloseEditorsInOtherGroupsAction extends Action { export class CloseEditorInAllGroupsAction extends Action { static readonly ID = 'workbench.action.closeEditorInAllGroups'; - static readonly LABEL = nls.localize('closeEditorInAllGroups', "Close Editor in All Groups"); + static readonly LABEL = localize('closeEditorInAllGroups', "Close Editor in All Groups"); constructor( id: string, @@ -827,7 +827,7 @@ class BaseMoveGroupAction extends BaseMoveCopyGroupAction { export class MoveGroupLeftAction extends BaseMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupLeft'; - static readonly LABEL = nls.localize('moveActiveGroupLeft', "Move Editor Group Left"); + static readonly LABEL = localize('moveActiveGroupLeft', "Move Editor Group Left"); constructor( id: string, @@ -841,7 +841,7 @@ export class MoveGroupLeftAction extends BaseMoveGroupAction { export class MoveGroupRightAction extends BaseMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupRight'; - static readonly LABEL = nls.localize('moveActiveGroupRight', "Move Editor Group Right"); + static readonly LABEL = localize('moveActiveGroupRight', "Move Editor Group Right"); constructor( id: string, @@ -855,7 +855,7 @@ export class MoveGroupRightAction extends BaseMoveGroupAction { export class MoveGroupUpAction extends BaseMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupUp'; - static readonly LABEL = nls.localize('moveActiveGroupUp', "Move Editor Group Up"); + static readonly LABEL = localize('moveActiveGroupUp', "Move Editor Group Up"); constructor( id: string, @@ -869,7 +869,7 @@ export class MoveGroupUpAction extends BaseMoveGroupAction { export class MoveGroupDownAction extends BaseMoveGroupAction { static readonly ID = 'workbench.action.moveActiveEditorGroupDown'; - static readonly LABEL = nls.localize('moveActiveGroupDown', "Move Editor Group Down"); + static readonly LABEL = localize('moveActiveGroupDown', "Move Editor Group Down"); constructor( id: string, @@ -895,7 +895,7 @@ class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction { export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft'; - static readonly LABEL = nls.localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left"); + static readonly LABEL = localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left"); constructor( id: string, @@ -909,7 +909,7 @@ export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction { export class DuplicateGroupRightAction extends BaseDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight'; - static readonly LABEL = nls.localize('duplicateActiveGroupRight', "Duplicate Editor Group Right"); + static readonly LABEL = localize('duplicateActiveGroupRight', "Duplicate Editor Group Right"); constructor( id: string, @@ -923,7 +923,7 @@ export class DuplicateGroupRightAction extends BaseDuplicateGroupAction { export class DuplicateGroupUpAction extends BaseDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp'; - static readonly LABEL = nls.localize('duplicateActiveGroupUp', "Duplicate Editor Group Up"); + static readonly LABEL = localize('duplicateActiveGroupUp', "Duplicate Editor Group Up"); constructor( id: string, @@ -937,7 +937,7 @@ export class DuplicateGroupUpAction extends BaseDuplicateGroupAction { export class DuplicateGroupDownAction extends BaseDuplicateGroupAction { static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown'; - static readonly LABEL = nls.localize('duplicateActiveGroupDown', "Duplicate Editor Group Down"); + static readonly LABEL = localize('duplicateActiveGroupDown', "Duplicate Editor Group Down"); constructor( id: string, @@ -951,7 +951,7 @@ export class DuplicateGroupDownAction extends BaseDuplicateGroupAction { export class MinimizeOtherGroupsAction extends Action { static readonly ID = 'workbench.action.minimizeOtherEditors'; - static readonly LABEL = nls.localize('minimizeOtherEditorGroups', "Maximize Editor Group"); + static readonly LABEL = localize('minimizeOtherEditorGroups', "Maximize Editor Group"); constructor(id: string, label: string, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService) { super(id, label); @@ -965,7 +965,7 @@ export class MinimizeOtherGroupsAction extends Action { export class ResetGroupSizesAction extends Action { static readonly ID = 'workbench.action.evenEditorWidths'; - static readonly LABEL = nls.localize('evenEditorGroups', "Reset Editor Group Sizes"); + static readonly LABEL = localize('evenEditorGroups', "Reset Editor Group Sizes"); constructor(id: string, label: string, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService) { super(id, label); @@ -979,7 +979,7 @@ export class ResetGroupSizesAction extends Action { export class ToggleGroupSizesAction extends Action { static readonly ID = 'workbench.action.toggleEditorWidths'; - static readonly LABEL = nls.localize('toggleEditorWidths', "Toggle Editor Group Sizes"); + static readonly LABEL = localize('toggleEditorWidths', "Toggle Editor Group Sizes"); constructor(id: string, label: string, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService) { super(id, label); @@ -993,7 +993,7 @@ export class ToggleGroupSizesAction extends Action { export class MaximizeGroupAction extends Action { static readonly ID = 'workbench.action.maximizeEditor'; - static readonly LABEL = nls.localize('maximizeEditor', "Maximize Editor Group and Hide Side Bar"); + static readonly LABEL = localize('maximizeEditor', "Maximize Editor Group and Hide Side Bar"); constructor( id: string, @@ -1047,7 +1047,7 @@ export abstract class BaseNavigateEditorAction extends Action { export class OpenNextEditor extends BaseNavigateEditorAction { static readonly ID = 'workbench.action.nextEditor'; - static readonly LABEL = nls.localize('openNextEditor', "Open Next Editor"); + static readonly LABEL = localize('openNextEditor', "Open Next Editor"); constructor( id: string, @@ -1082,7 +1082,7 @@ export class OpenNextEditor extends BaseNavigateEditorAction { export class OpenPreviousEditor extends BaseNavigateEditorAction { static readonly ID = 'workbench.action.previousEditor'; - static readonly LABEL = nls.localize('openPreviousEditor', "Open Previous Editor"); + static readonly LABEL = localize('openPreviousEditor', "Open Previous Editor"); constructor( id: string, @@ -1117,7 +1117,7 @@ export class OpenPreviousEditor extends BaseNavigateEditorAction { export class OpenNextEditorInGroup extends BaseNavigateEditorAction { static readonly ID = 'workbench.action.nextEditorInGroup'; - static readonly LABEL = nls.localize('nextEditorInGroup', "Open Next Editor in Group"); + static readonly LABEL = localize('nextEditorInGroup', "Open Next Editor in Group"); constructor( id: string, @@ -1140,7 +1140,7 @@ export class OpenNextEditorInGroup extends BaseNavigateEditorAction { export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction { static readonly ID = 'workbench.action.previousEditorInGroup'; - static readonly LABEL = nls.localize('openPreviousEditorInGroup', "Open Previous Editor in Group"); + static readonly LABEL = localize('openPreviousEditorInGroup', "Open Previous Editor in Group"); constructor( id: string, @@ -1163,7 +1163,7 @@ export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction { export class OpenFirstEditorInGroup extends BaseNavigateEditorAction { static readonly ID = 'workbench.action.firstEditorInGroup'; - static readonly LABEL = nls.localize('firstEditorInGroup', "Open First Editor in Group"); + static readonly LABEL = localize('firstEditorInGroup', "Open First Editor in Group"); constructor( id: string, @@ -1185,7 +1185,7 @@ export class OpenFirstEditorInGroup extends BaseNavigateEditorAction { export class OpenLastEditorInGroup extends BaseNavigateEditorAction { static readonly ID = 'workbench.action.lastEditorInGroup'; - static readonly LABEL = nls.localize('lastEditorInGroup', "Open Last Editor in Group"); + static readonly LABEL = localize('lastEditorInGroup', "Open Last Editor in Group"); constructor( id: string, @@ -1207,7 +1207,7 @@ export class OpenLastEditorInGroup extends BaseNavigateEditorAction { export class NavigateForwardAction extends Action { static readonly ID = 'workbench.action.navigateForward'; - static readonly LABEL = nls.localize('navigateNext', "Go Forward"); + static readonly LABEL = localize('navigateNext', "Go Forward"); constructor(id: string, label: string, @IHistoryService private readonly historyService: IHistoryService) { super(id, label); @@ -1221,7 +1221,7 @@ export class NavigateForwardAction extends Action { export class NavigateBackwardsAction extends Action { static readonly ID = 'workbench.action.navigateBack'; - static readonly LABEL = nls.localize('navigatePrevious', "Go Back"); + static readonly LABEL = localize('navigatePrevious', "Go Back"); constructor(id: string, label: string, @IHistoryService private readonly historyService: IHistoryService) { super(id, label); @@ -1235,7 +1235,7 @@ export class NavigateBackwardsAction extends Action { export class NavigateToLastEditLocationAction extends Action { static readonly ID = 'workbench.action.navigateToLastEditLocation'; - static readonly LABEL = nls.localize('navigateToLastEditLocation', "Go to Last Edit Location"); + static readonly LABEL = localize('navigateToLastEditLocation', "Go to Last Edit Location"); constructor(id: string, label: string, @IHistoryService private readonly historyService: IHistoryService) { super(id, label); @@ -1249,7 +1249,7 @@ export class NavigateToLastEditLocationAction extends Action { export class NavigateLastAction extends Action { static readonly ID = 'workbench.action.navigateLast'; - static readonly LABEL = nls.localize('navigateLast', "Go Last"); + static readonly LABEL = localize('navigateLast', "Go Last"); constructor(id: string, label: string, @IHistoryService private readonly historyService: IHistoryService) { super(id, label); @@ -1263,7 +1263,7 @@ export class NavigateLastAction extends Action { export class ReopenClosedEditorAction extends Action { static readonly ID = 'workbench.action.reopenClosedEditor'; - static readonly LABEL = nls.localize('reopenClosedEditor', "Reopen Closed Editor"); + static readonly LABEL = localize('reopenClosedEditor', "Reopen Closed Editor"); constructor( id: string, @@ -1281,7 +1281,7 @@ export class ReopenClosedEditorAction extends Action { export class ClearRecentFilesAction extends Action { static readonly ID = 'workbench.action.clearRecentFiles'; - static readonly LABEL = nls.localize('clearRecentFiles', "Clear Recently Opened"); + static readonly LABEL = localize('clearRecentFiles', "Clear Recently Opened"); constructor( id: string, @@ -1305,7 +1305,7 @@ export class ClearRecentFilesAction extends Action { export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action { static readonly ID = 'workbench.action.showEditorsInActiveGroup'; - static readonly LABEL = nls.localize('showEditorsInActiveGroup', "Show Editors in Active Group By Most Recently Used"); + static readonly LABEL = localize('showEditorsInActiveGroup', "Show Editors in Active Group By Most Recently Used"); constructor( id: string, @@ -1323,7 +1323,7 @@ export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action { export class ShowAllEditorsByAppearanceAction extends Action { static readonly ID = 'workbench.action.showAllEditors'; - static readonly LABEL = nls.localize('showAllEditors', "Show All Editors By Appearance"); + static readonly LABEL = localize('showAllEditors', "Show All Editors By Appearance"); constructor( id: string, @@ -1341,7 +1341,7 @@ export class ShowAllEditorsByAppearanceAction extends Action { export class ShowAllEditorsByMostRecentlyUsedAction extends Action { static readonly ID = 'workbench.action.showAllEditorsByMostRecentlyUsed'; - static readonly LABEL = nls.localize('showAllEditorsByMostRecentlyUsed', "Show All Editors By Most Recently Used"); + static readonly LABEL = localize('showAllEditorsByMostRecentlyUsed', "Show All Editors By Most Recently Used"); constructor( id: string, @@ -1382,7 +1382,7 @@ export class BaseQuickAccessEditorAction extends Action { export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditor'; - static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"); + static readonly LABEL = localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"); constructor( id: string, @@ -1397,7 +1397,7 @@ export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccess export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditor'; - static readonly LABEL = nls.localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor"); + static readonly LABEL = localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor"); constructor( id: string, @@ -1412,7 +1412,7 @@ export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEdi export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"); + static readonly LABEL = localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"); constructor( id: string, @@ -1427,7 +1427,7 @@ export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuic export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group"); + static readonly LABEL = localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group"); constructor( id: string, @@ -1442,7 +1442,7 @@ export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends BaseQuickAc export class QuickAccessPreviousEditorFromHistoryAction extends Action { static readonly ID = 'workbench.action.openPreviousEditorFromHistory'; - static readonly LABEL = nls.localize('navigateEditorHistoryByInput', "Quick Open Previous Editor from History"); + static readonly LABEL = localize('navigateEditorHistoryByInput', "Quick Open Previous Editor from History"); constructor( id: string, @@ -1471,7 +1471,7 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action { export class OpenNextRecentlyUsedEditorAction extends Action { static readonly ID = 'workbench.action.openNextRecentlyUsedEditor'; - static readonly LABEL = nls.localize('openNextRecentlyUsedEditor', "Open Next Recently Used Editor"); + static readonly LABEL = localize('openNextRecentlyUsedEditor', "Open Next Recently Used Editor"); constructor( id: string, @@ -1489,7 +1489,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action { export class OpenPreviousRecentlyUsedEditorAction extends Action { static readonly ID = 'workbench.action.openPreviousRecentlyUsedEditor'; - static readonly LABEL = nls.localize('openPreviousRecentlyUsedEditor', "Open Previous Recently Used Editor"); + static readonly LABEL = localize('openPreviousRecentlyUsedEditor', "Open Previous Recently Used Editor"); constructor( id: string, @@ -1507,7 +1507,7 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { export class OpenNextRecentlyUsedEditorInGroupAction extends Action { static readonly ID = 'workbench.action.openNextRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('openNextRecentlyUsedEditorInGroup', "Open Next Recently Used Editor In Group"); + static readonly LABEL = localize('openNextRecentlyUsedEditorInGroup', "Open Next Recently Used Editor In Group"); constructor( id: string, @@ -1526,7 +1526,7 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action { export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { static readonly ID = 'workbench.action.openPreviousRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('openPreviousRecentlyUsedEditorInGroup', "Open Previous Recently Used Editor In Group"); + static readonly LABEL = localize('openPreviousRecentlyUsedEditorInGroup', "Open Previous Recently Used Editor In Group"); constructor( id: string, @@ -1545,7 +1545,7 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { export class ClearEditorHistoryAction extends Action { static readonly ID = 'workbench.action.clearEditorHistory'; - static readonly LABEL = nls.localize('clearEditorHistory', "Clear Editor History"); + static readonly LABEL = localize('clearEditorHistory', "Clear Editor History"); constructor( id: string, @@ -1565,7 +1565,7 @@ export class ClearEditorHistoryAction extends Action { export class MoveEditorLeftInGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorLeftInGroup'; - static readonly LABEL = nls.localize('moveEditorLeft', "Move Editor Left"); + static readonly LABEL = localize('moveEditorLeft', "Move Editor Left"); constructor( id: string, @@ -1579,7 +1579,7 @@ export class MoveEditorLeftInGroupAction extends ExecuteCommandAction { export class MoveEditorRightInGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorRightInGroup'; - static readonly LABEL = nls.localize('moveEditorRight', "Move Editor Right"); + static readonly LABEL = localize('moveEditorRight', "Move Editor Right"); constructor( id: string, @@ -1593,7 +1593,7 @@ export class MoveEditorRightInGroupAction extends ExecuteCommandAction { export class MoveEditorToPreviousGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToPreviousGroup'; - static readonly LABEL = nls.localize('moveEditorToPreviousGroup', "Move Editor into Previous Group"); + static readonly LABEL = localize('moveEditorToPreviousGroup', "Move Editor into Previous Group"); constructor( id: string, @@ -1607,7 +1607,7 @@ export class MoveEditorToPreviousGroupAction extends ExecuteCommandAction { export class MoveEditorToNextGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToNextGroup'; - static readonly LABEL = nls.localize('moveEditorToNextGroup', "Move Editor into Next Group"); + static readonly LABEL = localize('moveEditorToNextGroup', "Move Editor into Next Group"); constructor( id: string, @@ -1621,7 +1621,7 @@ export class MoveEditorToNextGroupAction extends ExecuteCommandAction { export class MoveEditorToAboveGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToAboveGroup'; - static readonly LABEL = nls.localize('moveEditorToAboveGroup', "Move Editor into Above Group"); + static readonly LABEL = localize('moveEditorToAboveGroup', "Move Editor into Above Group"); constructor( id: string, @@ -1635,7 +1635,7 @@ export class MoveEditorToAboveGroupAction extends ExecuteCommandAction { export class MoveEditorToBelowGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToBelowGroup'; - static readonly LABEL = nls.localize('moveEditorToBelowGroup', "Move Editor into Below Group"); + static readonly LABEL = localize('moveEditorToBelowGroup', "Move Editor into Below Group"); constructor( id: string, @@ -1649,7 +1649,7 @@ export class MoveEditorToBelowGroupAction extends ExecuteCommandAction { export class MoveEditorToLeftGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToLeftGroup'; - static readonly LABEL = nls.localize('moveEditorToLeftGroup', "Move Editor into Left Group"); + static readonly LABEL = localize('moveEditorToLeftGroup', "Move Editor into Left Group"); constructor( id: string, @@ -1663,7 +1663,7 @@ export class MoveEditorToLeftGroupAction extends ExecuteCommandAction { export class MoveEditorToRightGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToRightGroup'; - static readonly LABEL = nls.localize('moveEditorToRightGroup', "Move Editor into Right Group"); + static readonly LABEL = localize('moveEditorToRightGroup', "Move Editor into Right Group"); constructor( id: string, @@ -1677,7 +1677,7 @@ export class MoveEditorToRightGroupAction extends ExecuteCommandAction { export class MoveEditorToFirstGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToFirstGroup'; - static readonly LABEL = nls.localize('moveEditorToFirstGroup', "Move Editor into First Group"); + static readonly LABEL = localize('moveEditorToFirstGroup', "Move Editor into First Group"); constructor( id: string, @@ -1691,7 +1691,7 @@ export class MoveEditorToFirstGroupAction extends ExecuteCommandAction { export class MoveEditorToLastGroupAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.moveEditorToLastGroup'; - static readonly LABEL = nls.localize('moveEditorToLastGroup', "Move Editor into Last Group"); + static readonly LABEL = localize('moveEditorToLastGroup', "Move Editor into Last Group"); constructor( id: string, @@ -1705,7 +1705,7 @@ export class MoveEditorToLastGroupAction extends ExecuteCommandAction { export class EditorLayoutSingleAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutSingle'; - static readonly LABEL = nls.localize('editorLayoutSingle', "Single Column Editor Layout"); + static readonly LABEL = localize('editorLayoutSingle', "Single Column Editor Layout"); constructor( id: string, @@ -1719,7 +1719,7 @@ export class EditorLayoutSingleAction extends ExecuteCommandAction { export class EditorLayoutTwoColumnsAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutTwoColumns'; - static readonly LABEL = nls.localize('editorLayoutTwoColumns', "Two Columns Editor Layout"); + static readonly LABEL = localize('editorLayoutTwoColumns', "Two Columns Editor Layout"); constructor( id: string, @@ -1733,7 +1733,7 @@ export class EditorLayoutTwoColumnsAction extends ExecuteCommandAction { export class EditorLayoutThreeColumnsAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutThreeColumns'; - static readonly LABEL = nls.localize('editorLayoutThreeColumns', "Three Columns Editor Layout"); + static readonly LABEL = localize('editorLayoutThreeColumns', "Three Columns Editor Layout"); constructor( id: string, @@ -1747,7 +1747,7 @@ export class EditorLayoutThreeColumnsAction extends ExecuteCommandAction { export class EditorLayoutTwoRowsAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutTwoRows'; - static readonly LABEL = nls.localize('editorLayoutTwoRows', "Two Rows Editor Layout"); + static readonly LABEL = localize('editorLayoutTwoRows', "Two Rows Editor Layout"); constructor( id: string, @@ -1761,7 +1761,7 @@ export class EditorLayoutTwoRowsAction extends ExecuteCommandAction { export class EditorLayoutThreeRowsAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutThreeRows'; - static readonly LABEL = nls.localize('editorLayoutThreeRows', "Three Rows Editor Layout"); + static readonly LABEL = localize('editorLayoutThreeRows', "Three Rows Editor Layout"); constructor( id: string, @@ -1775,7 +1775,7 @@ export class EditorLayoutThreeRowsAction extends ExecuteCommandAction { export class EditorLayoutTwoByTwoGridAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutTwoByTwoGrid'; - static readonly LABEL = nls.localize('editorLayoutTwoByTwoGrid', "Grid Editor Layout (2x2)"); + static readonly LABEL = localize('editorLayoutTwoByTwoGrid', "Grid Editor Layout (2x2)"); constructor( id: string, @@ -1789,7 +1789,7 @@ export class EditorLayoutTwoByTwoGridAction extends ExecuteCommandAction { export class EditorLayoutTwoColumnsBottomAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutTwoColumnsBottom'; - static readonly LABEL = nls.localize('editorLayoutTwoColumnsBottom', "Two Columns Bottom Editor Layout"); + static readonly LABEL = localize('editorLayoutTwoColumnsBottom', "Two Columns Bottom Editor Layout"); constructor( id: string, @@ -1803,7 +1803,7 @@ export class EditorLayoutTwoColumnsBottomAction extends ExecuteCommandAction { export class EditorLayoutTwoRowsRightAction extends ExecuteCommandAction { static readonly ID = 'workbench.action.editorLayoutTwoRowsRight'; - static readonly LABEL = nls.localize('editorLayoutTwoRowsRight', "Two Rows Right Editor Layout"); + static readonly LABEL = localize('editorLayoutTwoRowsRight', "Two Rows Right Editor Layout"); constructor( id: string, @@ -1833,7 +1833,7 @@ export class BaseCreateEditorGroupAction extends Action { export class NewEditorGroupLeftAction extends BaseCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupLeft'; - static readonly LABEL = nls.localize('newEditorLeft', "New Editor Group to the Left"); + static readonly LABEL = localize('newEditorLeft', "New Editor Group to the Left"); constructor( id: string, @@ -1847,7 +1847,7 @@ export class NewEditorGroupLeftAction extends BaseCreateEditorGroupAction { export class NewEditorGroupRightAction extends BaseCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupRight'; - static readonly LABEL = nls.localize('newEditorRight', "New Editor Group to the Right"); + static readonly LABEL = localize('newEditorRight', "New Editor Group to the Right"); constructor( id: string, @@ -1861,7 +1861,7 @@ export class NewEditorGroupRightAction extends BaseCreateEditorGroupAction { export class NewEditorGroupAboveAction extends BaseCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupAbove'; - static readonly LABEL = nls.localize('newEditorAbove', "New Editor Group Above"); + static readonly LABEL = localize('newEditorAbove', "New Editor Group Above"); constructor( id: string, @@ -1875,7 +1875,7 @@ export class NewEditorGroupAboveAction extends BaseCreateEditorGroupAction { export class NewEditorGroupBelowAction extends BaseCreateEditorGroupAction { static readonly ID = 'workbench.action.newGroupBelow'; - static readonly LABEL = nls.localize('newEditorBelow', "New Editor Group Below"); + static readonly LABEL = localize('newEditorBelow', "New Editor Group Below"); constructor( id: string, @@ -1889,7 +1889,7 @@ export class NewEditorGroupBelowAction extends BaseCreateEditorGroupAction { export class ReopenResourcesAction extends Action { static readonly ID = 'workbench.action.reopenWithEditor'; - static readonly LABEL = nls.localize('workbench.action.reopenWithEditor', "Reopen Editor With..."); + static readonly LABEL = localize('workbench.action.reopenWithEditor', "Reopen Editor With..."); constructor( id: string, @@ -1920,7 +1920,7 @@ export class ReopenResourcesAction extends Action { export class ToggleEditorTypeAction extends Action { static readonly ID = 'workbench.action.toggleEditorType'; - static readonly LABEL = nls.localize('workbench.action.toggleEditorType', "Toggle Editor Type"); + static readonly LABEL = localize('workbench.action.toggleEditorType', "Toggle Editor Type"); constructor( id: string, diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index f9fd2d57164..1bf46e607df 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { isObject, isString, isUndefined, isNumber, withNullAsUndefined } from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -103,11 +103,11 @@ function registerActiveEditorMoveCommand(): void { primary: 0, handler: (accessor, args) => moveActiveEditor(args, accessor), description: { - description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"), + description: localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"), args: [ { - name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"), - description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."), + name: localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"), + description: localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."), constraint: isActiveEditorMoveArg, schema: { 'type': 'object', @@ -408,10 +408,10 @@ function registerDiffEditorCommands(): void { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, title: { - value: nls.localize('toggleInlineView', "Toggle Inline View"), + value: localize('toggleInlineView', "Toggle Inline View"), original: 'Compare: Toggle Inline View' }, - category: nls.localize('compare', "Compare") + category: localize('compare', "Compare") }, when: ContextKeyExpr.has('textCompareEditorActive') }); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 90d33b124a2..efd6650f5be 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; +import { Dimension, $, clearNode } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; @@ -51,7 +51,7 @@ export class SideBySideEditor extends EditorPane { private secondaryEditorContainer: HTMLElement | undefined; private splitview: SplitView | undefined; - private dimension: DOM.Dimension = new DOM.Dimension(0, 0); + private dimension: Dimension = new Dimension(0, 0); private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>()); @@ -73,19 +73,19 @@ export class SideBySideEditor extends EditorPane { const splitview = this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL })); this._register(this.splitview.onDidSashReset(() => splitview.distributeViewSizes())); - this.secondaryEditorContainer = DOM.$('.secondary-editor-container'); + this.secondaryEditorContainer = $('.secondary-editor-container'); this.splitview.addView({ element: this.secondaryEditorContainer, - layout: size => this.secondaryEditorPane?.layout(new DOM.Dimension(size, this.dimension.height)), + layout: size => this.secondaryEditorPane?.layout(new Dimension(size, this.dimension.height)), minimumSize: 220, maximumSize: Number.POSITIVE_INFINITY, onDidChange: Event.None }, Sizing.Distribute); - this.primaryEditorContainer = DOM.$('.primary-editor-container'); + this.primaryEditorContainer = $('.primary-editor-container'); this.splitview.addView({ element: this.primaryEditorContainer, - layout: size => this.primaryEditorPane?.layout(new DOM.Dimension(size, this.dimension.height)), + layout: size => this.primaryEditorPane?.layout(new Dimension(size, this.dimension.height)), minimumSize: 220, maximumSize: Number.POSITIVE_INFINITY, onDidChange: Event.None @@ -139,7 +139,7 @@ export class SideBySideEditor extends EditorPane { } } - layout(dimension: DOM.Dimension): void { + layout(dimension: Dimension): void { this.dimension = dimension; const splitview = assertIsDefined(this.splitview); @@ -238,11 +238,11 @@ export class SideBySideEditor extends EditorPane { } if (this.secondaryEditorContainer) { - DOM.clearNode(this.secondaryEditorContainer); + clearNode(this.secondaryEditorContainer); } if (this.primaryEditorContainer) { - DOM.clearNode(this.primaryEditorContainer); + clearNode(this.primaryEditorContainer); } } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 7ae3677e182..54eca9b3349 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as objects from 'vs/base/common/objects'; +import { localize } from 'vs/nls'; +import { deepClone } from 'vs/base/common/objects'; import { isFunction, isObject, isArray, assertIsDefined, withUndefinedAsNull } from 'vs/base/common/types'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -99,7 +99,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan return this.input.getName(); } - return nls.localize('textDiffEditor', "Text Diff Editor"); + return localize('textDiffEditor', "Text Diff Editor"); } createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { @@ -236,7 +236,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan // Handle diff editor specially by merging in diffEditor configuration if (isObject(configuration.diffEditor)) { - const diffEditorConfiguration = objects.deepClone(configuration.diffEditor); + const diffEditorConfiguration: IDiffEditorOptions = deepClone(configuration.diffEditor); // User settings defines `diffEditor.codeLens`, but here we rename that to `diffEditor.diffCodeLens` to avoid collisions with `editor.codeLens`. diffEditorConfiguration.diffCodeLens = diffEditorConfiguration.codeLens; diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 2bb2640dc79..63135239171 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { assertIsDefined, isFunction, withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditor, getCodeEditor, IPasteEvent } from 'vs/editor/browser/editorBrowser'; import { TextEditorOptions, EditorInput, EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; @@ -50,7 +50,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { return this.input.getName(); } - return nls.localize('textEditor', "Text Editor"); + return localize('textEditor', "Text Editor"); } async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index f45f9ed0a6f..8677017e740 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -7,9 +7,9 @@ import 'vs/css!./media/titlecontrol'; import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem, SubmenuAction } from 'vs/base/common/actions'; +import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, SubmenuAction } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/src/vs/workbench/browser/parts/editor/untitledHint.ts b/src/vs/workbench/browser/parts/editor/untitledHint.ts new file mode 100644 index 00000000000..f29cec7444a --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/untitledHint.ts @@ -0,0 +1,175 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { localize } from 'vs/nls'; +import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { ChangeModeAction } from 'vs/workbench/browser/parts/editor/editorStatus'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { Schemas } from 'vs/base/common/network'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; +const $ = dom.$; + +const untitledHintSetting = 'workbench.editor.untitled.hint'; +export class UntitledHintContribution implements IEditorContribution { + + public static readonly ID = 'editor.contrib.untitledHint'; + + private toDispose: IDisposable[]; + private untitledHintContentWidget: UntitledHintContentWidget | undefined; + private button: FloatingClickWidget | undefined; + + constructor( + private editor: ICodeEditor, + @ICommandService private readonly commandService: ICommandService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IThemeService private readonly themeService: IThemeService + ) { + this.toDispose = []; + this.toDispose.push(this.editor.onDidChangeModel(() => this.update())); + this.toDispose.push(this.editor.onDidChangeModelLanguage(() => this.update())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(untitledHintSetting)) { + this.update(); + } + })); + this.update(); + } + + private update(): void { + this.untitledHintContentWidget?.dispose(); + this.button?.dispose(); + const untitledHintMode = this.configurationService.getValue(untitledHintSetting); + const model = this.editor.getModel(); + + if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID) { + if (untitledHintMode === 'text') { + this.untitledHintContentWidget = new UntitledHintContentWidget(this.editor, this.commandService, this.configurationService, this.keybindingService); + } + if (untitledHintMode === 'button') { + this.button = new FloatingClickWidget(this.editor, localize('selectALanguage', "Select a Language"), ChangeModeAction.ID, this.keybindingService, this.themeService); + this.toDispose.push(this.button.onClick(async () => { + await this.commandService.executeCommand(ChangeModeAction.ID); + this.editor.focus(); + })); + this.button.render(); + } + } + } + + dispose(): void { + dispose(this.toDispose); + this.untitledHintContentWidget?.dispose(); + } +} + +class UntitledHintContentWidget implements IContentWidget { + + private static readonly ID = 'editor.widget.untitledHint'; + + private domNode: HTMLElement | undefined; + private toDispose: IDisposable[]; + + constructor( + private readonly editor: ICodeEditor, + private readonly commandService: ICommandService, + private readonly configurationService: IConfigurationService, + private readonly keybindingService: IKeybindingService + ) { + this.toDispose = []; + this.toDispose.push(editor.onDidChangeModelContent(() => this.onDidChangeModelContent())); + this.onDidChangeModelContent(); + } + + private onDidChangeModelContent(): void { + if (this.editor.getValue() === '') { + this.editor.addContentWidget(this); + } else { + this.editor.removeContentWidget(this); + } + } + + getId(): string { + return UntitledHintContentWidget.ID; + } + + // Select a language to get started. Start typing to dismiss, or don't show this again. + getDomNode(): HTMLElement { + if (!this.domNode) { + this.domNode = $('.untitled-hint'); + this.domNode.style.width = 'max-content'; + const select = $('span'); + select.innerText = localize('select', "Select a "); + this.domNode.appendChild(select); + const language = $('span.language-mode.detected-link-active'); + const keybinding = this.keybindingService.lookupKeybinding(ChangeModeAction.ID); + const keybindingLabel = keybinding?.getLabel(); + const keybindingWithBrackets = keybindingLabel ? `(${keybindingLabel})` : ''; + language.innerText = localize('language', "language {0}", keybindingWithBrackets); + this.domNode.appendChild(language); + const toGetStarted = $('span'); + toGetStarted.innerText = localize('toGetStarted', " to get started. Start typing to dismiss, or ",); + this.domNode.appendChild(toGetStarted); + + const dontShow = $('span.detected-link-active'); + dontShow.innerText = localize('dontshow', "don't show"); + this.domNode.appendChild(dontShow); + + const thisAgain = $('span'); + thisAgain.innerText = localize('thisAgain', " this again."); + this.domNode.appendChild(thisAgain); + + this.toDispose.push(dom.addDisposableListener(language, 'click', async e => { + e.stopPropagation(); + await this.commandService.executeCommand(ChangeModeAction.ID); + this.editor.focus(); + })); + + this.toDispose.push(dom.addDisposableListener(dontShow, 'click', () => { + this.configurationService.updateValue(untitledHintSetting, 'hidden'); + this.dispose(); + this.editor.focus(); + })); + + this.toDispose.push(dom.addDisposableListener(this.domNode, 'click', () => { + this.editor.focus(); + })); + + this.domNode.style.fontFamily = DEFAULT_FONT_FAMILY; + this.domNode.style.fontStyle = 'italic'; + this.domNode.style.paddingLeft = '4px'; + } + + return this.domNode; + } + + getPosition(): IContentWidgetPosition | null { + return { + position: { lineNumber: 1, column: 1 }, + preference: [ContentWidgetPositionPreference.EXACT] + }; + } + + dispose(): void { + this.editor.removeContentWidget(this); + dispose(this.toDispose); + } +} + +registerThemingParticipant((theme, collector) => { + const inputPlaceholderForegroundColor = theme.getColor(inputPlaceholderForeground); + if (inputPlaceholderForegroundColor) { + collector.addRule(`.monaco-editor .contentWidgets .untitled-hint { color: ${inputPlaceholderForegroundColor}; }`); + } +}); diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 372e78f8a50..0d447ee9d3d 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panelpart'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Action } from 'vs/base/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -21,14 +21,14 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ViewContainerLocationToString, ViewContainerLocation } from 'vs/workbench/common/views'; -const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, nls.localize('maximizeIcon', 'Icon to maximize a panel.')); -const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, nls.localize('restoreIcon', 'Icon to restore a panel.')); -const closeIcon = registerIcon('panel-close', Codicon.close, nls.localize('closeIcon', 'Icon to close a panel.')); +const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, localize('maximizeIcon', 'Icon to maximize a panel.')); +const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, localize('restoreIcon', 'Icon to restore a panel.')); +const closeIcon = registerIcon('panel-close', Codicon.close, localize('closeIcon', 'Icon to close a panel.')); export class TogglePanelAction extends Action { static readonly ID = 'workbench.action.togglePanel'; - static readonly LABEL = nls.localize('togglePanel', "Toggle Panel"); + static readonly LABEL = localize('togglePanel', "Toggle Panel"); constructor( id: string, @@ -46,7 +46,7 @@ export class TogglePanelAction extends Action { class FocusPanelAction extends Action { static readonly ID = 'workbench.action.focusPanel'; - static readonly LABEL = nls.localize('focusPanel', "Focus into Panel"); + static readonly LABEL = localize('focusPanel', "Focus into Panel"); constructor( id: string, @@ -97,9 +97,9 @@ function createPositionPanelActionConfig(id: string, alias: string, label: strin } export const PositionPanelActionConfigs: PanelActionConfig[] = [ - createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Move Panel Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), - createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Move Panel Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), - createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Move Panel To Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), + createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Move Panel Left', localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), + createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Move Panel Right', localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), + createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Move Panel To Bottom', localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), ]; const positionByActionId = new Map(PositionPanelActionConfigs.map(config => [config.id, config.value])); @@ -191,7 +191,7 @@ export class SwitchPanelViewAction extends Action { export class PreviousPanelViewAction extends SwitchPanelViewAction { static readonly ID = 'workbench.action.previousPanelView'; - static readonly LABEL = nls.localize('previousPanelView', 'Previous Panel View'); + static readonly LABEL = localize('previousPanelView', 'Previous Panel View'); constructor( id: string, @@ -209,7 +209,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction { export class NextPanelViewAction extends SwitchPanelViewAction { static readonly ID = 'workbench.action.nextPanelView'; - static readonly LABEL = nls.localize('nextPanelView', 'Next Panel View'); + static readonly LABEL = localize('nextPanelView', 'Next Panel View'); constructor( id: string, @@ -234,12 +234,12 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.toggleMaximizedPanel', - title: { value: nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel"), original: 'Toggle Maximized Panel' }, - tooltip: nls.localize('maximizePanel', "Maximize Panel Size"), + title: { value: localize('toggleMaximizedPanel', "Toggle Maximized Panel"), original: 'Toggle Maximized Panel' }, + tooltip: localize('maximizePanel', "Maximize Panel Size"), category: CATEGORIES.View, f1: true, icon: maximizeIcon, - toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: nls.localize('minimizePanel', "Restore Panel Size") }, + toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: localize('minimizePanel', "Restore Panel Size") }, menu: [{ id: MenuId.PanelTitle, group: 'navigation', @@ -266,7 +266,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.closePanel', - title: { value: nls.localize('closePanel', "Close Panel"), original: 'Close Panel' }, + title: { value: localize('closePanel', "Close Panel"), original: 'Close Panel' }, category: CATEGORIES.View, icon: closeIcon, menu: [{ @@ -291,7 +291,7 @@ MenuRegistry.appendMenuItems([ group: '2_workbench_layout', command: { id: TogglePanelAction.ID, - title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"), + title: localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"), toggled: ActivePanelContext }, order: 5 @@ -302,7 +302,7 @@ MenuRegistry.appendMenuItems([ group: '3_workbench_layout_move', command: { id: TogglePanelAction.ID, - title: { value: nls.localize('hidePanel', "Hide Panel"), original: 'Hide Panel' }, + title: { value: localize('hidePanel', "Hide Panel"), original: 'Hide Panel' }, }, when: ContextKeyExpr.and(PanelVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))), order: 2 diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 671a05822eb..ed77ad7b157 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/sidebarpart'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { CompositePart } from 'vs/workbench/browser/parts/compositePart'; import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; @@ -307,7 +307,7 @@ class FocusSideBarAction extends Action2 { constructor() { super({ id: 'workbench.action.focusSideBar', - title: { value: nls.localize('focusSideBar', "Focus into Side Bar"), original: 'Focus into Side Bar' }, + title: { value: localize('focusSideBar', "Focus into Side Bar"), original: 'Focus into Side Bar' }, category: CATEGORIES.View, f1: true, keybinding: { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index aa9d9f05f9a..b339140fdd9 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/statusbarpart'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; @@ -364,7 +364,7 @@ class ToggleStatusbarEntryVisibilityAction extends Action { class HideStatusbarEntryAction extends Action { constructor(id: string, name: string, private model: StatusbarViewModel) { - super(id, nls.localize('hide', "Hide '{0}'", name), undefined, true); + super(id, localize('hide', "Hide '{0}'", name), undefined, true); } async run(): Promise { @@ -613,7 +613,7 @@ export class StatusbarPart extends Part implements IStatusbarService { const actions: IAction[] = []; // Provide an action to hide the status bar at last - actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar"))); + actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, localize('hideStatusBar', "Hide Status Bar"))); actions.push(new Separator()); // Show an entry per known status entry diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 96f49a70965..0600e3ae769 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; -import * as DOM from 'vs/base/browser/dom'; +import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; @@ -63,14 +63,14 @@ export abstract class MenubarControl extends Disposable { }; protected topLevelTitles: { [menu: string]: string } = { - 'File': nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File"), - 'Edit': nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit"), - 'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), - 'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), - 'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), - 'Run': nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run"), - 'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), - 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") + 'File': localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File"), + 'Edit': localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit"), + 'Selection': localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"), + 'View': localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"), + 'Go': localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"), + 'Run': localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run"), + 'Terminal': localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), + 'Help': localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") }; protected recentlyOpened: IRecentlyOpened = { files: [], workspaces: [] }; @@ -271,10 +271,10 @@ export abstract class MenubarControl extends Disposable { return; } - const message = nls.localize('menubar.customTitlebarAccessibilityNotification', "Accessibility support is enabled for you. For the most accessible experience, we recommend the custom title bar style."); + const message = localize('menubar.customTitlebarAccessibilityNotification', "Accessibility support is enabled for you. For the most accessible experience, we recommend the custom title bar style."); this.notificationService.prompt(Severity.Info, message, [ { - label: nls.localize('goToSetting', "Open Settings"), + label: localize('goToSetting', "Open Settings"), run: () => { return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); } @@ -432,7 +432,7 @@ export class CustomMenubarControl extends MenubarControl { constructor() { super({ id: `workbench.actions.menubar.focus`, - title: { value: nls.localize('focusMenu', "Focus Application Menu"), original: 'Focus Application Menu' }, + title: { value: localize('focusMenu', "Focus Application Menu"), original: 'Focus Application Menu' }, keybinding: { primary: KeyCode.F10, weight: KeybindingWeight.WorkbenchContrib, @@ -459,28 +459,28 @@ export class CustomMenubarControl extends MenubarControl { return null; case StateType.Idle: - return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => + return new Action('update.check', localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => this.updateService.checkForUpdates(this.environmentService.sessionId)); case StateType.CheckingForUpdates: - return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false); + return new Action('update.checking', localize('checkingForUpdates', "Checking for Updates..."), undefined, false); case StateType.AvailableForDownload: - return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Update"), undefined, true, () => + return new Action('update.downloadNow', localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Update"), undefined, true, () => this.updateService.downloadUpdate()); case StateType.Downloading: - return new Action('update.downloading', nls.localize('DownloadingUpdate', "Downloading Update..."), undefined, false); + return new Action('update.downloading', localize('DownloadingUpdate', "Downloading Update..."), undefined, false); case StateType.Downloaded: - return new Action('update.install', nls.localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), undefined, true, () => + return new Action('update.install', localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), undefined, true, () => this.updateService.applyUpdate()); case StateType.Updating: - return new Action('update.updating', nls.localize('installingUpdate', "Installing Update..."), undefined, false); + return new Action('update.updating', localize('installingUpdate', "Installing Update..."), undefined, false); case StateType.Ready: - return new Action('update.restart', nls.localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update"), undefined, true, () => + return new Action('update.restart', localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update"), undefined, true, () => this.updateService.quitAndInstall()); } } @@ -574,11 +574,11 @@ export class CustomMenubarControl extends MenubarControl { this._register(this.menubar.onVisibilityChange(e => this.onDidVisibilityChange(e))); // Before we focus the menubar, stop updates to it so that focus-related context keys will work - this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_IN, () => { + this._register(addDisposableListener(this.container, EventType.FOCUS_IN, () => { this.focusInsideMenubar = true; })); - this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_OUT, () => { + this._register(addDisposableListener(this.container, EventType.FOCUS_OUT, () => { this.focusInsideMenubar = false; })); @@ -722,7 +722,7 @@ export class CustomMenubarControl extends MenubarControl { const webNavigationActions: IAction[] = []; const href = this.environmentService.options?.homeIndicator?.href; if (href) { - webNavigationActions.push(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, + webNavigationActions.push(new Action('goHome', localize('goHome', "Go Home"), undefined, true, async (event?: MouseEvent) => { if ((!isMacintosh && event?.ctrlKey) || (isMacintosh && event?.metaKey)) { window.open(href, '_blank'); @@ -784,7 +784,7 @@ export class CustomMenubarControl extends MenubarControl { protected registerListeners(): void { super.registerListeners(); - this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { + this._register(addDisposableListener(window, EventType.RESIZE, () => { if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) { this.menubar.blur(); } @@ -805,12 +805,12 @@ export class CustomMenubarControl extends MenubarControl { return this._onFocusStateChange.event; } - getMenubarItemsDimensions(): DOM.Dimension { + getMenubarItemsDimensions(): Dimension { if (this.menubar) { - return new DOM.Dimension(this.menubar.getWidth(), this.menubar.getHeight()); + return new Dimension(this.menubar.getWidth(), this.menubar.getHeight()); } - return new DOM.Dimension(0, 0); + return new Dimension(0, 0); } create(parent: HTMLElement): HTMLElement { @@ -824,7 +824,7 @@ export class CustomMenubarControl extends MenubarControl { return this.container; } - layout(dimension: DOM.Dimension) { + layout(dimension: Dimension) { if (this.container) { this.container.style.height = `${dimension.height}px`; } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index f8961757153..04482e7d7f1 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -19,7 +19,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Event, Emitter } from 'vs/base/common/event'; -import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; import { createAndFillInContextMenuActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -27,7 +27,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { FileKind } from 'vs/platform/files/common/files'; @@ -873,8 +873,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer{ diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 4f1ecbcd076..5028f89cc5a 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/style'; - import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index bdecdbc0c90..9aea863b585 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -302,10 +302,10 @@ class BrowserMain extends Disposable { } private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { - const storageService = new BrowserStorageService(environmentService, fileService); + const storageService = new BrowserStorageService(payload, environmentService, fileService); try { - await storageService.initialize(payload); + await storageService.initialize(); return storageService; } catch (error) { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index e1353541362..c41f2503464 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; @@ -22,53 +22,53 @@ import { isStandalone } from 'vs/base/browser/browser'; type: 'string', enum: ['default', 'large'], enumDescriptions: [ - nls.localize('workbench.editor.titleScrollbarSizing.default', "The default size."), - nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabbed more easily with the mouse") + localize('workbench.editor.titleScrollbarSizing.default', "The default size."), + localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabbed more easily with the mouse") ], - description: nls.localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."), + description: localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."), default: 'default', }, 'workbench.editor.showTabs': { 'type': 'boolean', - 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), + 'description': localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, 'workbench.editor.wrapTabs': { 'type': 'boolean', - 'markdownDescription': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is disabled."), 'default': false }, 'workbench.editor.scrollToSwitchTabs': { 'type': 'boolean', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."), 'default': false }, 'workbench.editor.highlightModifiedTabs': { 'type': 'boolean', - 'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."), 'default': false }, 'workbench.editor.decorations.badges': { 'type': 'boolean', - 'markdownDescription': nls.localize('decorations.badges', "Controls whether editor file decorations should use badges."), + 'markdownDescription': localize('decorations.badges', "Controls whether editor file decorations should use badges."), 'default': false }, 'workbench.editor.decorations.colors': { 'type': 'boolean', - 'markdownDescription': nls.localize('decorations.colors', "Controls whether editor file decorations should use colors."), + 'markdownDescription': localize('decorations.colors', "Controls whether editor file decorations should use colors."), 'default': false }, 'workbench.editor.labelFormat': { 'type': 'string', 'enum': ['default', 'short', 'medium', 'long'], 'enumDescriptions': [ - nls.localize('workbench.editor.labelFormat.default', "Show the name of the file. When tabs are enabled and two files have the same name in one group the distinguishing sections of each file's path are added. When tabs are disabled, the path relative to the workspace folder is shown if the editor is active."), - nls.localize('workbench.editor.labelFormat.short', "Show the name of the file followed by its directory name."), - nls.localize('workbench.editor.labelFormat.medium', "Show the name of the file followed by its path relative to the workspace folder."), - nls.localize('workbench.editor.labelFormat.long', "Show the name of the file followed by its absolute path.") + localize('workbench.editor.labelFormat.default', "Show the name of the file. When tabs are enabled and two files have the same name in one group the distinguishing sections of each file's path are added. When tabs are disabled, the path relative to the workspace folder is shown if the editor is active."), + localize('workbench.editor.labelFormat.short', "Show the name of the file followed by its directory name."), + localize('workbench.editor.labelFormat.medium', "Show the name of the file followed by its path relative to the workspace folder."), + localize('workbench.editor.labelFormat.long', "Show the name of the file followed by its absolute path.") ], 'default': 'default', - 'description': nls.localize({ + 'description': localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'tabDescription' }, "Controls the format of the label for an editor."), @@ -77,235 +77,241 @@ import { isStandalone } from 'vs/base/browser/browser'; 'type': 'string', 'enum': ['content', 'name'], 'enumDescriptions': [ - nls.localize('workbench.editor.untitled.labelFormat.content', "The name of the untitled file is derived from the contents of its first line unless it has an associated file path. It will fallback to the name in case the line is empty or contains no word characters."), - nls.localize('workbench.editor.untitled.labelFormat.name', "The name of the untitled file is not derived from the contents of the file."), + localize('workbench.editor.untitled.labelFormat.content', "The name of the untitled file is derived from the contents of its first line unless it has an associated file path. It will fallback to the name in case the line is empty or contains no word characters."), + localize('workbench.editor.untitled.labelFormat.name', "The name of the untitled file is not derived from the contents of the file."), ], 'default': 'content', - 'description': nls.localize({ + 'description': localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'untitledLabelFormat' }, "Controls the format of the label for an untitled editor."), }, + 'workbench.editor.untitled.hint': { + 'type': 'string', + 'enum': ['text', 'button', 'hidden'], + 'default': 'text', + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled hint should be inline text in the editor or a floating button or hidden.") + }, 'workbench.editor.tabCloseButton': { 'type': 'string', 'enum': ['left', 'right', 'off'], 'default': 'right', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is disabled.") }, 'workbench.editor.tabSizing': { 'type': 'string', 'enum': ['fit', 'shrink'], 'default': 'fit', 'enumDescriptions': [ - nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), - nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") + localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), + localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") ], - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled.") }, 'workbench.editor.pinnedTabSizing': { 'type': 'string', 'enum': ['normal', 'compact', 'shrink'], 'default': 'normal', 'enumDescriptions': [ - nls.localize('workbench.editor.pinnedTabSizing.normal', "A pinned tab inherits the look of non pinned tabs."), - nls.localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."), - nls.localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name.") + localize('workbench.editor.pinnedTabSizing.normal', "A pinned tab inherits the look of non pinned tabs."), + localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."), + localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name.") ], - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.") }, 'workbench.editor.splitSizing': { 'type': 'string', 'enum': ['distribute', 'split'], 'default': 'distribute', 'enumDescriptions': [ - nls.localize('workbench.editor.splitSizingDistribute', "Splits all the editor groups to equal parts."), - nls.localize('workbench.editor.splitSizingSplit', "Splits the active editor group to equal parts.") + localize('workbench.editor.splitSizingDistribute', "Splits all the editor groups to equal parts."), + localize('workbench.editor.splitSizingSplit', "Splits the active editor group to equal parts.") ], - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'splitSizing' }, "Controls the sizing of editor groups when splitting them.") + 'description': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'splitSizing' }, "Controls the sizing of editor groups when splitting them.") }, 'workbench.editor.splitOnDragAndDrop': { 'type': 'boolean', 'default': true, - 'description': nls.localize('splitOnDragAndDrop', "Controls if editor groups can be split from drag and drop operations by dropping an editor or file on the edges of the editor area.") + 'description': localize('splitOnDragAndDrop', "Controls if editor groups can be split from drag and drop operations by dropping an editor or file on the edges of the editor area.") }, 'workbench.editor.focusRecentEditorAfterClose': { 'type': 'boolean', - 'description': nls.localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."), + 'description': localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."), 'default': true }, 'workbench.editor.showIcons': { 'type': 'boolean', - 'description': nls.localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires a file icon theme to be enabled as well."), + 'description': localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires a file icon theme to be enabled as well."), 'default': true }, 'workbench.editor.enablePreview': { 'type': 'boolean', - 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing) and show up with an italic font style."), + 'description': localize('enablePreview', "Controls whether opened editors show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing) and show up with an italic font style."), 'default': true }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'markdownDescription': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."), + 'markdownDescription': localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."), 'default': false }, 'workbench.editor.enablePreviewFromCodeNavigation': { 'type': 'boolean', - 'markdownDescription': nls.localize('enablePreviewFromCodeNavigation', "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."), + 'markdownDescription': localize('enablePreviewFromCodeNavigation', "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."), 'default': false }, 'workbench.editor.closeOnFileDelete': { 'type': 'boolean', - 'description': nls.localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), + 'description': localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), 'default': false }, 'workbench.editor.openPositioning': { 'type': 'string', 'enum': ['left', 'right', 'first', 'last'], 'default': 'right', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorOpenPositioning' }, "Controls where editors open. Select `left` or `right` to open editors to the left or right of the currently active one. Select `first` or `last` to open editors independently from the currently active one.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorOpenPositioning' }, "Controls where editors open. Select `left` or `right` to open editors to the left or right of the currently active one. Select `first` or `last` to open editors independently from the currently active one.") }, 'workbench.editor.openSideBySideDirection': { 'type': 'string', 'enum': ['right', 'down'], 'default': 'right', - 'markdownDescription': nls.localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.") + 'markdownDescription': localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.") }, 'workbench.editor.closeEmptyGroups': { 'type': 'boolean', - 'description': nls.localize('closeEmptyGroups', "Controls the behavior of empty editor groups when the last tab in the group is closed. When enabled, empty groups will automatically close. When disabled, empty groups will remain part of the grid."), + 'description': localize('closeEmptyGroups', "Controls the behavior of empty editor groups when the last tab in the group is closed. When enabled, empty groups will automatically close. When disabled, empty groups will remain part of the grid."), 'default': true }, 'workbench.editor.revealIfOpen': { 'type': 'boolean', - 'description': nls.localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, e.g. when forcing an editor to open in a specific group or to the side of the currently active group."), + 'description': localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, e.g. when forcing an editor to open in a specific group or to the side of the currently active group."), 'default': false }, 'workbench.editor.mouseBackForwardToNavigate': { 'type': 'boolean', - 'description': nls.localize('mouseBackForwardToNavigate', "Navigate between open files using mouse buttons four and five if provided."), + 'description': localize('mouseBackForwardToNavigate', "Navigate between open files using mouse buttons four and five if provided."), 'default': true }, 'workbench.editor.restoreViewState': { 'type': 'boolean', - 'description': nls.localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening textual editors after they have been closed."), + 'description': localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening textual editors after they have been closed."), 'default': true, 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'workbench.editor.centeredLayoutAutoResize': { 'type': 'boolean', 'default': true, - 'description': nls.localize('centeredLayoutAutoResize', "Controls if the centered layout should automatically resize to maximum width when more than one group is open. Once only one group is open it will resize back to the original centered width.") + 'description': localize('centeredLayoutAutoResize', "Controls if the centered layout should automatically resize to maximum width when more than one group is open. Once only one group is open it will resize back to the original centered width.") }, 'workbench.editor.limit.enabled': { 'type': 'boolean', 'default': false, - 'description': nls.localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors that are not dirty will close to make space for newly opening editors.") + 'description': localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors that are not dirty will close to make space for newly opening editors.") }, 'workbench.editor.limit.value': { 'type': 'number', 'default': 10, 'exclusiveMinimum': 0, - 'markdownDescription': nls.localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the `#workbench.editor.limit.perEditorGroup#` setting to control this limit per editor group or across all groups.") + 'markdownDescription': localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the `#workbench.editor.limit.perEditorGroup#` setting to control this limit per editor group or across all groups.") }, 'workbench.editor.limit.perEditorGroup': { 'type': 'boolean', 'default': false, - 'description': nls.localize('perEditorGroup', "Controls if the limit of maximum opened editors should apply per editor group or across all editor groups.") + 'description': localize('perEditorGroup', "Controls if the limit of maximum opened editors should apply per editor group or across all editor groups.") }, 'workbench.commandPalette.history': { 'type': 'number', - 'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), + 'description': localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), 'default': 50 }, 'workbench.commandPalette.preserveInput': { 'type': 'boolean', - 'description': nls.localize('preserveInput', "Controls whether the last typed input to the command palette should be restored when opening it the next time."), + 'description': localize('preserveInput', "Controls whether the last typed input to the command palette should be restored when opening it the next time."), 'default': false }, 'workbench.quickOpen.closeOnFocusLost': { 'type': 'boolean', - 'description': nls.localize('closeOnFocusLost', "Controls whether Quick Open should close automatically once it loses focus."), + 'description': localize('closeOnFocusLost', "Controls whether Quick Open should close automatically once it loses focus."), 'default': true }, 'workbench.quickOpen.preserveInput': { 'type': 'boolean', - 'description': nls.localize('workbench.quickOpen.preserveInput', "Controls whether the last typed input to Quick Open should be restored when opening it the next time."), + 'description': localize('workbench.quickOpen.preserveInput', "Controls whether the last typed input to Quick Open should be restored when opening it the next time."), 'default': false }, 'workbench.settings.openDefaultSettings': { 'type': 'boolean', - 'description': nls.localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), + 'description': localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), 'default': false }, 'workbench.settings.useSplitJSON': { 'type': 'boolean', - 'markdownDescription': nls.localize('useSplitJSON', "Controls whether to use the split JSON editor when editing settings as JSON."), + 'markdownDescription': localize('useSplitJSON', "Controls whether to use the split JSON editor when editing settings as JSON."), 'default': false }, 'workbench.settings.openDefaultKeybindings': { 'type': 'boolean', - 'description': nls.localize('openDefaultKeybindings', "Controls whether opening keybinding settings also opens an editor showing all default keybindings."), + 'description': localize('openDefaultKeybindings', "Controls whether opening keybinding settings also opens an editor showing all default keybindings."), 'default': false }, 'workbench.sideBar.location': { 'type': 'string', 'enum': ['left', 'right'], 'default': 'left', - 'description': nls.localize('sideBarLocation', "Controls the location of the sidebar and activity bar. They can either show on the left or right of the workbench.") + 'description': localize('sideBarLocation', "Controls the location of the sidebar and activity bar. They can either show on the left or right of the workbench.") }, 'workbench.panel.defaultLocation': { 'type': 'string', 'enum': ['left', 'bottom', 'right'], 'default': 'bottom', - 'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom, right, or left of the workbench.") + 'description': localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom, right, or left of the workbench.") }, 'workbench.panel.opensMaximized': { 'type': 'string', 'enum': ['always', 'never', 'preserve'], 'default': 'preserve', - 'description': nls.localize('panelOpensMaximized', "Controls whether the panel opens maximized. It can either always open maximized, never open maximized, or open to the last state it was in before being closed."), + 'description': localize('panelOpensMaximized', "Controls whether the panel opens maximized. It can either always open maximized, never open maximized, or open to the last state it was in before being closed."), 'enumDescriptions': [ - nls.localize('workbench.panel.opensMaximized.always', "Always maximize the panel when opening it."), - nls.localize('workbench.panel.opensMaximized.never', "Never maximize the panel when opening it. The panel will open un-maximized."), - nls.localize('workbench.panel.opensMaximized.preserve', "Open the panel to the state that it was in, before it was closed.") + localize('workbench.panel.opensMaximized.always', "Always maximize the panel when opening it."), + localize('workbench.panel.opensMaximized.never', "Never maximize the panel when opening it. The panel will open un-maximized."), + localize('workbench.panel.opensMaximized.preserve', "Open the panel to the state that it was in, before it was closed.") ] }, 'workbench.statusBar.visible': { 'type': 'boolean', 'default': true, - 'description': nls.localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") + 'description': localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") }, 'workbench.activityBar.visible': { 'type': 'boolean', 'default': true, - 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") + 'description': localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") }, 'workbench.activityBar.iconClickBehavior': { 'type': 'string', 'enum': ['toggle', 'focus'], 'default': 'toggle', - 'description': nls.localize('activityBarIconClickBehavior', "Controls the behavior of clicking an activity bar icon in the workbench."), + 'description': localize('activityBarIconClickBehavior', "Controls the behavior of clicking an activity bar icon in the workbench."), 'enumDescriptions': [ - nls.localize('workbench.activityBar.iconClickBehavior.toggle', "Hide the side bar if the clicked item is already visible."), - nls.localize('workbench.activityBar.iconClickBehavior.focus', "Focus side bar if the clicked item is already visible.") + localize('workbench.activityBar.iconClickBehavior.toggle', "Hide the side bar if the clicked item is already visible."), + localize('workbench.activityBar.iconClickBehavior.focus', "Focus side bar if the clicked item is already visible.") ] }, 'workbench.view.alwaysShowHeaderActions': { 'type': 'boolean', 'default': false, - 'description': nls.localize('viewVisibility', "Controls the visibility of view header actions. View header actions may either be always visible, or only visible when that view is focused or hovered over.") + 'description': localize('viewVisibility', "Controls the visibility of view header actions. View header actions may either be always visible, or only visible when that view is focused or hovered over.") }, 'workbench.fontAliasing': { 'type': 'string', 'enum': ['default', 'antialiased', 'none', 'auto'], 'default': 'default', 'description': - nls.localize('fontAliasing', "Controls font aliasing method in the workbench."), + localize('fontAliasing', "Controls font aliasing method in the workbench."), 'enumDescriptions': [ - nls.localize('workbench.fontAliasing.default', "Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text."), - nls.localize('workbench.fontAliasing.antialiased', "Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall."), - nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges."), - nls.localize('workbench.fontAliasing.auto', "Applies `default` or `antialiased` automatically based on the DPI of displays.") + localize('workbench.fontAliasing.default', "Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text."), + localize('workbench.fontAliasing.antialiased', "Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall."), + localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges."), + localize('workbench.fontAliasing.auto', "Applies `default` or `antialiased` automatically based on the DPI of displays.") ], 'included': isMacintosh }, @@ -313,10 +319,10 @@ import { isStandalone } from 'vs/base/browser/browser'; 'type': 'string', 'enum': ['ui', 'json'], 'enumDescriptions': [ - nls.localize('settings.editor.ui', "Use the settings UI editor."), - nls.localize('settings.editor.json', "Use the JSON file editor."), + localize('settings.editor.ui', "Use the settings UI editor."), + localize('settings.editor.json', "Use the JSON file editor."), ], - 'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."), + 'description': localize('settings.editor.desc', "Determines which settings editor to use by default."), 'default': 'ui', 'scope': ConfigurationScope.WINDOW } @@ -325,28 +331,28 @@ import { isStandalone } from 'vs/base/browser/browser'; // Window - let windowTitleDescription = nls.localize('windowTitle', "Controls the window title based on the active editor. Variables are substituted based on the context:"); + let windowTitleDescription = localize('windowTitle', "Controls the window title based on the active editor. Variables are substituted based on the context:"); windowTitleDescription += '\n- ' + [ - nls.localize('activeEditorShort', "`\${activeEditorShort}`: the file name (e.g. myFile.txt)."), - nls.localize('activeEditorMedium', "`\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)."), - nls.localize('activeEditorLong', "`\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)."), - nls.localize('activeFolderShort', "`\${activeFolderShort}`: the name of the folder the file is contained in (e.g. myFileFolder)."), - nls.localize('activeFolderMedium', "`\${activeFolderMedium}`: the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)."), - nls.localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."), - nls.localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."), - nls.localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."), - nls.localize('rootName', "`\${rootName}`: name of the opened workspace or folder (e.g. myFolder or myWorkspace)."), - nls.localize('rootPath', "`\${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."), - nls.localize('appName', "`\${appName}`: e.g. VS Code."), - nls.localize('remoteName', "`\${remoteName}`: e.g. SSH"), - nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."), - nls.localize('separator', "`\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") + localize('activeEditorShort', "`\${activeEditorShort}`: the file name (e.g. myFile.txt)."), + localize('activeEditorMedium', "`\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)."), + localize('activeEditorLong', "`\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)."), + localize('activeFolderShort', "`\${activeFolderShort}`: the name of the folder the file is contained in (e.g. myFileFolder)."), + localize('activeFolderMedium', "`\${activeFolderMedium}`: the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)."), + localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."), + localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."), + localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."), + localize('rootName', "`\${rootName}`: name of the opened workspace or folder (e.g. myFolder or myWorkspace)."), + localize('rootPath', "`\${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."), + localize('appName', "`\${appName}`: e.g. VS Code."), + localize('remoteName', "`\${remoteName}`: e.g. SSH"), + localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."), + localize('separator', "`\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations registry.registerConfiguration({ 'id': 'window', 'order': 8, - 'title': nls.localize('windowConfigurationTitle', "Window"), + 'title': localize('windowConfigurationTitle', "Window"), 'type': 'object', 'properties': { 'window.title': { @@ -368,80 +374,80 @@ import { isStandalone } from 'vs/base/browser/browser'; 'window.titleSeparator': { 'type': 'string', 'default': isMacintosh ? ' — ' : ' - ', - 'markdownDescription': nls.localize("window.titleSeparator", "Separator used by `window.title`.") + 'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.") }, 'window.menuBarVisibility': { 'type': 'string', 'enum': ['classic', 'visible', 'toggle', 'hidden', 'compact'], 'markdownEnumDescriptions': [ - nls.localize('window.menuBarVisibility.classic', "Menu is displayed at the top of the window and only hidden in full screen mode."), - nls.localize('window.menuBarVisibility.visible', "Menu is always visible at the top of the window even in full screen mode."), + localize('window.menuBarVisibility.classic', "Menu is displayed at the top of the window and only hidden in full screen mode."), + localize('window.menuBarVisibility.visible', "Menu is always visible at the top of the window even in full screen mode."), isMacintosh ? - nls.localize('window.menuBarVisibility.toggle.mac', "Menu is hidden but can be displayed at the top of the window by executing the `Focus Application Menu` command.") : - nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed at the top of the window via the Alt key."), - nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden."), - nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar. This value is ignored when `#window.titleBarStyle#` is `native`.") + localize('window.menuBarVisibility.toggle.mac', "Menu is hidden but can be displayed at the top of the window by executing the `Focus Application Menu` command.") : + localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed at the top of the window via the Alt key."), + localize('window.menuBarVisibility.hidden', "Menu is always hidden."), + localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar. This value is ignored when `#window.titleBarStyle#` is `native`.") ], 'default': isWeb ? 'compact' : 'classic', 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': isMacintosh ? - nls.localize('menuBarVisibility.mac', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and executing `Focus Application Menu` will show it. A setting of 'compact' will move the menu into the sidebar.") : - nls.localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. A setting of 'compact' will move the menu into the sidebar."), + localize('menuBarVisibility.mac', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and executing `Focus Application Menu` will show it. A setting of 'compact' will move the menu into the sidebar.") : + localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. A setting of 'compact' will move the menu into the sidebar."), 'included': isWindows || isLinux || isWeb }, 'window.enableMenuBarMnemonics': { 'type': 'boolean', 'default': true, 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('enableMenuBarMnemonics', "Controls whether the main menus can be opened via Alt-key shortcuts. Disabling mnemonics allows to bind these Alt-key shortcuts to editor commands instead."), + 'description': localize('enableMenuBarMnemonics', "Controls whether the main menus can be opened via Alt-key shortcuts. Disabling mnemonics allows to bind these Alt-key shortcuts to editor commands instead."), 'included': isWindows || isLinux }, 'window.customMenuBarAltFocus': { 'type': 'boolean', 'default': true, 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('customMenuBarAltFocus', "Controls whether the menu bar will be focused by pressing the Alt-key. This setting has no effect on toggling the menu bar with the Alt-key."), + 'markdownDescription': localize('customMenuBarAltFocus', "Controls whether the menu bar will be focused by pressing the Alt-key. This setting has no effect on toggling the menu bar with the Alt-key."), 'included': isWindows || isLinux }, 'window.openFilesInNewWindow': { 'type': 'string', 'enum': ['on', 'off', 'default'], 'enumDescriptions': [ - nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window."), - nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), + localize('window.openFilesInNewWindow.on', "Files will open in a new window."), + localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), isMacintosh ? - nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : - nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") + localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : + localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") ], 'default': 'off', 'scope': ConfigurationScope.APPLICATION, 'markdownDescription': isMacintosh ? - nls.localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : - nls.localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : + localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") }, 'window.openFoldersInNewWindow': { 'type': 'string', 'enum': ['on', 'off', 'default'], 'enumDescriptions': [ - nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), - nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), - nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") + localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), + localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), + localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") ], 'default': 'default', 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + 'markdownDescription': localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") }, 'window.confirmBeforeClose': { 'type': 'string', 'enum': ['always', 'keyboardOnly', 'never'], 'enumDescriptions': [ - nls.localize('window.confirmBeforeClose.always', "Always try to ask for confirmation. Note that browsers may still decide to close a tab or window without confirmation."), - nls.localize('window.confirmBeforeClose.keyboardOnly', "Only ask for confirmation if a keybinding was detected. Note that detection may not be possible in some cases."), - nls.localize('window.confirmBeforeClose.never', "Never explicitly ask for confirmation unless data loss is imminent.") + localize('window.confirmBeforeClose.always', "Always try to ask for confirmation. Note that browsers may still decide to close a tab or window without confirmation."), + localize('window.confirmBeforeClose.keyboardOnly', "Only ask for confirmation if a keybinding was detected. Note that detection may not be possible in some cases."), + localize('window.confirmBeforeClose.never', "Never explicitly ask for confirmation unless data loss is imminent.") ], 'default': isWeb && !isStandalone ? 'keyboardOnly' : 'never', // on by default in web, unless PWA - 'description': nls.localize('confirmBeforeCloseWeb', "Controls whether to show a confirmation dialog before closing the browser tab or window. Note that even if enabled, browsers may still decide to close a tab or window without confirmation and that this setting is only a hint that may not work in all cases."), + 'description': localize('confirmBeforeCloseWeb', "Controls whether to show a confirmation dialog before closing the browser tab or window. Note that even if enabled, browsers may still decide to close a tab or window without confirmation and that this setting is only a hint that may not work in all cases."), 'scope': ConfigurationScope.APPLICATION, 'included': isWeb } @@ -452,48 +458,48 @@ import { isStandalone } from 'vs/base/browser/browser'; registry.registerConfiguration({ 'id': 'zenMode', 'order': 9, - 'title': nls.localize('zenModeConfigurationTitle', "Zen Mode"), + 'title': localize('zenModeConfigurationTitle', "Zen Mode"), 'type': 'object', 'properties': { 'zenMode.fullScreen': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.fullScreen', "Controls whether turning on Zen Mode also puts the workbench into full screen mode.") + 'description': localize('zenMode.fullScreen', "Controls whether turning on Zen Mode also puts the workbench into full screen mode.") }, 'zenMode.centerLayout': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.centerLayout', "Controls whether turning on Zen Mode also centers the layout.") + 'description': localize('zenMode.centerLayout', "Controls whether turning on Zen Mode also centers the layout.") }, 'zenMode.hideTabs': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.hideTabs', "Controls whether turning on Zen Mode also hides workbench tabs.") + 'description': localize('zenMode.hideTabs', "Controls whether turning on Zen Mode also hides workbench tabs.") }, 'zenMode.hideStatusBar': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.hideStatusBar', "Controls whether turning on Zen Mode also hides the status bar at the bottom of the workbench.") + 'description': localize('zenMode.hideStatusBar', "Controls whether turning on Zen Mode also hides the status bar at the bottom of the workbench.") }, 'zenMode.hideActivityBar': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.hideActivityBar', "Controls whether turning on Zen Mode also hides the activity bar either at the left or right of the workbench.") + 'description': localize('zenMode.hideActivityBar', "Controls whether turning on Zen Mode also hides the activity bar either at the left or right of the workbench.") }, 'zenMode.hideLineNumbers': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.hideLineNumbers', "Controls whether turning on Zen Mode also hides the editor line numbers.") + 'description': localize('zenMode.hideLineNumbers', "Controls whether turning on Zen Mode also hides the editor line numbers.") }, 'zenMode.restore': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.") + 'description': localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.") }, 'zenMode.silentNotifications': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.silentNotifications', "Controls whether notifications are shown while in zen mode. If true, only error notifications will pop out.") + 'description': localize('zenMode.silentNotifications', "Controls whether notifications are shown while in zen mode. If true, only error notifications will pop out.") } } }); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 29c56c5202c..43d66630da8 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/workbench/browser/style'; - import { localize } from 'vs/nls'; import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { runWhenIdle } from 'vs/base/common/async'; diff --git a/src/vs/workbench/buildfile.desktop.js b/src/vs/workbench/buildfile.desktop.js index 89939f33214..2272b7bbabe 100644 --- a/src/vs/workbench/buildfile.desktop.js +++ b/src/vs/workbench/buildfile.desktop.js @@ -28,6 +28,8 @@ exports.collectModules = function () { createModuleDescription('vs/platform/files/node/watcher/unix/watcherApp', []), createModuleDescription('vs/platform/files/node/watcher/nsfw/watcherApp', []), + createModuleDescription('vs/platform/terminal/node/ptyHostMain', []), + createModuleDescription('vs/workbench/services/extensions/node/extensionHostProcess', []), ]; -}; \ No newline at end of file +}; diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index d27f59b3627..b2bd447062c 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; export interface IComposite { @@ -48,11 +48,6 @@ export interface IComposite { */ getContextMenuActions(): ReadonlyArray; - /** - * Returns the action item for a specific action. - */ - getActionViewItem(action: IAction): IActionViewItem | undefined; - /** * Returns the underlying control of this composite. */ diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 5471bd95d6f..9feceb6188b 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1549,9 +1549,9 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService pinned: true, override: path.overrideId } : { - pinned: true, - override: path.overrideId - }; + pinned: true, + override: path.overrideId + }; let input: IResourceEditorInput | IUntitledTextResourceEditorInput; if (!exists) { diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 94e7e7a4bac..27e05b1d228 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as objects from 'vs/base/common/objects'; +import { deepClone, equals } from 'vs/base/common/objects'; import { Emitter } from 'vs/base/common/event'; import { basename, dirname, extname, relativePath } from 'vs/base/common/resources'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -159,11 +159,11 @@ export class ResourceGlobMatcher extends Disposable { // Add excludes per workspaces that got added this.contextService.getWorkspace().folders.forEach(folder => { const rootExcludes = this.globFn(folder.uri); - if (!this.mapRootToExpressionConfig.has(folder.uri.toString()) || !objects.equals(this.mapRootToExpressionConfig.get(folder.uri.toString()), rootExcludes)) { + if (!this.mapRootToExpressionConfig.has(folder.uri.toString()) || !equals(this.mapRootToExpressionConfig.get(folder.uri.toString()), rootExcludes)) { changed = true; this.mapRootToParsedExpression.set(folder.uri.toString(), parse(rootExcludes)); - this.mapRootToExpressionConfig.set(folder.uri.toString(), objects.deepClone(rootExcludes)); + this.mapRootToExpressionConfig.set(folder.uri.toString(), deepClone(rootExcludes)); } }); @@ -183,11 +183,11 @@ export class ResourceGlobMatcher extends Disposable { // Always set for resources outside root as well const globalExcludes = this.globFn(); - if (!this.mapRootToExpressionConfig.has(ResourceGlobMatcher.NO_ROOT) || !objects.equals(this.mapRootToExpressionConfig.get(ResourceGlobMatcher.NO_ROOT), globalExcludes)) { + if (!this.mapRootToExpressionConfig.has(ResourceGlobMatcher.NO_ROOT) || !equals(this.mapRootToExpressionConfig.get(ResourceGlobMatcher.NO_ROOT), globalExcludes)) { changed = true; this.mapRootToParsedExpression.set(ResourceGlobMatcher.NO_ROOT, parse(globalExcludes)); - this.mapRootToExpressionConfig.set(ResourceGlobMatcher.NO_ROOT, objects.deepClone(globalExcludes)); + this.mapRootToExpressionConfig.set(ResourceGlobMatcher.NO_ROOT, deepClone(globalExcludes)); } if (fromEvent && changed) { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 317cfd2fcd9..272f767d2e8 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke, errorForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; @@ -29,25 +29,25 @@ export const TAB_ACTIVE_BACKGROUND = registerColor('tab.activeBackground', { dark: editorBackground, light: editorBackground, hc: editorBackground -}, nls.localize('tabActiveBackground', "Active tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveBackground', "Active tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_BACKGROUND = registerColor('tab.unfocusedActiveBackground', { dark: TAB_ACTIVE_BACKGROUND, light: TAB_ACTIVE_BACKGROUND, hc: TAB_ACTIVE_BACKGROUND -}, nls.localize('tabUnfocusedActiveBackground', "Active tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedActiveBackground', "Active tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_INACTIVE_BACKGROUND = registerColor('tab.inactiveBackground', { dark: '#2D2D2D', light: '#ECECEC', hc: null -}, nls.localize('tabInactiveBackground', "Inactive tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabInactiveBackground', "Inactive tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_INACTIVE_BACKGROUND = registerColor('tab.unfocusedInactiveBackground', { dark: TAB_INACTIVE_BACKGROUND, light: TAB_INACTIVE_BACKGROUND, hc: TAB_INACTIVE_BACKGROUND -}, nls.localize('tabUnfocusedInactiveBackground', "Inactive tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedInactiveBackground', "Inactive tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion @@ -57,25 +57,25 @@ export const TAB_ACTIVE_FOREGROUND = registerColor('tab.activeForeground', { dark: Color.white, light: '#333333', hc: Color.white -}, nls.localize('tabActiveForeground', "Active tab foreground color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveForeground', "Active tab foreground color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_INACTIVE_FOREGROUND = registerColor('tab.inactiveForeground', { dark: transparent(TAB_ACTIVE_FOREGROUND, 0.5), light: transparent(TAB_ACTIVE_FOREGROUND, 0.7), hc: Color.white -}, nls.localize('tabInactiveForeground', "Inactive tab foreground color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabInactiveForeground', "Inactive tab foreground color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_FOREGROUND = registerColor('tab.unfocusedActiveForeground', { dark: transparent(TAB_ACTIVE_FOREGROUND, 0.5), light: transparent(TAB_ACTIVE_FOREGROUND, 0.7), hc: Color.white -}, nls.localize('tabUnfocusedActiveForeground', "Active tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedActiveForeground', "Active tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_INACTIVE_FOREGROUND = registerColor('tab.unfocusedInactiveForeground', { dark: transparent(TAB_INACTIVE_FOREGROUND, 0.5), light: transparent(TAB_INACTIVE_FOREGROUND, 0.5), hc: Color.white -}, nls.localize('tabUnfocusedInactiveForeground', "Inactive tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedInactiveForeground', "Inactive tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion @@ -85,25 +85,25 @@ export const TAB_HOVER_BACKGROUND = registerColor('tab.hoverBackground', { dark: null, light: null, hc: null -}, nls.localize('tabHoverBackground', "Tab background color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabHoverBackground', "Tab background color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_HOVER_BACKGROUND = registerColor('tab.unfocusedHoverBackground', { dark: transparent(TAB_HOVER_BACKGROUND, 0.5), light: transparent(TAB_HOVER_BACKGROUND, 0.7), hc: null -}, nls.localize('tabUnfocusedHoverBackground', "Tab background color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedHoverBackground', "Tab background color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_HOVER_FOREGROUND = registerColor('tab.hoverForeground', { dark: null, light: null, hc: null -}, nls.localize('tabHoverForeground', "Tab foreground color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabHoverForeground', "Tab foreground color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_HOVER_FOREGROUND = registerColor('tab.unfocusedHoverForeground', { dark: transparent(TAB_HOVER_FOREGROUND, 0.5), light: transparent(TAB_HOVER_FOREGROUND, 0.5), hc: null -}, nls.localize('tabUnfocusedHoverForeground', "Tab foreground color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedHoverForeground', "Tab foreground color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion @@ -113,49 +113,49 @@ export const TAB_BORDER = registerColor('tab.border', { dark: '#252526', light: '#F3F3F3', hc: contrastBorder -}, nls.localize('tabBorder', "Border to separate tabs from each other. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabBorder', "Border to separate tabs from each other. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_LAST_PINNED_BORDER = registerColor('tab.lastPinnedBorder', { dark: treeIndentGuidesStroke, light: treeIndentGuidesStroke, hc: contrastBorder -}, nls.localize('lastPinnedTabBorder', "Border to separate pinned tabs from other tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('lastPinnedTabBorder', "Border to separate pinned tabs from other tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_ACTIVE_BORDER = registerColor('tab.activeBorder', { dark: null, light: null, hc: null -}, nls.localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_BORDER = registerColor('tab.unfocusedActiveBorder', { dark: transparent(TAB_ACTIVE_BORDER, 0.5), light: transparent(TAB_ACTIVE_BORDER, 0.7), hc: null -}, nls.localize('tabActiveUnfocusedBorder', "Border on the bottom of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveUnfocusedBorder', "Border on the bottom of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_ACTIVE_BORDER_TOP = registerColor('tab.activeBorderTop', { dark: null, light: null, hc: null -}, nls.localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_BORDER_TOP = registerColor('tab.unfocusedActiveBorderTop', { dark: transparent(TAB_ACTIVE_BORDER_TOP, 0.5), light: transparent(TAB_ACTIVE_BORDER_TOP, 0.7), hc: null -}, nls.localize('tabActiveUnfocusedBorderTop', "Border to the top of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveUnfocusedBorderTop', "Border to the top of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', { dark: null, light: null, hc: null -}, nls.localize('tabHoverBorder', "Border to highlight tabs when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabHoverBorder', "Border to highlight tabs when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorder', { dark: transparent(TAB_HOVER_BORDER, 0.5), light: transparent(TAB_HOVER_BORDER, 0.7), hc: null -}, nls.localize('tabUnfocusedHoverBorder', "Border to highlight tabs in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabUnfocusedHoverBorder', "Border to highlight tabs in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion @@ -165,25 +165,25 @@ export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorde dark: '#3399CC', light: '#33AAEE', hc: null -}, nls.localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_INACTIVE_MODIFIED_BORDER = registerColor('tab.inactiveModifiedBorder', { dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), hc: Color.white -}, nls.localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedActiveModifiedBorder', { dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.7), hc: Color.white -}, nls.localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedInactiveModifiedBorder', { dark: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), light: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), hc: Color.white -}, nls.localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +}, localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); //#endregion @@ -193,61 +193,61 @@ export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', { dark: editorBackground, light: editorBackground, hc: editorBackground -}, nls.localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout.")); +}, localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout.")); registerColor('editorGroup.background', { dark: null, light: null, hc: null -}, nls.localize('editorGroupBackground', "Deprecated background color of an editor group."), false, nls.localize('deprecatedEditorGroupBackground', "Deprecated: Background color of an editor group is no longer being supported with the introduction of the grid editor layout. You can use editorGroup.emptyBackground to set the background color of empty editor groups.")); +}, localize('editorGroupBackground', "Deprecated background color of an editor group."), false, localize('deprecatedEditorGroupBackground', "Deprecated: Background color of an editor group is no longer being supported with the introduction of the grid editor layout. You can use editorGroup.emptyBackground to set the background color of empty editor groups.")); export const EDITOR_GROUP_EMPTY_BACKGROUND = registerColor('editorGroup.emptyBackground', { dark: null, light: null, hc: null -}, nls.localize('editorGroupEmptyBackground', "Background color of an empty editor group. Editor groups are the containers of editors.")); +}, localize('editorGroupEmptyBackground', "Background color of an empty editor group. Editor groups are the containers of editors.")); export const EDITOR_GROUP_FOCUSED_EMPTY_BORDER = registerColor('editorGroup.focusedEmptyBorder', { dark: null, light: null, hc: focusBorder -}, nls.localize('editorGroupFocusedEmptyBorder', "Border color of an empty editor group that is focused. Editor groups are the containers of editors.")); +}, localize('editorGroupFocusedEmptyBorder', "Border color of an empty editor group that is focused. Editor groups are the containers of editors.")); export const EDITOR_GROUP_HEADER_TABS_BACKGROUND = registerColor('editorGroupHeader.tabsBackground', { dark: '#252526', light: '#F3F3F3', hc: null -}, nls.localize('tabsContainerBackground', "Background color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); +}, localize('tabsContainerBackground', "Background color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); export const EDITOR_GROUP_HEADER_TABS_BORDER = registerColor('editorGroupHeader.tabsBorder', { dark: null, light: null, hc: null -}, nls.localize('tabsContainerBorder', "Border color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); +}, localize('tabsContainerBorder', "Border color of the editor group title header when tabs are enabled. Editor groups are the containers of editors.")); export const EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND = registerColor('editorGroupHeader.noTabsBackground', { dark: editorBackground, light: editorBackground, hc: editorBackground -}, nls.localize('editorGroupHeaderBackground', "Background color of the editor group title header when tabs are disabled (`\"workbench.editor.showTabs\": false`). Editor groups are the containers of editors.")); +}, localize('editorGroupHeaderBackground', "Background color of the editor group title header when tabs are disabled (`\"workbench.editor.showTabs\": false`). Editor groups are the containers of editors.")); export const EDITOR_GROUP_HEADER_BORDER = registerColor('editorGroupHeader.border', { dark: null, light: null, hc: contrastBorder -}, nls.localize('editorTitleContainerBorder', "Border color of the editor group title header. Editor groups are the containers of editors.")); +}, localize('editorTitleContainerBorder', "Border color of the editor group title header. Editor groups are the containers of editors.")); export const EDITOR_GROUP_BORDER = registerColor('editorGroup.border', { dark: '#444444', light: '#E7E7E7', hc: contrastBorder -}, nls.localize('editorGroupBorder', "Color to separate multiple editor groups from each other. Editor groups are the containers of editors.")); +}, localize('editorGroupBorder', "Color to separate multiple editor groups from each other. Editor groups are the containers of editors.")); export const EDITOR_DRAG_AND_DROP_BACKGROUND = registerColor('editorGroup.dropBackground', { dark: Color.fromHex('#53595D').transparent(0.5), light: Color.fromHex('#2677CB').transparent(0.18), hc: null -}, nls.localize('editorDragAndDropBackground', "Background color when dragging editors around. The color should have transparency so that the editor contents can still shine through.")); +}, localize('editorDragAndDropBackground', "Background color when dragging editors around. The color should have transparency so that the editor contents can still shine through.")); // < --- Resource Viewer --- > @@ -255,7 +255,7 @@ export const IMAGE_PREVIEW_BORDER = registerColor('imagePreview.border', { dark: Color.fromHex('#808080').transparent(0.35), light: Color.fromHex('#808080').transparent(0.35), hc: contrastBorder -}, nls.localize('imagePreviewBorder', "Border color for image in image preview.")); +}, localize('imagePreviewBorder', "Border color for image in image preview.")); // < --- Panels --- > @@ -263,74 +263,74 @@ export const PANEL_BACKGROUND = registerColor('panel.background', { dark: editorBackground, light: editorBackground, hc: editorBackground -}, nls.localize('panelBackground', "Panel background color. Panels are shown below the editor area and contain views like output and integrated terminal.")); +}, localize('panelBackground', "Panel background color. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_BORDER = registerColor('panel.border', { dark: Color.fromHex('#808080').transparent(0.35), light: Color.fromHex('#808080').transparent(0.35), hc: contrastBorder -}, nls.localize('panelBorder', "Panel border color to separate the panel from the editor. Panels are shown below the editor area and contain views like output and integrated terminal.")); +}, localize('panelBorder', "Panel border color to separate the panel from the editor. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_ACTIVE_TITLE_FOREGROUND = registerColor('panelTitle.activeForeground', { dark: '#E7E7E7', light: '#424242', hc: Color.white -}, nls.localize('panelActiveTitleForeground', "Title color for the active panel. Panels are shown below the editor area and contain views like output and integrated terminal.")); +}, localize('panelActiveTitleForeground', "Title color for the active panel. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_INACTIVE_TITLE_FOREGROUND = registerColor('panelTitle.inactiveForeground', { dark: transparent(PANEL_ACTIVE_TITLE_FOREGROUND, 0.6), light: transparent(PANEL_ACTIVE_TITLE_FOREGROUND, 0.75), hc: Color.white -}, nls.localize('panelInactiveTitleForeground', "Title color for the inactive panel. Panels are shown below the editor area and contain views like output and integrated terminal.")); +}, localize('panelInactiveTitleForeground', "Title color for the inactive panel. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_ACTIVE_TITLE_BORDER = registerColor('panelTitle.activeBorder', { dark: PANEL_ACTIVE_TITLE_FOREGROUND, light: PANEL_ACTIVE_TITLE_FOREGROUND, hc: contrastBorder -}, nls.localize('panelActiveTitleBorder', "Border color for the active panel title. Panels are shown below the editor area and contain views like output and integrated terminal.")); +}, localize('panelActiveTitleBorder', "Border color for the active panel title. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_INPUT_BORDER = registerColor('panelInput.border', { dark: null, light: Color.fromHex('#ddd'), hc: null -}, nls.localize('panelInputBorder', "Input box border for inputs in the panel.")); +}, localize('panelInputBorder', "Input box border for inputs in the panel.")); export const PANEL_DRAG_AND_DROP_BORDER = registerColor('panel.dropBorder', { dark: PANEL_ACTIVE_TITLE_FOREGROUND, light: PANEL_ACTIVE_TITLE_FOREGROUND, hc: PANEL_ACTIVE_TITLE_FOREGROUND, -}, nls.localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal.")); +}, localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_SECTION_DRAG_AND_DROP_BACKGROUND = registerColor('panelSection.dropBackground', { dark: EDITOR_DRAG_AND_DROP_BACKGROUND, light: EDITOR_DRAG_AND_DROP_BACKGROUND, hc: EDITOR_DRAG_AND_DROP_BACKGROUND, -}, nls.localize('panelSectionDragAndDropBackground', "Drag and drop feedback color for the panel sections. The color should have transparency so that the panel sections can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +}, localize('panelSectionDragAndDropBackground', "Drag and drop feedback color for the panel sections. The color should have transparency so that the panel sections can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); export const PANEL_SECTION_HEADER_BACKGROUND = registerColor('panelSectionHeader.background', { dark: Color.fromHex('#808080').transparent(0.2), light: Color.fromHex('#808080').transparent(0.2), hc: null -}, nls.localize('panelSectionHeaderBackground', "Panel section header background color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +}, localize('panelSectionHeaderBackground', "Panel section header background color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); export const PANEL_SECTION_HEADER_FOREGROUND = registerColor('panelSectionHeader.foreground', { dark: null, light: null, hc: null -}, nls.localize('panelSectionHeaderForeground', "Panel section header foreground color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +}, localize('panelSectionHeaderForeground', "Panel section header foreground color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); export const PANEL_SECTION_HEADER_BORDER = registerColor('panelSectionHeader.border', { dark: contrastBorder, light: contrastBorder, hc: contrastBorder -}, nls.localize('panelSectionHeaderBorder', "Panel section header border color used when multiple views are stacked vertically in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +}, localize('panelSectionHeaderBorder', "Panel section header border color used when multiple views are stacked vertically in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); export const PANEL_SECTION_BORDER = registerColor('panelSection.border', { dark: PANEL_BORDER, light: PANEL_BORDER, hc: PANEL_BORDER -}, nls.localize('panelSectionBorder', "Panel section border color used when multiple views are stacked horizontally in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); +}, localize('panelSectionBorder', "Panel section border color used when multiple views are stacked horizontally in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels.")); // < --- Status --- > @@ -339,79 +339,79 @@ export const STATUS_BAR_FOREGROUND = registerColor('statusBar.foreground', { dark: '#FFFFFF', light: '#FFFFFF', hc: '#FFFFFF' -}, nls.localize('statusBarForeground', "Status bar foreground color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); +}, localize('statusBarForeground', "Status bar foreground color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_NO_FOLDER_FOREGROUND = registerColor('statusBar.noFolderForeground', { dark: STATUS_BAR_FOREGROUND, light: STATUS_BAR_FOREGROUND, hc: STATUS_BAR_FOREGROUND -}, nls.localize('statusBarNoFolderForeground', "Status bar foreground color when no folder is opened. The status bar is shown in the bottom of the window.")); +}, localize('statusBarNoFolderForeground', "Status bar foreground color when no folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_BACKGROUND = registerColor('statusBar.background', { dark: '#007ACC', light: '#007ACC', hc: null -}, nls.localize('statusBarBackground', "Status bar background color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); +}, localize('statusBarBackground', "Status bar background color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_NO_FOLDER_BACKGROUND = registerColor('statusBar.noFolderBackground', { dark: '#68217A', light: '#68217A', hc: null -}, nls.localize('statusBarNoFolderBackground', "Status bar background color when no folder is opened. The status bar is shown in the bottom of the window.")); +}, localize('statusBarNoFolderBackground', "Status bar background color when no folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_BORDER = registerColor('statusBar.border', { dark: null, light: null, hc: contrastBorder -}, nls.localize('statusBarBorder', "Status bar border color separating to the sidebar and editor. The status bar is shown in the bottom of the window.")); +}, localize('statusBarBorder', "Status bar border color separating to the sidebar and editor. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_NO_FOLDER_BORDER = registerColor('statusBar.noFolderBorder', { dark: STATUS_BAR_BORDER, light: STATUS_BAR_BORDER, hc: STATUS_BAR_BORDER -}, nls.localize('statusBarNoFolderBorder', "Status bar border color separating to the sidebar and editor when no folder is opened. The status bar is shown in the bottom of the window.")); +}, localize('statusBarNoFolderBorder', "Status bar border color separating to the sidebar and editor when no folder is opened. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ITEM_ACTIVE_BACKGROUND = registerColor('statusBarItem.activeBackground', { dark: Color.white.transparent(0.18), light: Color.white.transparent(0.18), hc: Color.white.transparent(0.18) -}, nls.localize('statusBarItemActiveBackground', "Status bar item background color when clicking. The status bar is shown in the bottom of the window.")); +}, localize('statusBarItemActiveBackground', "Status bar item background color when clicking. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.hoverBackground', { dark: Color.white.transparent(0.12), light: Color.white.transparent(0.12), hc: Color.white.transparent(0.12) -}, nls.localize('statusBarItemHoverBackground', "Status bar item background color when hovering. The status bar is shown in the bottom of the window.")); +}, localize('statusBarItemHoverBackground', "Status bar item background color when hovering. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_PROMINENT_ITEM_FOREGROUND = registerColor('statusBarItem.prominentForeground', { dark: STATUS_BAR_FOREGROUND, light: STATUS_BAR_FOREGROUND, hc: STATUS_BAR_FOREGROUND -}, nls.localize('statusBarProminentItemForeground', "Status bar prominent items foreground color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); +}, localize('statusBarProminentItemForeground', "Status bar prominent items foreground color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_PROMINENT_ITEM_BACKGROUND = registerColor('statusBarItem.prominentBackground', { dark: Color.black.transparent(0.5), light: Color.black.transparent(0.5), hc: Color.black.transparent(0.5), -}, nls.localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); +}, localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.prominentHoverBackground', { dark: Color.black.transparent(0.3), light: Color.black.transparent(0.3), hc: Color.black.transparent(0.3), -}, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); +}, localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ERROR_ITEM_BACKGROUND = registerColor('statusBarItem.errorBackground', { dark: darken(errorForeground, .4), light: darken(errorForeground, .4), hc: null, -}, nls.localize('statusBarErrorItemBackground', "Status bar error items background color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); +}, localize('statusBarErrorItemBackground', "Status bar error items background color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_ERROR_ITEM_FOREGROUND = registerColor('statusBarItem.errorForeground', { dark: Color.white, light: Color.white, hc: Color.white, -}, nls.localize('statusBarErrorItemForeground', "Status bar error items foreground color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); +}, localize('statusBarErrorItemForeground', "Status bar error items foreground color. Error items stand out from other status bar entries to indicate error conditions. The status bar is shown in the bottom of the window.")); // < --- Activity Bar --- > @@ -419,61 +419,61 @@ export const ACTIVITY_BAR_BACKGROUND = registerColor('activityBar.background', { dark: '#333333', light: '#2C2C2C', hc: '#000000' -}, nls.localize('activityBarBackground', "Activity bar background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarBackground', "Activity bar background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_FOREGROUND = registerColor('activityBar.foreground', { dark: Color.white, light: Color.white, hc: Color.white -}, nls.localize('activityBarForeground', "Activity bar item foreground color when it is active. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarForeground', "Activity bar item foreground color when it is active. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_INACTIVE_FOREGROUND = registerColor('activityBar.inactiveForeground', { dark: transparent(ACTIVITY_BAR_FOREGROUND, 0.4), light: transparent(ACTIVITY_BAR_FOREGROUND, 0.4), hc: Color.white -}, nls.localize('activityBarInActiveForeground', "Activity bar item foreground color when it is inactive. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarInActiveForeground', "Activity bar item foreground color when it is inactive. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_BORDER = registerColor('activityBar.border', { dark: null, light: null, hc: contrastBorder -}, nls.localize('activityBarBorder', "Activity bar border color separating to the side bar. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarBorder', "Activity bar border color separating to the side bar. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_ACTIVE_BORDER = registerColor('activityBar.activeBorder', { dark: ACTIVITY_BAR_FOREGROUND, light: ACTIVITY_BAR_FOREGROUND, hc: null -}, nls.localize('activityBarActiveBorder', "Activity bar border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarActiveBorder', "Activity bar border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_ACTIVE_FOCUS_BORDER = registerColor('activityBar.activeFocusBorder', { dark: null, light: null, hc: null -}, nls.localize('activityBarActiveFocusBorder', "Activity bar focus border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarActiveFocusBorder', "Activity bar focus border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', { dark: null, light: null, hc: null -}, nls.localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_DRAG_AND_DROP_BORDER = registerColor('activityBar.dropBorder', { dark: ACTIVITY_BAR_FOREGROUND, light: ACTIVITY_BAR_FOREGROUND, hc: ACTIVITY_BAR_FOREGROUND, -}, nls.localize('activityBarDragAndDropBorder', "Drag and drop feedback color for the activity bar items. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarDragAndDropBorder', "Drag and drop feedback color for the activity bar items. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_BADGE_BACKGROUND = registerColor('activityBarBadge.background', { dark: '#007ACC', light: '#007ACC', hc: '#000000' -}, nls.localize('activityBarBadgeBackground', "Activity notification badge background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarBadgeBackground', "Activity notification badge background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.foreground', { dark: Color.white, light: Color.white, hc: Color.white -}, nls.localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +}, localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); // < --- Remote --- > @@ -482,25 +482,25 @@ export const STATUS_BAR_HOST_NAME_BACKGROUND = registerColor('statusBarItem.remo dark: ACTIVITY_BAR_BADGE_BACKGROUND, light: ACTIVITY_BAR_BADGE_BACKGROUND, hc: ACTIVITY_BAR_BADGE_BACKGROUND -}, nls.localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar.")); +}, localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar.")); export const STATUS_BAR_HOST_NAME_FOREGROUND = registerColor('statusBarItem.remoteForeground', { dark: ACTIVITY_BAR_BADGE_FOREGROUND, light: ACTIVITY_BAR_BADGE_FOREGROUND, hc: ACTIVITY_BAR_BADGE_FOREGROUND -}, nls.localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar.")); +}, localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar.")); export const EXTENSION_BADGE_REMOTE_BACKGROUND = registerColor('extensionBadge.remoteBackground', { dark: ACTIVITY_BAR_BADGE_BACKGROUND, light: ACTIVITY_BAR_BADGE_BACKGROUND, hc: ACTIVITY_BAR_BADGE_BACKGROUND -}, nls.localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view.")); +}, localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view.")); export const EXTENSION_BADGE_REMOTE_FOREGROUND = registerColor('extensionBadge.remoteForeground', { dark: ACTIVITY_BAR_BADGE_FOREGROUND, light: ACTIVITY_BAR_BADGE_FOREGROUND, hc: ACTIVITY_BAR_BADGE_FOREGROUND -}, nls.localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view.")); +}, localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view.")); // < --- Side Bar --- > @@ -509,49 +509,49 @@ export const SIDE_BAR_BACKGROUND = registerColor('sideBar.background', { dark: '#252526', light: '#F3F3F3', hc: '#000000' -}, nls.localize('sideBarBackground', "Side bar background color. The side bar is the container for views like explorer and search.")); +}, localize('sideBarBackground', "Side bar background color. The side bar is the container for views like explorer and search.")); export const SIDE_BAR_FOREGROUND = registerColor('sideBar.foreground', { dark: null, light: null, hc: null -}, nls.localize('sideBarForeground', "Side bar foreground color. The side bar is the container for views like explorer and search.")); +}, localize('sideBarForeground', "Side bar foreground color. The side bar is the container for views like explorer and search.")); export const SIDE_BAR_BORDER = registerColor('sideBar.border', { dark: null, light: null, hc: contrastBorder -}, nls.localize('sideBarBorder', "Side bar border color on the side separating to the editor. The side bar is the container for views like explorer and search.")); +}, localize('sideBarBorder', "Side bar border color on the side separating to the editor. The side bar is the container for views like explorer and search.")); export const SIDE_BAR_TITLE_FOREGROUND = registerColor('sideBarTitle.foreground', { dark: SIDE_BAR_FOREGROUND, light: SIDE_BAR_FOREGROUND, hc: SIDE_BAR_FOREGROUND -}, nls.localize('sideBarTitleForeground', "Side bar title foreground color. The side bar is the container for views like explorer and search.")); +}, localize('sideBarTitleForeground', "Side bar title foreground color. The side bar is the container for views like explorer and search.")); export const SIDE_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('sideBar.dropBackground', { dark: EDITOR_DRAG_AND_DROP_BACKGROUND, light: EDITOR_DRAG_AND_DROP_BACKGROUND, hc: EDITOR_DRAG_AND_DROP_BACKGROUND, -}, nls.localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +}, localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionHeader.background', { dark: Color.fromHex('#808080').transparent(0.2), light: Color.fromHex('#808080').transparent(0.2), hc: null -}, nls.localize('sideBarSectionHeaderBackground', "Side bar section header background color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +}, localize('sideBarSectionHeaderBackground', "Side bar section header background color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); export const SIDE_BAR_SECTION_HEADER_FOREGROUND = registerColor('sideBarSectionHeader.foreground', { dark: SIDE_BAR_FOREGROUND, light: SIDE_BAR_FOREGROUND, hc: SIDE_BAR_FOREGROUND -}, nls.localize('sideBarSectionHeaderForeground', "Side bar section header foreground color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +}, localize('sideBarSectionHeaderForeground', "Side bar section header foreground color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeader.border', { dark: contrastBorder, light: contrastBorder, hc: contrastBorder -}, nls.localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); +}, localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar.")); // < --- Title Bar --- > @@ -560,31 +560,31 @@ export const TITLE_BAR_ACTIVE_FOREGROUND = registerColor('titleBar.activeForegro dark: '#CCCCCC', light: '#333333', hc: '#FFFFFF' -}, nls.localize('titleBarActiveForeground', "Title bar foreground when the window is active.")); +}, localize('titleBarActiveForeground', "Title bar foreground when the window is active.")); export const TITLE_BAR_INACTIVE_FOREGROUND = registerColor('titleBar.inactiveForeground', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, 0.6), light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, 0.6), hc: null -}, nls.localize('titleBarInactiveForeground', "Title bar foreground when the window is inactive.")); +}, localize('titleBarInactiveForeground', "Title bar foreground when the window is inactive.")); export const TITLE_BAR_ACTIVE_BACKGROUND = registerColor('titleBar.activeBackground', { dark: '#3C3C3C', light: '#DDDDDD', hc: '#000000' -}, nls.localize('titleBarActiveBackground', "Title bar background when the window is active.")); +}, localize('titleBarActiveBackground', "Title bar background when the window is active.")); export const TITLE_BAR_INACTIVE_BACKGROUND = registerColor('titleBar.inactiveBackground', { dark: transparent(TITLE_BAR_ACTIVE_BACKGROUND, 0.6), light: transparent(TITLE_BAR_ACTIVE_BACKGROUND, 0.6), hc: null -}, nls.localize('titleBarInactiveBackground', "Title bar background when the window is inactive.")); +}, localize('titleBarInactiveBackground', "Title bar background when the window is inactive.")); export const TITLE_BAR_BORDER = registerColor('titleBar.border', { dark: null, light: null, hc: contrastBorder -}, nls.localize('titleBarBorder', "Title bar border color.")); +}, localize('titleBarBorder', "Title bar border color.")); // < --- Menubar --- > @@ -592,19 +592,19 @@ export const MENUBAR_SELECTION_FOREGROUND = registerColor('menubar.selectionFore dark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hc: TITLE_BAR_ACTIVE_FOREGROUND -}, nls.localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar.")); +}, localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar.")); export const MENUBAR_SELECTION_BACKGROUND = registerColor('menubar.selectionBackground', { dark: transparent(Color.white, 0.1), light: transparent(Color.black, 0.1), hc: null -}, nls.localize('menubarSelectionBackground', "Background color of the selected menu item in the menubar.")); +}, localize('menubarSelectionBackground', "Background color of the selected menu item in the menubar.")); export const MENUBAR_SELECTION_BORDER = registerColor('menubar.selectionBorder', { dark: null, light: null, hc: activeContrastBorder -}, nls.localize('menubarSelectionBorder', "Border color of the selected menu item in the menubar.")); +}, localize('menubarSelectionBorder', "Border color of the selected menu item in the menubar.")); // < --- Notifications --- > @@ -612,76 +612,76 @@ export const NOTIFICATIONS_CENTER_BORDER = registerColor('notificationCenter.bor dark: null, light: null, hc: contrastBorder -}, nls.localize('notificationCenterBorder', "Notifications center border color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationCenterBorder', "Notifications center border color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_TOAST_BORDER = registerColor('notificationToast.border', { dark: null, light: null, hc: contrastBorder -}, nls.localize('notificationToastBorder', "Notification toast border color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationToastBorder', "Notification toast border color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_FOREGROUND = registerColor('notifications.foreground', { dark: editorWidgetForeground, light: editorWidgetForeground, hc: editorWidgetForeground -}, nls.localize('notificationsForeground', "Notifications foreground color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsForeground', "Notifications foreground color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_BACKGROUND = registerColor('notifications.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hc: editorWidgetBackground -}, nls.localize('notificationsBackground', "Notifications background color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsBackground', "Notifications background color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_LINKS = registerColor('notificationLink.foreground', { dark: textLinkForeground, light: textLinkForeground, hc: textLinkForeground -}, nls.localize('notificationsLink', "Notification links foreground color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsLink', "Notification links foreground color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_CENTER_HEADER_FOREGROUND = registerColor('notificationCenterHeader.foreground', { dark: null, light: null, hc: null -}, nls.localize('notificationCenterHeaderForeground', "Notifications center header foreground color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationCenterHeaderForeground', "Notifications center header foreground color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_CENTER_HEADER_BACKGROUND = registerColor('notificationCenterHeader.background', { dark: lighten(NOTIFICATIONS_BACKGROUND, 0.3), light: darken(NOTIFICATIONS_BACKGROUND, 0.05), hc: NOTIFICATIONS_BACKGROUND -}, nls.localize('notificationCenterHeaderBackground', "Notifications center header background color. Notifications slide in from the bottom right of the window.")); +}, localize('notificationCenterHeaderBackground', "Notifications center header background color. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_BORDER = registerColor('notifications.border', { dark: NOTIFICATIONS_CENTER_HEADER_BACKGROUND, light: NOTIFICATIONS_CENTER_HEADER_BACKGROUND, hc: NOTIFICATIONS_CENTER_HEADER_BACKGROUND -}, nls.localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground -}, nls.localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground -}, nls.localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoForeground -}, nls.localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window.")); +}, localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window.")); export const WINDOW_ACTIVE_BORDER = registerColor('window.activeBorder', { dark: null, light: null, hc: contrastBorder -}, nls.localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client when using the custom title bar.")); +}, localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client when using the custom title bar.")); export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { dark: null, light: null, hc: contrastBorder -}, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); +}, localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 85c2711cb46..bcdb3736d19 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -26,9 +26,9 @@ import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -const _ctxHasCallHierarchyProvider = new RawContextKey('editorHasCallHierarchyProvider', false); -const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); -const _ctxCallHierarchyDirection = new RawContextKey('callHierarchyDirection', undefined); +const _ctxHasCallHierarchyProvider = new RawContextKey('editorHasCallHierarchyProvider', false, localize('editorHasCallHierarchyProvider', 'Whether a call hierarchy provider is available')); +const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false, localize('callHierarchyVisible', 'Whether call hierarchy peek is currently showing')); +const _ctxCallHierarchyDirection = new RawContextKey('callHierarchyDirection', undefined, { type: 'string', description: localize('callHierarchyDirection', 'Whether call hierarchy shows incoming or outgoing calls') }); function sanitizedDirection(candidate: string): CallHierarchyDirection { return candidate === CallHierarchyDirection.CallsFrom || candidate === CallHierarchyDirection.CallsTo diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 41840b2342c..14d3d0e67a8 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -377,7 +377,7 @@ class BreakpointsRenderer implements IListRenderer Promise) }[] = []; + private debugOptions: { label: string, handler: (() => Promise) }[] = []; private toDispose: IDisposable[]; private selected = 0; private providers: { label: string, type: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[] = []; @@ -47,6 +46,7 @@ export class StartDebugActionViewItem implements IActionViewItem { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextViewService contextViewService: IContextViewService, ) { + super(context, action); this.toDispose = []; this.selectBox = new SelectBox([], -1, contextViewService, undefined, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') }); this.toDispose.push(this.selectBox); @@ -72,7 +72,6 @@ export class StartDebugActionViewItem implements IActionViewItem { this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); this.start.title = this.action.label; this.start.setAttribute('role', 'button'); - this.start.tabIndex = 0; this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { this.start.blur(); @@ -104,7 +103,7 @@ export class StartDebugActionViewItem implements IActionViewItem { } })); this.toDispose.push(this.selectBox.onDidSelect(async e => { - const target = this.options[e.index]; + const target = this.debugOptions[e.index]; const shouldBeSelected = target.handler ? await target.handler() : false; if (shouldBeSelected) { this.selected = e.index; @@ -151,11 +150,13 @@ export class StartDebugActionViewItem implements IActionViewItem { if (fromRight) { this.selectBox.focus(); } else { + this.start.tabIndex = 0; this.start.focus(); } } blur(): void { + this.start.tabIndex = -1; this.container.blur(); } @@ -165,7 +166,7 @@ export class StartDebugActionViewItem implements IActionViewItem { private updateOptions(): void { this.selected = 0; - this.options = []; + this.debugOptions = []; const manager = this.debugService.getConfigurationManager(); const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; let lastGroup: string | undefined; @@ -173,17 +174,17 @@ export class StartDebugActionViewItem implements IActionViewItem { manager.getAllConfigurations().forEach(({ launch, name, presentation }) => { if (lastGroup !== presentation?.group) { lastGroup = presentation?.group; - if (this.options.length) { - this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); - disabledIdxs.push(this.options.length - 1); + if (this.debugOptions.length) { + this.debugOptions.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); + disabledIdxs.push(this.debugOptions.length - 1); } } if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { - this.selected = this.options.length; + this.selected = this.debugOptions.length; } const label = inWorkspace ? `${name} (${launch.name})` : name; - this.options.push({ + this.debugOptions.push({ label, handler: async () => { await manager.selectConfiguration(launch, name); return true; @@ -194,9 +195,9 @@ export class StartDebugActionViewItem implements IActionViewItem { // Only take 3 elements from the recent dynamic configurations to not clutter the dropdown manager.getRecentDynamicConfigurations().slice(0, 3).forEach(({ name, type }) => { if (type === manager.selectedConfiguration.type && manager.selectedConfiguration.name === name) { - this.selected = this.options.length; + this.selected = this.debugOptions.length; } - this.options.push({ + this.debugOptions.push({ label: name, handler: async () => { await manager.selectConfiguration(undefined, name, undefined, { type }); @@ -205,16 +206,16 @@ export class StartDebugActionViewItem implements IActionViewItem { }); }); - if (this.options.length === 0) { - this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false }); + if (this.debugOptions.length === 0) { + this.debugOptions.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false }); } - this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); - disabledIdxs.push(this.options.length - 1); + this.debugOptions.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) }); + disabledIdxs.push(this.debugOptions.length - 1); this.providers.forEach(p => { - this.options.push({ + this.debugOptions.push({ label: `${p.label}...`, handler: async () => { const picked = await p.pick(); @@ -229,7 +230,7 @@ export class StartDebugActionViewItem implements IActionViewItem { manager.getLaunches().filter(l => !l.hidden).forEach(l => { const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); - this.options.push({ + this.debugOptions.push({ label, handler: async () => { await this.commandService.executeCommand(ADD_CONFIGURATION_ID, l.uri.toString()); return false; @@ -237,7 +238,7 @@ export class StartDebugActionViewItem implements IActionViewItem { }); }); - this.selectBox.setOptions(this.options.map((data, index) => { text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 }), this.selected); + this.selectBox.setOptions(this.debugOptions.map((data, index) => { text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 }), this.selected); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index f3fa36a2c2e..3fb7a826cd7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import * as strings from 'vs/base/common/strings'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as env from 'vs/base/common/platform'; import { visit } from 'vs/base/common/json'; @@ -11,7 +12,10 @@ import { setProperty } from 'vs/base/common/jsonEdit'; import { Constants } from 'vs/base/common/uint'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { StandardTokenType } from 'vs/editor/common/modes'; +import { InlineValuesProviderRegistry, StandardTokenType } from 'vs/editor/common/modes'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { flatten } from 'vs/base/common/arrays'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; @@ -40,6 +44,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Event } from 'vs/base/common/event'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/; const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration'; @@ -47,7 +52,12 @@ const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped -function createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions { +class InlineSegment { + constructor(public column: number, public text: string) { + } +} + +function createInlineValueDecoration(lineNumber: number, contentText: string, column = Constants.MAX_SAFE_SMALL_INTEGER): IDecorationOptions { // If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) { contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; @@ -57,8 +67,8 @@ function createInlineValueDecoration(lineNumber: number, contentText: string): I range: { startLineNumber: lineNumber, endLineNumber: lineNumber, - startColumn: Constants.MAX_SAFE_SMALL_INTEGER, - endColumn: Constants.MAX_SAFE_SMALL_INTEGER + startColumn: column, + endColumn: column }, renderOptions: { after: { @@ -561,6 +571,10 @@ export class DebugEditorContribution implements IDebugEditorContribution { } private async updateInlineValueDecorations(stackFrame: IStackFrame | undefined): Promise { + + const var_value_format = '{0} = {1}'; + const separator = ', '; + const model = this.editor.getModel(); if (!this.configurationService.getValue('debug').inlineValues || !model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) { @@ -572,20 +586,117 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.removeInlineValuesScheduler.cancel(); - const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range); - // Get all top level children in the scope chain - const decorationsPerScope = await Promise.all(scopes.map(async scope => { - const children = await scope.getChildren(); - let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn); - if (scope.range) { - range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); - } + let allDecorations: IDecorationOptions[]; - return createInlineValueDecorationsInsideRange(children, range, model, this.wordToLineNumbersMap); - })); + if (InlineValuesProviderRegistry.has(model)) { + const findVariable = async (_key: string, caseSensitiveLookup: boolean): Promise => { + const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range); + const key = caseSensitiveLookup ? _key : _key.toLowerCase(); + for (let scope of scopes) { + const variables = await scope.getChildren(); + const found = variables.find(v => caseSensitiveLookup ? (v.name === key) : (v.name.toLowerCase() === key)); + if (found) { + return found.value; + } + } + return undefined; + }; + + const ranges = this.editor.getVisibleRangesPlusViewportAboveBelow(); + + const ctx = { stoppedLocation: new Range(1, 1, stackFrame.range.startLineNumber, stackFrame.range.startColumn) }; + const token = new CancellationTokenSource().token; + + const providers = InlineValuesProviderRegistry.ordered(model).reverse(); + + allDecorations = []; + + const lineDecorations = new Map(); + + const promises = flatten(providers.map(provider => ranges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, token)).then(async (result) => { + if (result) { + for (let iv of result) { + + let text: string | undefined = undefined; + switch (iv.type) { + case 'text': + text = iv.text; + break; + case 'variable': + let va = iv.variableName; + if (!va) { + const lineContent = model.getLineContent(iv.range.startLineNumber); + va = lineContent.substring(iv.range.startColumn - 1, iv.range.endColumn - 1); + } + const value = await findVariable(va, iv.caseSensitiveLookup); + if (value) { + text = strings.format(var_value_format, va, value); + } + break; + case 'expression': + let expr = iv.expression; + if (!expr) { + const lineContent = model.getLineContent(iv.range.startLineNumber); + expr = lineContent.substring(iv.range.startColumn - 1, iv.range.endColumn - 1); + } + if (expr) { + const viewModel = this.debugService.getViewModel(); + const expression = new Expression(expr); + await expression.evaluate(viewModel.focusedSession, viewModel.focusedStackFrame, 'watch'); + if (expression.available) { + text = strings.format(var_value_format, expr, expression.value); + } + } + break; + } + + if (text) { + const line = iv.range.startLineNumber; + let lineSegments = lineDecorations.get(line); + if (!lineSegments) { + lineSegments = []; + lineDecorations.set(line, lineSegments); + } + lineSegments.push(new InlineSegment(range.startColumn, text)); + } + } + } + }, err => { + onUnexpectedExternalError(err); + })))); + + await Promise.all(promises); + + // sort line segments and concatenate them into a decoration + + lineDecorations.forEach((segments, line) => { + if (segments.length > 0) { + segments = segments.sort((a, b) => a.column - b.column); + const text = segments.map(s => s.text).join(separator); + allDecorations.push(createInlineValueDecoration(line, text)); + } + }); + + } else { + // old "one-size-fits-all" strategy + + const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range); + // Get all top level variables in the scope chain + const decorationsPerScope = await Promise.all(scopes.map(async scope => { + const variables = await scope.getChildren(); + + let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn); + if (scope.range) { + range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); + } + + return createInlineValueDecorationsInsideRange(variables, range, model, this.wordToLineNumbersMap); + })); + + allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []); + } - const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []); this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations); } diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 5139f3384dd..d1c14273d55 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/debugViewlet'; import * as nls from 'vs/nls'; -import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID, CONTEXT_DEBUG_STATE, ILaunch, getStateLabel, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -30,6 +30,7 @@ import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { FOCUS_SESSION_ID, SELECT_AND_START_ID, DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export class DebugViewPaneContainer extends ViewPaneContainer { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 5487c7f7e11..a17503cf544 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -150,7 +150,8 @@ margin-right: 1px; } -.debug-pane .monaco-list-row:hover .monaco-action-bar { +.debug-pane .monaco-list-row:hover .monaco-action-bar, +.debug-pane .monaco-list-row.focused .monaco-action-bar { display: initial; } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 46cd8f54f9d..b8f12426720 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/repl'; import { URI as uri } from 'vs/base/common/uri'; -import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -62,6 +62,7 @@ import { ReplFilter, ReplFilterState, ReplFilterActionViewItem } from 'vs/workbe import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { registerAction2, MenuId, Action2, IMenuService, IMenu } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index cecfebf4939..0d4d32e914e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -720,36 +720,6 @@ export interface IExtensionActionViewItemOptions extends IActionViewItemOptions tabOnlyOnFocus?: boolean; } -export class ExtensionActionViewItem extends ActionViewItem { - - constructor(context: any, action: IAction, options: IExtensionActionViewItemOptions = {}) { - super(context, action, options); - } - - updateEnabled(): void { - super.updateEnabled(); - - if (this.label && (this.options).tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { - DOM.removeTabIndexAndUpdateFocus(this.label); - } - } - - private _hasFocus: boolean = false; - setFocus(value: boolean): void { - if (!(this.options).tabOnlyOnFocus || this._hasFocus === value) { - return; - } - this._hasFocus = value; - if (this.label && this.getAction().enabled) { - if (this._hasFocus) { - this.label.tabIndex = 0; - } else { - DOM.removeTabIndexAndUpdateFocus(this.label); - } - } - } -} - export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdownActionViewItem { constructor( @@ -773,29 +743,6 @@ export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdow } } - updateEnabled(): void { - super.updateEnabled(); - - if (this.label && (this.options).tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { - DOM.removeTabIndexAndUpdateFocus(this.label); - } - } - - private _hasFocus: boolean = false; - setFocus(value: boolean): void { - if (!(this.options).tabOnlyOnFocus || this._hasFocus === value) { - return; - } - this._hasFocus = value; - if (this.label && this.getAction().enabled) { - if (this._hasFocus) { - this.label.tabIndex = 0; - } else { - DOM.removeTabIndexAndUpdateFocus(this.label); - } - } - } - } export abstract class ExtensionDropDownAction extends ExtensionAction { @@ -805,7 +752,6 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { label: string, cssClass: string, enabled: boolean, - private readonly tabOnlyOnFocus: boolean, @IInstantiationService protected instantiationService: IInstantiationService ) { super(id, label, cssClass, enabled); @@ -813,7 +759,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { private _actionViewItem: DropDownMenuActionViewItem | null = null; createActionViewItem(): DropDownMenuActionViewItem { - this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, this.tabOnlyOnFocus); + this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this); return this._actionViewItem; } @@ -825,13 +771,12 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } } -export class DropDownMenuActionViewItem extends ExtensionActionViewItem { +export class DropDownMenuActionViewItem extends ActionViewItem { constructor(action: ExtensionDropDownAction, - tabOnlyOnFocus: boolean, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { - super(null, action, { icon: true, label: true, tabOnlyOnFocus }); + super(null, action, { icon: true, label: true }); } public showMenu(menuActionGroups: IAction[][], disposeActionsOnHide: boolean): void { @@ -905,7 +850,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, ) { - super(ManageExtensionAction.ID, '', '', true, true, instantiationService); + super(ManageExtensionAction.ID, '', '', true, instantiationService); this.tooltip = localize('manage', "Manage"); @@ -977,7 +922,7 @@ export class ExtensionEditorManageExtensionAction extends ExtensionDropDownActio constructor( @IInstantiationService instantiationService: IInstantiationService ) { - super('extensionEditor.manageExtension', '', `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, true, true, instantiationService); + super('extensionEditor.manageExtension', '', `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, true, instantiationService); this.tooltip = localize('manage', "Manage"); } @@ -2003,7 +1948,7 @@ export class ToggleSyncExtensionAction extends ExtensionDropDownAction { @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IInstantiationService instantiationService: IInstantiationService, ) { - super('extensions.sync', '', ToggleSyncExtensionAction.SYNC_CLASS, false, true, instantiationService); + super('extensions.sync', '', ToggleSyncExtensionAction.SYNC_CLASS, false, instantiationService); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('settingsSync.ignoredExtensions'))(() => this.update())); this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => this.update())); this.update(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 81a2610a66b..563e4cbc65a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -14,7 +14,7 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; @@ -97,10 +97,10 @@ export class Renderer implements IPagedRenderer { if (action instanceof ExtensionDropDownAction) { return action.createActionViewItem(); } - return new ExtensionActionViewItem(null, action, actionOptions); - }, - respectOrientationForPreviousAndNextKey: true + return undefined; + } }); + actionbar.setFocusable(false); actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction); @@ -208,21 +208,13 @@ export class Renderer implements IPagedRenderer { this.extensionViewState.onFocus(e => { if (areSameExtensions(extension.identifier, e.identifier)) { - data.actionbar.viewItems.forEach(item => { - if (item instanceof ExtensionActionViewItem || item instanceof ExtensionActionWithDropdownActionViewItem) { - item.setFocus(true); - } - }); + data.actionbar.setFocusable(true); } }, this, data.extensionDisposables); this.extensionViewState.onBlur(e => { if (areSameExtensions(extension.identifier, e.identifier)) { - data.actionbar.viewItems.forEach(item => { - if (item instanceof ExtensionActionViewItem || item instanceof ExtensionActionWithDropdownActionViewItem) { - item.setFocus(false); - } - }); + data.actionbar.setFocusable(false); } }, this, data.extensionDisposables); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index e0378866cc4..f2554726ef5 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -20,18 +20,18 @@ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorIn import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TextFileContentProvider } from 'vs/workbench/contrib/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { SAVE_FILE_AS_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; import { INotificationService, INotificationHandle, INotificationActions, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; import { IProductService } from 'vs/platform/product/common/productService'; import { Event } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { SaveReason } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, SaveReason } from 'vs/workbench/common/editor'; +import { GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; @@ -126,7 +126,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa message = nls.localize('staleSaveError', "Failed to save '{0}': The content of the file is newer. Please compare your version with the file contents or overwrite the content of the file with your changes.", basename(resource)); primaryActions.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model)); - primaryActions.push(this.instantiationService.createInstance(SaveIgnoreModifiedSinceAction, model)); + primaryActions.push(this.instantiationService.createInstance(SaveModelIgnoreModifiedSinceAction, model)); secondaryActions.push(this.instantiationService.createInstance(ConfigureSaveConflictAction)); } @@ -141,24 +141,24 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa // Save Elevated if (canHandlePermissionOrReadonlyErrors && (isPermissionDenied || triedToMakeWriteable)) { - primaryActions.push(this.instantiationService.createInstance(SaveElevatedAction, model, !!triedToMakeWriteable)); + primaryActions.push(this.instantiationService.createInstance(SaveModelElevatedAction, model, !!triedToMakeWriteable)); } // Overwrite else if (canHandlePermissionOrReadonlyErrors && isReadonly) { - primaryActions.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); + primaryActions.push(this.instantiationService.createInstance(OverwriteReadonlyModelAction, model)); } // Retry else { - primaryActions.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_COMMAND_ID, nls.localize('retry', "Retry"))); + primaryActions.push(this.instantiationService.createInstance(RetrySaveModelAction, model)); } // Save As - primaryActions.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL)); + primaryActions.push(this.instantiationService.createInstance(SaveModelAsAction, model)); // Discard - primaryActions.push(this.instantiationService.createInstance(ExecuteCommandAction, REVERT_FILE_COMMAND_ID, nls.localize('discard', "Discard"))); + primaryActions.push(this.instantiationService.createInstance(DiscardModelAction, model)); // Message if (canHandlePermissionOrReadonlyErrors && isReadonly) { @@ -261,13 +261,13 @@ class ResolveSaveConflictAction extends Action { } } -class SaveElevatedAction extends Action { +class SaveModelElevatedAction extends Action { constructor( private model: ITextFileEditorModel, private triedToMakeWriteable: boolean ) { - super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo...")); + super('workbench.files.action.saveModelElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo...")); } async run(): Promise { @@ -281,7 +281,70 @@ class SaveElevatedAction extends Action { } } -class OverwriteReadonlyAction extends Action { +class RetrySaveModelAction extends Action { + + constructor( + private model: ITextFileEditorModel + ) { + super('workbench.files.action.saveModel', nls.localize('retry', "Retry")); + } + + async run(): Promise { + if (!this.model.isDisposed()) { + await this.model.save({ reason: SaveReason.EXPLICIT }); + } + } +} + +class DiscardModelAction extends Action { + + constructor( + private model: ITextFileEditorModel + ) { + super('workbench.files.action.discardModel', nls.localize('discard', "Discard")); + } + + async run(): Promise { + if (!this.model.isDisposed()) { + await this.model.revert(); + } + } +} + +class SaveModelAsAction extends Action { + + constructor( + private model: ITextFileEditorModel, + private editorService: IEditorService, + private editorGroupService: IEditorGroupsService + ) { + super('workbench.files.action.saveModelAs', SAVE_FILE_AS_LABEL); + } + + async run(): Promise { + if (!this.model.isDisposed()) { + const editor = this.findEditor(); + if (editor) { + await this.editorService.save(editor, { saveAs: true, reason: SaveReason.EXPLICIT }); + } + } + } + + private findEditor(): IEditorIdentifier | undefined { + for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + const editors = this.editorService.findEditors(this.model.resource, group); + for (const editor of editors) { + if (editor instanceof FileEditorInput) { + return { editor, groupId: group.id }; + } + } + } + + return undefined; + } +} + +class OverwriteReadonlyModelAction extends Action { constructor( private model: ITextFileEditorModel @@ -296,7 +359,7 @@ class OverwriteReadonlyAction extends Action { } } -class SaveIgnoreModifiedSinceAction extends Action { +class SaveModelIgnoreModifiedSinceAction extends Action { constructor( private model: ITextFileEditorModel diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d33543aaf0f..f57fc7c8630 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -16,7 +16,7 @@ import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/ex import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IListService } from 'vs/platform/list/browser/listService'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -438,7 +438,6 @@ function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: ReadonlyAr async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { const editorService = accessor.get(IEditorService); const notificationService = accessor.get(INotificationService); - const commandService = accessor.get(ICommandService); try { await editorService.save(editors, options); @@ -449,9 +448,8 @@ async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentif message: nls.localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)), actions: { primary: [ - toAction({ id: SAVE_FILE_COMMAND_ID, label: nls.localize('retry', "Retry"), run: () => commandService.executeCommand(SAVE_FILE_COMMAND_ID) }), - toAction({ id: SAVE_FILE_AS_COMMAND_ID, label: SAVE_FILE_AS_LABEL, run: () => commandService.executeCommand(SAVE_FILE_AS_COMMAND_ID) }), - toAction({ id: REVERT_FILE_COMMAND_ID, label: nls.localize('discard', "Discard"), run: () => commandService.executeCommand(REVERT_FILE_COMMAND_ID) }) + toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => editorService.save(editors, options) }), + toAction({ id: 'workbench.action.files.revertEditors', label: nls.localize('discard', "Discard"), run: () => editorService.revert(editors) }) ] } }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 6bf781d2e31..67fdc77b546 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -1229,7 +1229,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { res = await reader.read(); } - writeableStream.end(res.value instanceof Uint8Array ? VSBuffer.wrap(res.value) : undefined); + writeableStream.end(undefined); } catch (error) { writeableStream.end(error); } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index bb751c3785c..89b91bb4916 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -536,7 +536,7 @@ class EditorGroupRenderer implements IListRenderer action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action) : undefined, - respectOrientationForPreviousAndNextKey: true + actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); this.icon = dom.append(parent, dom.$('')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')), { diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index b2a7ce0f6f5..5f8de976926 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/markers'; import { URI } from 'vs/base/common/uri'; import * as dom from 'vs/base/browser/dom'; -import { IAction, IActionViewItem, Action, Separator } from 'vs/base/common/actions'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; @@ -32,7 +32,7 @@ import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index ab61c29ec14..3cdf11ce45c 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -13,14 +13,17 @@ export const CELL_RUN_GUTTER = 28; export const CODE_CELL_LEFT_MARGIN = 32; export const EDITOR_TOOLBAR_HEIGHT = 0; -export const BOTTOM_CELL_TOOLBAR_GAP = 16; -export const BOTTOM_CELL_TOOLBAR_HEIGHT = 24; +export const BOTTOM_CELL_TOOLBAR_GAP = 18; +export const BOTTOM_CELL_TOOLBAR_HEIGHT = 22; export const CELL_STATUSBAR_HEIGHT = 22; // Margin above editor export const CELL_TOP_MARGIN = 6; export const CELL_BOTTOM_MARGIN = 6; +export const MARKDOWN_CELL_TOP_MARGIN = 8; +export const MARKDOWN_CELL_BOTTOM_MARGIN = 8; + // Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` // export const EDITOR_TOP_PADDING = 12; export const EDITOR_BOTTOM_PADDING = 4; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 26772735238..61e55c38886 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -273,23 +273,31 @@ registerAction2(class ExecuteCell extends NotebookCellAction { } getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined { - if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { + if (!context) { return; } + if (typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { + throw new Error(`The first argument '${context}' is not a valid CellRange`); + } + if (additionalArgs.length && additionalArgs[0]) { const uri = URI.revive(additionalArgs[0]); - if (uri) { - const widget = getWidgetFromUri(accessor, uri); - if (widget) { - const cells = widget.viewModel.viewCells; + if (!uri) { + throw new Error(`The second argument '${uri}' is not a valid Uri`); + } - return { - notebookEditor: widget, - cell: cells[context.start] - }; - } + const widget = getWidgetFromUri(accessor, uri); + if (widget) { + const cells = widget.viewModel.viewCells; + + return { + notebookEditor: widget, + cell: cells[context.start] + }; + } else { + throw new Error(`There is no editor opened for resource ${uri}`); } } @@ -1607,19 +1615,81 @@ interface ILanguagePickInput extends IQuickPickItem { description: string; } -export class ChangeCellLanguageAction extends NotebookCellAction { + +interface IChangeCellContext extends INotebookCellActionContext { + // TODO@rebornix : `cells` + // range: ICellRange; + language?: string; +} + +export class ChangeCellLanguageAction extends NotebookCellAction { constructor() { super({ id: CHANGE_CELL_LANGUAGE, title: localize('changeLanguage', 'Change Cell Language'), + description: { + description: localize('changeLanguage', 'Change Cell Language'), + args: [ + { + name: 'range', + description: 'The cell range', + schema: { + 'type': 'object', + 'required': ['start', 'end'], + 'properties': { + 'start': { + 'type': 'number' + }, + 'end': { + 'type': 'number' + } + } + } + }, + { + name: 'language', + description: 'The target cell language', + schema: { + 'type': 'string' + } + } + ] + } }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - this.showLanguagePicker(accessor, context); + protected getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): IChangeCellContext | undefined { + if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) { + return; + } + + const language = additionalArgs.length && typeof additionalArgs[0] === 'string' ? additionalArgs[0] : undefined; + const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor); + + if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) { + return; + } + + const cells = activeEditorContext.notebookEditor.viewModel.viewCells; + + // TODO@rebornix, support multiple cells + return { + notebookEditor: activeEditorContext.notebookEditor, + cell: cells[context.start], + language + }; } - private async showLanguagePicker(accessor: ServicesAccessor, context: INotebookCellActionContext) { + + async runWithContext(accessor: ServicesAccessor, context: IChangeCellContext): Promise { + if (context.language) { + await this.setLanguage(context, context.language); + } else { + await this.showLanguagePicker(accessor, context); + } + } + + private async showLanguagePicker(accessor: ServicesAccessor, context: IChangeCellContext) { const topItems: ILanguagePickInput[] = []; const mainItems: ILanguagePickInput[] = []; @@ -1671,21 +1741,25 @@ export class ChangeCellLanguageAction extends NotebookCellAction { const selection = await quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode") }) as ILanguagePickInput | undefined; if (selection && selection.languageId) { - if (selection.languageId === 'markdown' && context.cell?.language !== 'markdown') { - const newCell = await changeCellToKind(CellKind.Markdown, { cell: context.cell, notebookEditor: context.notebookEditor }, 'markdown'); - if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, 'editor'); - } - } else if (selection.languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markdown) { - await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, selection.languageId); - } else { - const index = context.notebookEditor.viewModel.notebookDocument.cells.indexOf(context.cell.model); - context.notebookEditor.viewModel.notebookDocument.applyEdits( - context.notebookEditor.viewModel.notebookDocument.versionId, - [{ editType: CellEditType.CellLanguage, index, language: selection.languageId }], - true, undefined, () => undefined, undefined - ); + await this.setLanguage(context, selection.languageId); + } + } + + private async setLanguage(context: IChangeCellContext, languageId: string) { + if (languageId === 'markdown' && context.cell?.language !== 'markdown') { + const newCell = await changeCellToKind(CellKind.Markdown, { cell: context.cell, notebookEditor: context.notebookEditor }, 'markdown'); + if (newCell) { + context.notebookEditor.focusNotebookCell(newCell, 'editor'); } + } else if (languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markdown) { + await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, languageId); + } else { + const index = context.notebookEditor.viewModel.notebookDocument.cells.indexOf(context.cell.model); + context.notebookEditor.viewModel.notebookDocument.applyEdits( + context.notebookEditor.viewModel.notebookDocument.versionId, + [{ editType: CellEditType.CellLanguage, index, language: languageId }], + true, undefined, () => undefined, undefined + ); } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 24f880fbabf..235dd429996 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -150,8 +150,7 @@ class PropertyHeader extends Disposable { } return undefined; - }, - respectOrientationForPreviousAndNextKey: true + } }); this._register(this._toolbar); this._toolbar.context = { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 81390f3d9ae..8112dbcedc2 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -610,6 +610,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return cellInfo.diffElement.getCellByUri(cellInfo.cellUri); } + getCellById(cellId: string): IGenericCellViewModel | undefined { + throw new Error('Not implemented'); + } + removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index e25db2f37b0..ce2bc9c6ec2 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -188,8 +188,7 @@ export class CellDiffSideBySideRenderer implements IListRenderer .cell-list-container > .monaco-list:focus-within > .monaco-scrollable-element > .monaco-list-rows:not(:hover) > .monaco-list-row.focused .cell-has-toolbar-actions .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell-has-toolbar-actions .cell-title-toolbar, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .markdown-cell-hover.cell-has-toolbar-actions .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions.cell-output-hover .cell-title-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-has-toolbar-actions:hover .cell-title-toolbar { visibility: visible; @@ -640,6 +638,7 @@ .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container:focus-within, .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container:hover, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell-bottom-toolbar-container, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .markdown-cell-hover .cell-bottom-toolbar-container, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list:focus-within > .monaco-scrollable-element > .monaco-list-rows:not(:hover) > .monaco-list-row.focused .cell-bottom-toolbar-container, .monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:focus-within { opacity: 1; @@ -647,7 +646,12 @@ .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .monaco-toolbar, .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .monaco-toolbar { - margin: 0px 8px; + margin: -2px 0px 8px; +} + +.monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .monaco-toolbar, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.markdown-cell-row .cell-bottom-toolbar-container .monaco-toolbar { + margin-top: 0px } .monaco-workbench .notebookOverlay .cell-list-top-cell-toolbar-container .monaco-toolbar .action-item, @@ -833,7 +837,7 @@ width: 20px; position: absolute; - top: 6px; + top: 10px; left: 8px; display: flex; justify-content: center; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index a67a588bbeb..192e9bd0c68 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -162,6 +162,7 @@ export interface ICommonNotebookEditor { getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo; triggerScroll(event: IMouseWheelEvent): void; getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; + getCellById(cellId: string): IGenericCellViewModel | undefined; focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; @@ -604,6 +605,7 @@ export interface INotebookEditor extends IEditor, ICommonNotebookEditor { getContribution(id: string): T; getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel; + getCellById(cellId: string): ICellViewModel | undefined; updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; } @@ -774,14 +776,15 @@ export enum CursorAtBoundary { } export interface CellViewModelStateChangeEvent { - metadataChanged?: boolean; - selectionChanged?: boolean; - focusModeChanged?: boolean; - editStateChanged?: boolean; - languageChanged?: boolean; - foldingStateChanged?: boolean; - contentChanged?: boolean; - outputIsHoveredChanged?: boolean; + readonly metadataChanged?: boolean; + readonly selectionChanged?: boolean; + readonly focusModeChanged?: boolean; + readonly editStateChanged?: boolean; + readonly languageChanged?: boolean; + readonly foldingStateChanged?: boolean; + readonly contentChanged?: boolean; + readonly outputIsHoveredChanged?: boolean; + readonly cellIsHoveredChanged?: boolean; } export function cellRangesEqual(a: ICellRange[], b: ICellRange[]) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts index 8442f6e3781..604cad0e755 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts @@ -12,8 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IReference } from 'vs/base/common/lifecycle'; -import { INotebookEditorModel, INotebookDiffEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { INotebookDiffEditorModel, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; interface NotebookEditorInputOptions { startDirty?: boolean; @@ -21,8 +20,8 @@ interface NotebookEditorInputOptions { class NotebookDiffEditorModel extends EditorModel implements INotebookDiffEditorModel { constructor( - readonly original: NotebookEditorModel, - readonly modified: NotebookEditorModel, + readonly original: IResolvedNotebookEditorModel, + readonly modified: IResolvedNotebookEditorModel, ) { super(); } @@ -54,8 +53,8 @@ export class NotebookDiffEditorInput extends EditorInput { static readonly ID: string = 'workbench.input.diffNotebookInput'; - private _textModel: IReference | null = null; - private _originalTextModel: IReference | null = null; + private _textModel: IReference | null = null; + private _originalTextModel: IReference | null = null; private _defaultDirtyState: boolean = false; constructor( @@ -186,10 +185,12 @@ ${patterns} if (!this._textModel) { this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!); + } + if (!this._originalTextModel) { this._originalTextModel = await this._notebookModelResolverService.resolve(this.originalResource, this.viewType!); } - return new NotebookDiffEditorModel(this._originalTextModel!.object as NotebookEditorModel, this._textModel.object as NotebookEditorModel); + return new NotebookDiffEditorModel(this._originalTextModel.object, this._textModel.object); } matches(otherInput: unknown): boolean { @@ -204,10 +205,10 @@ ${patterns} } dispose() { - if (this._textModel) { - this._textModel.dispose(); - this._textModel = null; - } + this._textModel?.dispose(); + this._textModel = null; + this._originalTextModel?.dispose(); + this._originalTextModel = null; super.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 1025ccaf67b..7128a683466 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -173,7 +173,7 @@ export class NotebookEditor extends EditorPane { label: localize('fail.reOpen', "Reopen file with VS Code standard text editor"), run: async () => { const fileEditorInput = this._editorService.createEditorInput({ resource: input.resource, forceFile: true }); - const textOptions: IEditorOptions | ITextEditorOptions = options ? { ...options, override: false } : { override: false }; + const textOptions: IEditorOptions | ITextEditorOptions = { ...options, override: false }; await this._editorService.openEditor(fileEditorInput, textOptions); } }] diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index cd68acce0ec..dd6cb29677a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -12,7 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IReference } from 'vs/base/common/lifecycle'; -import { INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; interface NotebookEditorInputOptions { startDirty?: boolean; @@ -25,7 +25,7 @@ export class NotebookEditorInput extends EditorInput { static readonly ID: string = 'workbench.input.notebook'; - private _textModel: IReference | null = null; + private _textModel: IReference | null = null; private _defaultDirtyState: boolean = false; constructor( @@ -153,7 +153,7 @@ ${patterns} return; } - async resolve(): Promise { + async resolve(): Promise { if (!await this._notebookService.canResolve(this.viewType!)) { return null; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 6f8e12d51b9..4e29c273f56 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -40,7 +40,7 @@ import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; -import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; @@ -1978,11 +1978,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); + if (!this._webview) { + return; + } + const cellTop = this._list.getAbsoluteTopOfElement(cell); if (this._webview.markdownPreviewMapping.has(cell.id)) { - await this._webview!.showMarkdownPreview(cell.id, cell.getText(), cellTop); + await this._webview.showMarkdownPreview(cell.id, cell.getText(), cellTop); } else { - await this._webview!.createMarkdownPreview(cell.id, cell.getText(), cellTop); + await this._webview.createMarkdownPreview(cell.id, cell.getText(), cellTop); } } @@ -1993,7 +1997,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); - await this._webview!.hideMarkdownPreview(cell.id); + await this._webview?.hideMarkdownPreview(cell.id); } async removeMarkdownPreview(cell: MarkdownCellViewModel) { @@ -2003,7 +2007,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._resolveWebview(); - await this._webview!.removeMarkdownPreview(cell.id); + await this._webview?.removeMarkdownPreview(cell.id); } async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { @@ -2080,6 +2084,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.viewModel?.viewCells.find(vc => vc.handle === cellHandle) as CodeCellViewModel; } + getCellById(cellId: string): ICellViewModel | undefined { + return this.viewModel?.viewCells.find(vc => vc.id === cellId); + } + updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean): void { const cell = this.viewModel?.viewCells.find(vc => vc.handle === cellInfo.cellHandle); if (cell && cell instanceof CodeCellViewModel) { @@ -2090,24 +2098,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean) { - const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId); - + const cell = this.getCellById(cellId); if (cell && cell instanceof MarkdownCellViewModel) { cell.renderedMarkdownHeight = height; } } setMarkdownCellEditState(cellId: string, editState: CellEditState): void { - const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId); - + const cell = this.getCellById(cellId); if (cell && cell instanceof MarkdownCellViewModel) { cell.editState = editState; } } markdownCellDragStart(cellId: string, ctx: { clientY: number }): void { - const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId); - + const cell = this.getCellById(cellId); if (cell && cell instanceof MarkdownCellViewModel) { this._dndController?.startExplicitDrag(cell, ctx); } @@ -2143,6 +2148,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._onWillDispose.fire(); // dispose webview first this._webview?.dispose(); + this._webview = null; this.notebookService.removeNotebookEditor(this); dispose(this._contributions.values()); @@ -2380,8 +2386,6 @@ registerThemingParticipant((theme, collector) => { collector.addRule(` .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-top:before, .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-focus-indicator-bottom:before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):after, .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container:not(.cell-editor-focus) .cell-focus-indicator-left:before, .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused .cell-inner-container:not(.cell-editor-focus) .cell-focus-indicator-right:before { border-color: ${focusedCellBorderColor} !important; @@ -2390,9 +2394,7 @@ registerThemingParticipant((theme, collector) => { const inactiveFocusedBorderColor = theme.getColor(inactiveFocusedCellBorder); collector.addRule(` .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, - .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):before, - .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused .cell-inner-container:not(.cell-editor-focus):after { + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before { border-color: ${inactiveFocusedBorderColor} !important; }`); @@ -2510,7 +2512,7 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${CELL_TOP_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${CELL_BOTTOM_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${MARKDOWN_CELL_BOTTOM_MARGIN}px; padding-top: ${MARKDOWN_CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); @@ -2532,6 +2534,7 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${SCROLLABLE_ELEMENT_PADDING_TOP}px }`); collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { height: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px }`); + collector.addRule(`.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before { top: -${CELL_TOP_MARGIN}px; height: calc(100% + ${CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN}px)}`); }); @@ -2610,15 +2613,12 @@ class DecorationCSSRules { const borderColor = this._resolveValue(this._providerArgs.options.borderColor); this._styleSheet.insertRule(`.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-top:before, - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-bottom:before, - .monaco-workbench .notebookOverlay .monaco-list .${this.className}.markdown-cell-row.focused:before, - .monaco-workbench .notebookOverlay .monaco-list .${this.className}.markdown-cell-row.focused:after { + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-bottom:before { border-color: ${borderColor} !important; }`); this._styleSheet.insertRule(` - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-bottom:before, - .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.${this.className}:after { + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-bottom:before { content: ""; position: absolute; width: 100%; @@ -2628,8 +2628,7 @@ class DecorationCSSRules { `); this._styleSheet.insertRule(` - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-top:before, - .monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.${this.className}:before { + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.${this.className} .cell-focus-indicator-top:before { content: ""; position: absolute; width: 100%; @@ -2639,9 +2638,7 @@ class DecorationCSSRules { // more specific rule for `.focused` can override existing rules this._styleSheet.insertRule(`.monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused.${this.className} .cell-focus-indicator-top:before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused.${this.className} .cell-focus-indicator-bottom:before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused.${this.className}:before, - .monaco-workbench .notebookOverlay .monaco-list:focus-within .markdown-cell-row.focused.${this.className}:after { + .monaco-workbench .notebookOverlay .monaco-list:focus-within .monaco-list-row.focused.${this.className} .cell-focus-indicator-bottom:before { border-color: ${borderColor} !important; }`); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 3aabfc323aa..33513e0b4e1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -10,7 +10,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; @@ -32,7 +31,7 @@ import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRe import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -507,41 +506,37 @@ export class NotebookService extends Disposable implements INotebookService, ICu const viewModel = editor.viewModel; - if (!viewModel) { + if (!viewModel || !viewModel.metadata.editable) { return false; } - if (!viewModel.metadata.editable) { - return false; - } + const cloneMetadata = (cell: NotebookCellTextModel) => { + return { + editable: cell.metadata?.editable, + runnable: cell.metadata?.runnable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + }; + }; + + const cloneCell = (cell: NotebookCellTextModel) => { + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs.map(output => ({ ...output, /* paste should generate new outputId */ outputId: UUID.generateUuid() })), + metadata: cloneMetadata(cell) + }; + }; if (activeCell) { const currCellIndex = viewModel.getCellIndex(activeCell); let topPastedCell: CellViewModel | undefined = undefined; - pasteCells.items.reverse().map(cell => { - return { - source: cell.getValue(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs.map(output => { - return { - ...output, - // paste should generate new outputId - outputId: UUID.generateUuid() - }; - }), - metadata: { - editable: cell.metadata?.editable, - runnable: cell.metadata?.runnable, - breakpointMargin: cell.metadata?.breakpointMargin, - hasExecutionOrder: cell.metadata?.hasExecutionOrder, - inputCollapsed: cell.metadata?.inputCollapsed, - outputCollapsed: cell.metadata?.outputCollapsed, - custom: cell.metadata?.custom - } - }; - }).forEach(pasteCell => { + pasteCells.items.reverse().map(cell => cloneCell(cell)).forEach(pasteCell => { const newIdx = typeof currCellIndex === 'number' ? currCellIndex + 1 : 0; topPastedCell = viewModel.createCell(newIdx, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); }); @@ -555,28 +550,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu } let topPastedCell: CellViewModel | undefined = undefined; - pasteCells.items.reverse().map(cell => { - return { - source: cell.getValue(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs.map(output => { - return { - ...output, - outputId: UUID.generateUuid() - }; - }), - metadata: { - editable: cell.metadata?.editable, - runnable: cell.metadata?.runnable, - breakpointMargin: cell.metadata?.breakpointMargin, - hasExecutionOrder: cell.metadata?.hasExecutionOrder, - inputCollapsed: cell.metadata?.inputCollapsed, - outputCollapsed: cell.metadata?.outputCollapsed, - custom: cell.metadata?.custom - } - }; - }).forEach(pasteCell => { + pasteCells.items.reverse().map(cell => cloneCell(cell)).forEach(pasteCell => { topPastedCell = viewModel.createCell(0, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); }); @@ -604,11 +578,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu const viewModel = editor.viewModel; - if (!viewModel) { - return false; - } - - if (!viewModel.metadata.editable) { + if (!viewModel || !viewModel.metadata.editable) { return false; } @@ -621,12 +591,17 @@ export class NotebookService extends Disposable implements INotebookService, ICu } clipboardService.writeText(selectedCells.map(cell => cell.getText()).join('\n')); + const selectionIndexes = selectedCells.map(cell => [cell, viewModel.getCellIndex(cell)] as [ICellViewModel, number]).sort((a, b) => b[1] - a[1]); + const edits: ICellEditOperation[] = selectionIndexes.map(value => ({ editType: CellEditType.Replace, index: value[1], count: 1, cells: [] })); - const edits: ICellEditOperation[] = selectedCells.map(cell => [cell, viewModel.getCellIndex(cell)] as [ICellViewModel, number]).sort((a, b) => b[1] - a[1]).map(value => { - return { editType: CellEditType.Replace, index: value[1], count: 1, cells: [] }; - }); - - viewModel.notebookDocument.applyEdits(viewModel.notebookDocument.versionId, edits, true, editor.getSelectionHandles(), () => { return undefined; }, undefined, true); + viewModel.notebookDocument.applyEdits(viewModel.notebookDocument.versionId, edits, true, editor.getSelectionHandles(), () => { + const firstSelectIndex = selectionIndexes[0][1]; + if (firstSelectIndex < viewModel.notebookDocument.cells.length) { + return [viewModel.notebookDocument.cells[firstSelectIndex].handle]; + } else { + return [viewModel.notebookDocument.cells[viewModel.notebookDocument.cells.length - 1].handle]; + } + }, undefined, true); notebookService.setToCopy(selectedCells.map(cell => cell.model), false); return true; @@ -724,6 +699,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return toDisposable(() => { kernelChangeEventListener.dispose(); d.dispose(); + this._onDidChangeKernels.fire(undefined); }); } @@ -751,45 +727,22 @@ export class NotebookService extends Disposable implements INotebookService, ICu return Array.from(this.markdownRenderersInfos); } - async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise { - + async fetchNotebookRawData(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }> { if (!await this.canResolve(viewType)) { - throw new Error(`CANNOT load notebook, no provider for '${viewType}'`); + throw new Error(`CANNOT fetch notebook data, there is NO provider for '${viewType}'`); } - const provider = this._notebookProviders.get(viewType)!; - let notebookModel: NotebookTextModel; + return await provider.controller.openNotebook(viewType, uri, backupId); + } + + createNotebookTextModel(viewType: string, uri: URI, data: NotebookDataDto, transientOptions: TransientOptions): NotebookTextModel { if (this._models.has(uri)) { - // the model already exists - notebookModel = this._models.get(uri)!.model; - if (forceReload) { - await provider.controller.reloadNotebook(notebookModel); - } - return notebookModel; - - } else { - const dataDto = await provider.controller.resolveNotebookDocument(viewType, uri, backupId); - let cells = dataDto.data.cells.length ? dataDto.data.cells : (uri.scheme === Schemas.untitled ? [{ - cellKind: CellKind.Code, - language: 'plaintext', //TODO@jrieken unsure what this is - outputs: [], - metadata: undefined, - source: '' - }] : []); - - notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, provider.controller.supportBackup, uri, cells, dataDto.data.metadata, dataDto.transientOptions); + throw new Error(`notebook for ${uri} already exists`); } - - // new notebook model created - const modelData = new ModelData( - notebookModel, - (model) => this._onWillDisposeDocument(model), - ); - - this._models.set(uri, modelData); + const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, true, uri, data.cells, data.metadata, transientOptions); + this._models.set(uri, new ModelData(notebookModel, this._onWillDisposeDocument.bind(this))); this._onDidAddNotebookDocument.fire(notebookModel); - - return modelData.model; + return notebookModel; } getNotebookTextModel(uri: URI): NotebookTextModel | undefined { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index d41f0ce579b..72a6fac7b31 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -369,6 +369,9 @@ export class NotebookCellList extends WorkbenchList implements ID return model.getCellByHandle(handle); }).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); this.setSelection(viewSelections, undefined, true); + if (viewSelections.length) { + this.setFocus([viewSelections[0]]); + } })); const hiddenRanges = model.getHiddenRanges(); @@ -531,7 +534,11 @@ export class NotebookCellList extends WorkbenchList implements ID private _getViewIndexUpperBound(cell: ICellViewModel): number { - const modelIndex = this._viewModel!.getCellIndex(cell); + if (!this._viewModel) { + return -1; + } + + const modelIndex = this._viewModel.getCellIndex(cell); if (!this.hiddenRangesPrefixSum) { return modelIndex; } @@ -959,7 +966,8 @@ export class NotebookCellList extends WorkbenchList implements ID this._revealInternal(viewIndex, true, CellRevealPosition.Center); const element = this.view.element(viewIndex); - if (!element.editorAttached) { + // wait for the editor to be created only if the cell is in editing mode (meaning it has an editor and will focus the editor) + if (element.editState === CellEditState.Editing && !element.editorAttached) { return getEditorAttachedPromise(element); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index bc6c5860802..7d6765fc046 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -20,6 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; +import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewContentPurpose, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; @@ -35,9 +35,9 @@ export interface IDimensionMessage { __vscode_notebook_message: boolean; type: 'dimension'; id: string; - init: boolean; - data: DOM.Dimension; - isOutput: boolean; + init?: boolean; + data: { height: number }; + isOutput?: boolean; } export interface IMouseEnterMessage { @@ -76,10 +76,28 @@ export interface IBlurOutputMessage { export interface IClickedDataUrlMessage { __vscode_notebook_message: boolean; type: 'clicked-data-url'; - data: string; + data: string | ArrayBuffer | null; downloadName?: string; } +export interface IFocusMarkdownPreviewMessage { + __vscode_notebook_message: boolean; + type: 'focusMarkdownPreview'; + cellId: string; +} + +export interface IMouseEnterMarkdownPreviewMessage { + __vscode_notebook_message: boolean; + type: 'mouseEnterMarkdownPreview'; + cellId: string; +} + +export interface IMouseLeaveMarkdownPreviewMessage { + __vscode_notebook_message: boolean; + type: 'mouseLeaveMarkdownPreview'; + cellId: string; +} + export interface IToggleMarkdownPreviewMessage { __vscode_notebook_message: boolean; type: 'toggleMarkdownPreview'; @@ -111,6 +129,10 @@ export interface ICellDragEndMessage { readonly clientY: number; }; } +export interface IInitializedMarkdownPreviewMessage { + readonly __vscode_notebook_message: boolean; + readonly type: 'initializedMarkdownPreview'; +} export interface IClearMessage { type: 'clear'; @@ -264,10 +286,14 @@ export type FromWebviewMessage = | IBlurOutputMessage | ICustomRendererMessage | IClickedDataUrlMessage + | IFocusMarkdownPreviewMessage + | IMouseEnterMarkdownPreviewMessage + | IMouseLeaveMarkdownPreviewMessage | IToggleMarkdownPreviewMessage | ICellDragStartMessage | ICellDragMessage | ICellDragEndMessage + | IInitializedMarkdownPreviewMessage ; export type ToWebviewMessage = | IClearMessage @@ -783,8 +809,23 @@ var requirejs = (function() { this._onDidClickDataLink(data); } else if (data.type === 'customRendererMessage') { this._onMessage.fire({ message: data.message, forRenderer: data.rendererId }); + } else if (data.type === 'focusMarkdownPreview') { + const cell = this.notebookEditor.getCellById(data.cellId); + if (cell) { + this.notebookEditor.focusNotebookCell(cell, 'container'); + } } else if (data.type === 'toggleMarkdownPreview') { this.notebookEditor.setMarkdownCellEditState(data.cellId, CellEditState.Editing); + } else if (data.type === 'mouseEnterMarkdownPreview') { + const cell = this.notebookEditor.getCellById(data.cellId); + if (cell instanceof MarkdownCellViewModel) { + cell.cellIsHovered = true; + } + } else if (data.type === 'mouseLeaveMarkdownPreview') { + const cell = this.notebookEditor.getCellById(data.cellId); + if (cell instanceof MarkdownCellViewModel) { + cell.cellIsHovered = false; + } } else if (data.type === 'cell-drag-start') { this.notebookEditor.markdownCellDragStart(data.cellId, data.position); } else if (data.type === 'cell-drag') { @@ -804,6 +845,10 @@ var requirejs = (function() { } private async _onDidClickDataLink(event: IClickedDataUrlMessage): Promise { + if (typeof event.data !== 'string') { + return; + } + const [splitStart, splitData] = event.data.split(';base64,'); if (!splitData || !splitStart) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts index f10882298bb..4a0087234a8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts @@ -315,9 +315,8 @@ export class CellOutputContainer extends Disposable { render(editorHeight: number) { if (this.viewCell.outputsViewModels.length > 0) { - let layoutCache = false; if (this.viewCell.layoutInfo.totalHeight !== 0 && this.viewCell.layoutInfo.editorHeight > editorHeight) { - layoutCache = true; + this.viewCell.outputMinHeight = this.viewCell.layoutInfo.outputTotalHeight; this._relayoutCell(); } @@ -336,11 +335,8 @@ export class CellOutputContainer extends Disposable { this.viewCell.updateOutputShowMoreContainerHeight(46); } - if (layoutCache) { - this._relayoutCellDebounced(); - } else { - this._relayoutCell(); - } + this._relayoutCell(); + this._validateFinalOutputHeight(false); } else { // noop this.viewCell.editorHeight = editorHeight; @@ -418,6 +414,24 @@ export class CellOutputContainer extends Disposable { return outputs; } + private _outputHeightTimer: any = null; + + private _validateFinalOutputHeight(synchronous: boolean) { + if (this._outputHeightTimer !== null) { + clearTimeout(this._outputHeightTimer); + } + + if (synchronous) { + this.viewCell.outputMinHeight = 0; + this.viewCell.layoutChange({ outputHeight: true }); + } else { + this._outputHeightTimer = setTimeout(() => { + this.viewCell.outputMinHeight = 0; + this.viewCell.layoutChange({ outputHeight: true }); + }, 1000); + } + } + private _updateOutputs(splices: NotebookCellOutputsSplice[]) { if (!splices.length) { return; @@ -425,6 +439,9 @@ export class CellOutputContainer extends Disposable { const previousOutputHeight = this.viewCell.layoutInfo.outputTotalHeight; + // for cell output update, we make sure the cell does not shrink before the new outputs are rendered. + this.viewCell.outputMinHeight = previousOutputHeight; + if (this.viewCell.outputsViewModels.length) { this.templateData.outputContainer.style.display = 'block'; } else { @@ -480,12 +497,9 @@ export class CellOutputContainer extends Disposable { const editorHeight = this.templateData.editor.getContentHeight(); this.viewCell.editorHeight = editorHeight; - if (previousOutputHeight === 0 || this.viewCell.outputsViewModels.length === 0) { - // first execution or removing all outputs - this._relayoutCell(); - } else { - this._relayoutCellDebounced(); - } + this._relayoutCell(); + // if it's clearing all outputs, shrink immediately as the final output height will be zero. + this._validateFinalOutputHeight(this.viewCell.outputsViewModels.length === 0); } private _renderOutput(currOutput: ICellOutputViewModel, index: number, beforeElement?: HTMLElement) { @@ -534,27 +548,16 @@ export class CellOutputContainer extends Disposable { } private _relayoutCell() { - if (this._timer !== null) { - clearTimeout(this._timer); - } - this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); } - private _timer: number | null = null; + dispose() { + this.viewCell.outputMinHeight = 0; - private _relayoutCellDebounced() { - if (this._timer !== null) { - clearTimeout(this._timer); + if (this._outputHeightTimer) { + clearTimeout(this._outputHeightTimer); } - this._timer = setTimeout(() => { - this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); - this._timer = null; - }, 200) as unknown as number | null; - } - - dispose() { this.outputEntries.forEach((value) => { value.dispose(); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 6eff616e86e..672cf0180cd 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -199,8 +199,7 @@ abstract class AbstractCellRenderer { } return undefined; - }, - respectOrientationForPreviousAndNextKey: true + } }); const cellMenu = this.instantiationService.createInstance(CellMenus); @@ -234,8 +233,7 @@ abstract class AbstractCellRenderer { } return createActionViewItem(this.instantiationService, action); }, - renderDropdownAsChildElement: true, - respectOrientationForPreviousAndNextKey: true + renderDropdownAsChildElement: true }); if (elementClass) { @@ -268,8 +266,8 @@ abstract class AbstractCellRenderer { if (actions.primary.length || actions.secondary.length) { templateData.container.classList.add('cell-has-toolbar-actions'); if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicatorLeft.style.top = `${EDITOR_TOOLBAR_HEIGHT}px`; - templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT}px`; + templateData.focusIndicatorLeft.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`; + templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`; } } else { templateData.container.classList.remove('cell-has-toolbar-actions'); @@ -402,6 +400,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const deleteToolbar = disposables.add(this.createToolbar(titleToolbarContainer, 'cell-delete-toolbar')); deleteToolbar.setActions([this.instantiationService.createInstance(DeleteCellAction)]); + DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-top')); const focusIndicatorLeft = DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-left')); const focusIndicatorRight = DOM.append(container, DOM.$('.cell-focus-indicator.cell-focus-indicator-side.cell-focus-indicator-right')); @@ -535,6 +534,13 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR this.updateForLayout(element, templateData); })); + this.updateForHover(element, templateData); + elementDisposables.add(element.onDidChangeState(e => { + if (e.cellIsHoveredChanged) { + this.updateForHover(element, templateData); + } + })); + // render toolbar first this.setupCellToolbarActions(templateData, elementDisposables); @@ -559,6 +565,14 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR private updateForLayout(element: MarkdownCellViewModel, templateData: MarkdownCellRenderTemplate): void { // templateData.focusIndicatorLeft.style.height = `${element.layoutInfo.indicatorHeight}px`; templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP - CELL_BOTTOM_MARGIN}px`; + + const focusSideHeight = element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_GAP; + templateData.focusIndicatorLeft.style.height = `${focusSideHeight}px`; + templateData.focusIndicatorRight.style.height = `${focusSideHeight}px`; + } + + private updateForHover(element: MarkdownCellViewModel, templateData: MarkdownCellRenderTemplate): void { + templateData.container.classList.toggle('markdown-cell-hover', element.cellIsHovered); } disposeTemplate(templateData: MarkdownCellRenderTemplate): void { @@ -1110,8 +1124,7 @@ export class ListTopCellToolbar extends Disposable { } return undefined; - }, - respectOrientationForPreviousAndNextKey: true + } }); const cellMenu = this.instantiationService.createInstance(CellMenus); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 9874394d3e5..cba45be0a7a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -5,8 +5,8 @@ import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; -import { ICellDragEndMessage, ICellDragMessage, ICellDragStartMessage, ToWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { FromWebviewMessage, IBlurOutputMessage, ICellDragEndMessage, ICellDragMessage, ICellDragStartMessage, IClickedDataUrlMessage, ICustomRendererMessage, IDimensionMessage, IFocusMarkdownPreviewMessage, IMouseEnterMarkdownPreviewMessage, IMouseEnterMessage, IMouseLeaveMarkdownPreviewMessage, IMouseLeaveMessage, IToggleMarkdownPreviewMessage, IWheelMessage, ToWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; // !! IMPORTANT !! everything must be in-line within the webviewPreloads // function. Imports are not allowed. This is stringifies and injected into @@ -60,9 +60,7 @@ function webviewPreloads() { }; const handleDataUrl = async (data: string | ArrayBuffer | null, downloadName: string) => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'clicked-data-url', + postNotebookMessage('clicked-data-url', { data, downloadName }); @@ -146,9 +144,7 @@ function webviewPreloads() { if (entry.target.id === id && entry.contentRect) { if (entry.contentRect.height !== 0) { entry.target.style.padding = `${__outputNodePadding__}px ${__outputNodePadding__}px ${__outputNodePadding__}px ${__outputNodeLeftPadding__}px`; - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'dimension', + postNotebookMessage('dimension', { id: id, data: { height: entry.contentRect.height + __outputNodePadding__ * 2 @@ -157,9 +153,7 @@ function webviewPreloads() { }); } else { entry.target.style.padding = `0px`; - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'dimension', + postNotebookMessage('dimension', { id: id, data: { height: entry.contentRect.height @@ -201,10 +195,7 @@ function webviewPreloads() { if (event.defaultPrevented || scrollWillGoToParent(event)) { return; } - - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'did-scroll-wheel', + postNotebookMessage('did-scroll-wheel', { payload: { deltaMode: event.deltaMode, deltaX: event.deltaX, @@ -228,9 +219,7 @@ function webviewPreloads() { const element = document.createElement('div'); element.tabIndex = 0; element.addEventListener('focus', () => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'focus-editor', + postNotebookMessage('focus-editor', { id: outputId, focusNext }); @@ -246,19 +235,13 @@ function webviewPreloads() { function addMouseoverListeners(element: HTMLElement, outputId: string): void { element.addEventListener('mouseenter', () => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'mouseenter', + postNotebookMessage('mouseenter', { id: outputId, - data: {} }); }); element.addEventListener('mouseleave', () => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'mouseleave', + postNotebookMessage('mouseleave', { id: outputId, - data: {} }); }); } @@ -345,9 +328,7 @@ function webviewPreloads() { return { postMessage(message: unknown) { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'customRendererMessage', + postNotebookMessage('customRendererMessage', { rendererId: namespace, message, }); @@ -418,10 +399,7 @@ function webviewPreloads() { createMarkdownPreview(cell.cellId, cell.content, -10000); } - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'initializedMarkdownPreview', - }); + postNotebookMessage('initializedMarkdownPreview', {}); break; case 'createMarkdownPreview': createMarkdownPreview(event.data.id, event.data.content, event.data.top); @@ -524,9 +502,7 @@ function webviewPreloads() { resizeObserve(outputNode, outputId, true); - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'dimension', + postNotebookMessage('dimension', { id: outputId, init: true, data: { @@ -602,9 +578,7 @@ function webviewPreloads() { output.parentElement!.style.display = 'block'; output.style.top = top + 'px'; - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'dimension', + postNotebookMessage('dimension', { id: outputId, data: { height: output.clientHeight @@ -674,15 +648,12 @@ function webviewPreloads() { e.preventDefault(); const { cellId } = JSON.parse(data); - const msg: ICellDragEndMessage = { - __vscode_notebook_message: true, - type: 'cell-drag-end', + postNotebookMessage('cell-drag-end', { cellId: cellId, ctrlKey: e.ctrlKey, altKey: e.altKey, position: { clientX: e.clientX, clientY: e.clientY }, - }; - vscode.postMessage(msg); + }); }); function createMarkdownPreview(cellId: string, content: string, top: number) { @@ -700,12 +671,21 @@ function webviewPreloads() { previewContainerNode.style.top = top + 'px'; previewContainerNode.id = `${cellId}_preview`; previewContainerNode.classList.add('preview'); + previewContainerNode.addEventListener('dblclick', () => { - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'toggleMarkdownPreview', - cellId, - }); + postNotebookMessage('toggleMarkdownPreview', { cellId }); + }); + + previewContainerNode.addEventListener('click', () => { + postNotebookMessage('focusMarkdownPreview', { cellId }); + }); + + previewContainerNode.addEventListener('mouseenter', () => { + postNotebookMessage('mouseEnterMarkdownPreview', { cellId }); + }); + + previewContainerNode.addEventListener('mouseleave', () => { + postNotebookMessage('mouseLeaveMarkdownPreview', { cellId }); }); previewContainerNode.setAttribute('draggable', 'true'); @@ -718,23 +698,17 @@ function webviewPreloads() { (e.target as HTMLElement).classList.add('dragging'); - const msg: ICellDragStartMessage = { - __vscode_notebook_message: true, - type: 'cell-drag-start', + postNotebookMessage('cell-drag-start', { cellId: cellId, position: { clientX: e.clientX, clientY: e.clientY }, - }; - vscode.postMessage(msg); + }); }); previewContainerNode.addEventListener('drag', e => { - const msg: ICellDragMessage = { - __vscode_notebook_message: true, - type: 'cell-drag', + postNotebookMessage('cell-drag', { cellId: cellId, position: { clientX: e.clientX, clientY: e.clientY }, - }; - vscode.postMessage(msg); + }); }); previewContainerNode.addEventListener('dragend', e => { @@ -754,13 +728,11 @@ function webviewPreloads() { resizeObserve(previewContainerNode, `${cellId}_preview`, false); - vscode.postMessage({ - __vscode_notebook_message: true, - type: 'dimension', + postNotebookMessage('dimension', { id: `${cellId}_preview`, init: true, data: { - height: previewContainerNode.clientHeight + height: previewContainerNode.clientHeight, }, isOutput: false }); @@ -769,6 +741,17 @@ function webviewPreloads() { } } + function postNotebookMessage( + type: T['type'], + properties: Omit + ) { + vscode.postMessage({ + __vscode_notebook_message: true, + type, + ...properties + }); + } + function updateMarkdownPreview(cellId: string, content: string) { const previewNode = document.getElementById(`${cellId}_preview`); if (previewNode) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index c59b6d52458..fafb2684b18 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -50,6 +50,16 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this._onDidChangeState.fire({ outputIsHoveredChanged: true }); } + private _outputMinHeight: number = 0; + + get outputMinHeight() { + return this._outputMinHeight; + } + + set outputMinHeight(newMin: number) { + this._outputMinHeight = newMin; + } + private _layoutInfo: CodeCellLayoutInfo; get layoutInfo() { @@ -108,7 +118,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod // recompute this._ensureOutputsTop(); const outputShowMoreContainerHeight = state.outputShowMoreContainerHeight ? state.outputShowMoreContainerHeight : this._layoutInfo.outputShowMoreContainerHeight; - let outputTotalHeight = this.metadata?.outputCollapsed ? COLLAPSED_INDICATOR_HEIGHT : this._outputsTop!.getTotalValue(); + let outputTotalHeight = Math.max(this._outputMinHeight, this.metadata?.outputCollapsed ? COLLAPSED_INDICATOR_HEIGHT : this._outputsTop!.getTotalValue()); if (!this.metadata?.inputCollapsed) { let newState: CodeCellLayoutState; @@ -131,7 +141,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } const statusbarHeight = this.getEditorStatusbarHeight(); - const indicatorHeight = editorHeight + statusbarHeight + outputTotalHeight + outputShowMoreContainerHeight + CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN; + const indicatorHeight = editorHeight + statusbarHeight + outputTotalHeight + outputShowMoreContainerHeight; const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + statusbarHeight; const outputShowMoreContainerOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2 - outputShowMoreContainerHeight; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2; @@ -151,8 +161,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod layoutState: newState }; } else { - outputTotalHeight = this.metadata?.inputCollapsed && this.metadata.outputCollapsed ? 0 : outputTotalHeight; - const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight + outputShowMoreContainerHeight + CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN; + outputTotalHeight = Math.max(this._outputMinHeight, this.metadata?.inputCollapsed && this.metadata.outputCollapsed ? 0 : outputTotalHeight); + const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight + outputShowMoreContainerHeight; const outputContainerOffset = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT; const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_GAP + outputTotalHeight + outputShowMoreContainerHeight; const outputShowMoreContainerOffset = totalHeight - BOTTOM_CELL_TOOLBAR_GAP - BOTTOM_CELL_TOOLBAR_HEIGHT / 2 - outputShowMoreContainerHeight; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index eae9ae0070c..2f062ac3809 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -9,7 +9,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; import { EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { CellFindMatch, ICellOutputViewModel, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; @@ -46,7 +46,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie set editorHeight(newHeight: number) { this._editorHeight = newHeight; - this.totalHeight = this._editorHeight + CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_GAP + this.getEditorStatusbarHeight(); + this.totalHeight = this._editorHeight + MARKDOWN_CELL_TOP_MARGIN + MARKDOWN_CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_GAP + this.getEditorStatusbarHeight(); } get editorHeight() { @@ -69,6 +69,16 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie this._hoveringOutput = v; } + private _hoveringCell = false; + public get cellIsHovered(): boolean { + return this._hoveringCell; + } + + public set cellIsHovered(v: boolean) { + this._hoveringCell = v; + this._onDidChangeState.fire({ cellIsHoveredChanged: true }); + } + constructor( readonly viewType: string, readonly model: NotebookCellTextModel, @@ -129,7 +139,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie }; } else { const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; - const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + BOTTOM_CELL_TOOLBAR_GAP + CELL_BOTTOM_MARGIN; + const totalHeight = MARKDOWN_CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + BOTTOM_CELL_TOOLBAR_GAP + MARKDOWN_CELL_BOTTOM_MARGIN; state.totalHeight = totalHeight; this._layoutInfo = { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts index 0172d6a1f2d..aa05e729203 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -16,9 +16,11 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp get outputs() { return this._rawOutput.outputs || []; } - // get metadata(): NotebookCellOutputMetadata | undefined { - // return this._rawOutput.metadata; - // } + + get metadata(): Record | undefined { + return this._rawOutput.metadata; + } + get outputId(): string { return this._rawOutput.outputId; } @@ -47,10 +49,10 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp this._onDidChangeData.fire(); } - toJSON() { + toJSON(): IOutputDto { return { // data: this._data, - // metadata: this._rawOutput.metadata, + metadata: this._rawOutput.metadata, outputs: this._rawOutput.outputs, outputId: this._rawOutput.outputId }; diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 3885b5e3d02..ad138d44321 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -229,7 +229,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel constructor( readonly viewType: string, - readonly supportBackup: boolean, + readonly supportBackup: boolean, //TODO@jrieken,@rebornix all support backup, right? readonly uri: URI, cells: ICellDto2[], metadata: NotebookDocumentMetadata, @@ -661,6 +661,14 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const output = cell.outputs[outputIndex]; output.appendData(items); + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.OutputItem, + index: this._cells.indexOf(cell), + outputId: output.outputId, + outputItems: items, + append: true, + transient: this.transientOptions.transientOutputs + }, true); } private _replaceNotebookCellOutputItems(cellHandle: number, outputId: string, items: IOutputItemDto[]) { @@ -677,6 +685,14 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const output = cell.outputs[outputIndex]; output.replaceData(items); + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.OutputItem, + index: this._cells.indexOf(cell), + outputId: output.outputId, + outputItems: items, + append: false, + transient: this.transientOptions.transientOutputs + }, true); } private _moveCellToIdx(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index dee355a50bb..d9592bd84b8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -159,13 +159,8 @@ export interface IOutputItemDto { export interface IOutputDto { outputs: IOutputItemDto[]; - /** - * { mime_type: value } - */ - // data: { [key: string]: unknown; } - - // metadata?: NotebookCellOutputMetadata; outputId: string; + metadata?: Record; } export interface ICellOutput { @@ -237,9 +232,10 @@ export enum NotebookCellsChangeType { Initialize = 6, ChangeCellMetadata = 7, Output = 8, - ChangeCellContent = 9, - ChangeDocumentMetadata = 10, - Unknown = 11 + OutputItem = 9, + ChangeCellContent = 10, + ChangeDocumentMetadata = 11, + Unknown = 12 } export interface NotebookCellsInitializeEvent { @@ -270,6 +266,14 @@ export interface NotebookOutputChangedEvent { readonly outputs: IOutputDto[]; } +export interface NotebookOutputItemChangedEvent { + readonly kind: NotebookCellsChangeType.OutputItem; + readonly index: number; + readonly outputId: string; + readonly outputItems: IOutputItemDto[]; + readonly append: boolean; +} + export interface NotebookCellsChangeLanguageEvent { readonly kind: NotebookCellsChangeType.ChangeLanguage; readonly index: number; @@ -291,14 +295,14 @@ export interface NotebookDocumentUnknownChangeEvent { readonly kind: NotebookCellsChangeType.Unknown; } -export type NotebookRawContentEventDto = NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | NotebookDocumentUnknownChangeEvent; +export type NotebookRawContentEventDto = NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookOutputItemChangedEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | NotebookDocumentUnknownChangeEvent; export type NotebookCellsChangedEventDto = { readonly rawEvents: NotebookRawContentEventDto[]; readonly versionId: number; }; -export type NotebookRawContentEvent = (NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | NotebookDocumentUnknownChangeEvent) & { transient: boolean; }; +export type NotebookRawContentEvent = (NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookOutputItemChangedEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | NotebookDocumentUnknownChangeEvent) & { transient: boolean; }; export type NotebookTextModelChangedEvent = { readonly rawEvents: NotebookRawContentEvent[]; readonly versionId: number; @@ -592,22 +596,35 @@ export const NOTEBOOK_EDITOR_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' export const NOTEBOOK_EDITOR_CURSOR_BEGIN_END = new RawContextKey('notebookEditorCursorAtEditorBeginEnd', false); +export interface INotebookLoadOptions { + /** + * Go to disk bypassing any cache of the model if any. + */ + forceReadFromDisk?: boolean; +} + +export interface IResolvedNotebookEditorModel extends INotebookEditorModel { + notebook: NotebookTextModel; +} + export interface INotebookEditorModel extends IEditorModel { readonly onDidChangeDirty: Event; readonly resource: URI; readonly viewType: string; - readonly notebook: NotebookTextModel; + readonly notebook: NotebookTextModel | undefined; readonly lastResolvedFileStat: IFileStatWithMetadata | undefined; + isResolved(): this is IResolvedNotebookEditorModel; isDirty(): boolean; isUntitled(): boolean; + load(options?: INotebookLoadOptions): Promise; save(): Promise; saveAs(target: URI): Promise; revert(options?: IRevertOptions | undefined): Promise; } export interface INotebookDiffEditorModel extends IEditorModel { - original: INotebookEditorModel; - modified: INotebookEditorModel; + original: IResolvedNotebookEditorModel; + modified: IResolvedNotebookEditorModel; resolveOriginalFromDisk(): Promise; resolveModifiedFromDisk(): Promise; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 583dad5a8cf..98fafe2da1a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; @@ -14,20 +14,12 @@ import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapab import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Schemas } from 'vs/base/common/network'; -import { IFileStatWithMetadata, IFileService } from 'vs/platform/files/common/files'; +import { IFileStatWithMetadata, IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; import { TaskSequentializer } from 'vs/base/common/async'; - - -export interface INotebookLoadOptions { - /** - * Go to disk bypassing any cache of the model if any. - */ - forceReadFromDisk?: boolean; -} - +import { assertType } from 'vs/base/common/types'; export class NotebookEditorModel extends EditorModel implements INotebookEditorModel { @@ -37,14 +29,13 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM readonly onDidChangeDirty = this._onDidChangeDirty.event; readonly onDidChangeContent = this._onDidChangeContent.event; - private _notebook!: NotebookTextModel; private _lastResolvedFileStat?: IFileStatWithMetadata; private readonly _name: string; private readonly _workingCopyResource: URI; - private readonly saveSequentializer = new TaskSequentializer(); + private readonly _saveSequentializer = new TaskSequentializer(); - private _dirty = false; + private _dirty: boolean = false; constructor( readonly resource: URI, @@ -76,14 +67,41 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM }; this._register(this._workingCopyService.registerWorkingCopy(workingCopyAdapter)); + this._register(this._fileService.onDidFilesChange(async e => { + if (this.isDirty() || !this.isResolved()) { + // skip when dirty or unresolved... + return; + } + if (!e.affects(this.resource, FileChangeType.UPDATED)) { + // no my file + return; + } + const stats = await this._resolveStats(this.resource); + if (stats && this._lastResolvedFileStat && stats.etag !== this._lastResolvedFileStat.etag) { + this.load({ forceReadFromDisk: true }); + } + })); } - get lastResolvedFileStat() { + isResolved(): this is IResolvedNotebookEditorModel { + return this.notebook !== undefined; + } + + isDirty(): boolean { + return this._dirty; + } + + isUntitled(): boolean { + return this.resource.scheme === Schemas.untitled; + } + + get lastResolvedFileStat(): IFileStatWithMetadata | undefined { return this._lastResolvedFileStat; } - get notebook() { - return this._notebook; + get notebook(): NotebookTextModel | undefined { + const candidate = this._notebookService.getNotebookTextModel(this.resource); + return candidate && candidate.viewType === this.viewType ? candidate : undefined; } setDirty(newState: boolean) { @@ -94,7 +112,12 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } async backup(token: CancellationToken): Promise> { - if (this._notebook.supportBackup) { + + if (!this.isResolved()) { + throw new Error('CANNOT call backup before notebook is resolved'); + } + + if (this.notebook.supportBackup) { const tokenSource = new CancellationTokenSource(token); const backupId = await this._notebookService.backup(this.viewType, this.resource, tokenSource.token); if (token.isCancellationRequested) { @@ -106,7 +129,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM meta: { mtime: stats?.mtime || new Date().getTime(), name: this._name, - viewType: this._notebook.viewType, + viewType: this.notebook.viewType, backupId: backupId } }; @@ -115,16 +138,15 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM meta: { mtime: new Date().getTime(), name: this._name, - viewType: this._notebook.viewType + viewType: this.notebook.viewType }, - content: this._notebook.createSnapshot(true) + content: this.notebook.createSnapshot(true) }; } } async revert(options?: IRevertOptions | undefined): Promise { if (options?.soft) { - await this._backupFileService.discardBackup(this.resource); this.setDirty(false); return; } @@ -137,9 +159,11 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM this._onDidChangeDirty.fire(); } - async load(options?: INotebookLoadOptions): Promise { + async load(options?: INotebookLoadOptions): Promise { if (options?.forceReadFromDisk) { - return this._loadFromProvider(true, undefined); + this._loadFromProvider(undefined); + assertType(this.isResolved()); + return this; } if (this.isResolved()) { @@ -152,63 +176,82 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM return this; // Make sure meanwhile someone else did not succeed in loading } - return this._loadFromProvider(false, backup?.meta?.backupId); - } - - private async _loadFromProvider(forceReloadFromDisk: boolean, backupId: string | undefined) { - this._notebook = await this._notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, backupId); - - const newStats = await this._resolveStats(this.resource); - this._lastResolvedFileStat = newStats; - - this._register(this._notebook); - - this._register(this._notebook.onDidChangeContent(e => { - let triggerDirty = false; - for (let i = 0; i < e.rawEvents.length; i++) { - if (e.rawEvents[i].kind !== NotebookCellsChangeType.Initialize) { - this._onDidChangeContent.fire(); - triggerDirty = triggerDirty || !e.rawEvents[i].transient; - } - } - - if (triggerDirty) { - this.setDirty(true); - } - })); - - if (forceReloadFromDisk) { - this.setDirty(false); - } - - if (backupId) { - await this._backupFileService.discardBackup(this._workingCopyResource); - this.setDirty(true); - } - + await this._loadFromProvider(backup?.meta?.backupId); + assertType(this.isResolved()); return this; } - isResolved(): boolean { - return !!this._notebook; + private async _loadFromProvider(backupId: string | undefined): Promise { + + const data = await this._notebookService.fetchNotebookRawData(this.viewType, this.resource, backupId); + this._lastResolvedFileStat = await this._resolveStats(this.resource); + + if (this.isDisposed()) { + return; + } + + if (!this.notebook) { + // FRESH there is no notebook yet and we are now creating it + + // UGLY + // There might be another notebook for the URI which was created from a different + // source (different viewType). In that case we simply dispose the + // existing/conflicting model and proceed with a new notebook + const conflictingNotebook = this._notebookService.getNotebookTextModel(this.resource); + if (conflictingNotebook) { + this._logService.warn('DISPOSING conflicting notebook with same URI but different view type', this.resource.toString(), this.viewType); + conflictingNotebook.dispose(); + } + + // todo@jrieken@rebornix what about reload? + if (this.resource.scheme === Schemas.untitled && data.data.cells.length === 0) { + data.data.cells.push({ + cellKind: CellKind.Code, + language: 'plaintext', //TODO@jrieken unsure what this is + outputs: [], + metadata: undefined, + source: '' + }); + } + + // this creates and caches a new notebook model so that notebookService.getNotebookTextModel(...) + // will return this one model + const notebook = this._notebookService.createNotebookTextModel(this.viewType, this.resource, data.data, data.transientOptions); + this._register(notebook); + this._register(notebook.onDidChangeContent(e => { + let triggerDirty = false; + for (let i = 0; i < e.rawEvents.length; i++) { + if (e.rawEvents[i].kind !== NotebookCellsChangeType.Initialize) { + this._onDidChangeContent.fire(); + triggerDirty = triggerDirty || !e.rawEvents[i].transient; + } + } + if (triggerDirty) { + this.setDirty(true); + } + })); + + } else { + // UPDATE exitsing notebook with data that we have just fetched + this.notebook.metadata = data.data.metadata; + this.notebook.transientOptions = data.transientOptions; + const edits: ICellEditOperation[] = [{ editType: CellEditType.Replace, index: 0, count: this.notebook.cells.length, cells: data.data.cells }]; + this.notebook.applyEdits(this.notebook.versionId, edits, true, undefined, () => undefined, undefined); + } + + if (backupId) { + this._backupFileService.discardBackup(this._workingCopyResource); + this.setDirty(true); + } else { + this.setDirty(false); + } } - isDirty() { - return this._dirty; - } - - isUntitled() { - return this.resource.scheme === Schemas.untitled; - } - - private async _assertStat() { + private async _assertStat(): Promise<'overwrite' | 'revert' | 'none'> { this._logService.debug('[notebook editor model] start assert stat'); const stats = await this._resolveStats(this.resource); if (this._lastResolvedFileStat && stats && stats.mtime > this._lastResolvedFileStat.mtime) { - this._logService.debug(`[notebook editor model] noteboook file on disk is newer: -LastResolvedStat: ${this._lastResolvedFileStat ? JSON.stringify(this._lastResolvedFileStat) : undefined}. -Current stat: ${JSON.stringify(stats)} -`); + this._logService.debug(`[notebook editor model] noteboook file on disk is newer:\nLastResolvedStat: ${this._lastResolvedFileStat ? JSON.stringify(this._lastResolvedFileStat) : undefined}.\nCurrent stat: ${JSON.stringify(stats)}`); this._lastResolvedFileStat = stats; return new Promise<'overwrite' | 'revert' | 'none'>(resolve => { const handle = this._notificationService.prompt( @@ -241,40 +284,44 @@ Current stat: ${JSON.stringify(stats)} } async save(): Promise { - let versionId = this._notebook.versionId; + + if (!this.isResolved()) { + return false; + } + + const versionId = this.notebook.versionId; this._logService.debug(`[notebook editor model] save(${versionId}) - enter with versionId ${versionId}`, this.resource.toString(true)); - if (this.saveSequentializer.hasPending(versionId)) { + if (this._saveSequentializer.hasPending(versionId)) { this._logService.debug(`[notebook editor model] save(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString(true)); - return this.saveSequentializer.pending.then(() => { + return this._saveSequentializer.pending.then(() => { return true; }); } - if (this.saveSequentializer.hasPending()) { - return this.saveSequentializer.setNext(async () => { + if (this._saveSequentializer.hasPending()) { + return this._saveSequentializer.setNext(async () => { await this.save(); }).then(() => { return true; }); } - return this.saveSequentializer.setPending(versionId, (async () => { + return this._saveSequentializer.setPending(versionId, (async () => { const result = await this._assertStat(); if (result === 'none') { return; } - if (result === 'revert') { await this.revert(); return; } - - const tokenSource = new CancellationTokenSource(); - await this._notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + if (!this.isResolved()) { + return; + } + await this._notebookService.save(this.notebook.viewType, this.notebook.uri, CancellationToken.None); this._logService.debug(`[notebook editor model] save(${versionId}) - document saved saved, start updating file stats`, this.resource.toString(true)); - const newStats = await this._resolveStats(this.resource); - this._lastResolvedFileStat = newStats; + this._lastResolvedFileStat = await this._resolveStats(this.resource); this.setDirty(false); })()).then(() => { return true; @@ -282,6 +329,11 @@ Current stat: ${JSON.stringify(stats)} } async saveAs(targetResource: URI): Promise { + + if (!this.isResolved()) { + return false; + } + this._logService.debug(`[notebook editor model] saveAs - enter`, this.resource.toString(true)); const result = await this._assertStat(); @@ -294,11 +346,9 @@ Current stat: ${JSON.stringify(stats)} return true; } - const tokenSource = new CancellationTokenSource(); - await this._notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token); + await this._notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, CancellationToken.None); this._logService.debug(`[notebook editor model] saveAs - document saved, start updating file stats`, this.resource.toString(true)); - const newStats = await this._resolveStats(this.resource); - this._lastResolvedFileStat = newStats; + this._lastResolvedFileStat = await this._resolveStats(this.resource); this.setDirty(false); return true; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index ace31e6a1b4..85a19e97356 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -5,7 +5,7 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { CellUri, INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { combinedDisposable, DisposableStore, IDisposable, IReference, ReferenceCollection } from 'vs/base/common/lifecycle'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -16,11 +16,11 @@ export const INotebookEditorModelResolverService = createDecorator>; + resolve(resource: URI, viewType?: string): Promise>; } -export class NotebookModelReferenceCollection extends ReferenceCollection> { +export class NotebookModelReferenceCollection extends ReferenceCollection> { constructor( @IInstantiationService readonly _instantiationService: IInstantiationService, @@ -30,14 +30,14 @@ export class NotebookModelReferenceCollection extends ReferenceCollection { + protected createReferencedObject(key: string, viewType: string): Promise { const uri = URI.parse(key); const model = this._instantiationService.createInstance(NotebookEditorModel, uri, viewType); const promise = model.load(); return promise; } - protected destroyReferencedObject(_key: string, object: Promise): void { + protected destroyReferencedObject(_key: string, object: Promise): void { object.then(model => { this._notebookService.destoryNotebookDocument(model.viewType, model.notebook); model.dispose(); @@ -60,7 +60,7 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve this._data = instantiationService.createInstance(NotebookModelReferenceCollection); } - async resolve(resource: URI, viewType?: string): Promise> { + async resolve(resource: URI, viewType?: string): Promise> { if (resource.scheme === CellUri.scheme) { throw new Error(`CANNOT open a cell-uri as notebook. Tried with ${resource.toString()}`); } @@ -96,7 +96,7 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve }; } - private static _autoReferenceDirtyModel(model: INotebookEditorModel, ref: () => IDisposable): IDisposable { + private static _autoReferenceDirtyModel(model: IResolvedNotebookEditorModel, ref: () => IDisposable): IDisposable { const references = new DisposableStore(); const listener = model.onDidChangeDirty(() => { diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index b0faba9fcd8..f25bea8de6f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -23,8 +23,7 @@ export interface IMainNotebookController { supportBackup: boolean; viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; }; options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; - resolveNotebookDocument(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>; - reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise; + openNotebook(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; save(uri: URI, token: CancellationToken): Promise; @@ -32,6 +31,12 @@ export interface IMainNotebookController { backup(uri: URI, token: CancellationToken): Promise; } + +export interface INotebookRawData { + data: NotebookDataDto; + transientOptions: TransientOptions; +} + export interface INotebookService { readonly _serviceBrand: undefined; canResolve(viewType: string): Promise; @@ -55,7 +60,7 @@ export interface INotebookService { getRendererInfo(id: string): INotebookRendererInfo | undefined; getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[]; - resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise; + createNotebookTextModel(viewType: string, uri: URI, data: NotebookDataDto, transientOptions: TransientOptions): NotebookTextModel; getNotebookTextModel(uri: URI): NotebookTextModel | undefined; getNotebookTextModels(): Iterable; getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[]; @@ -64,9 +69,12 @@ export interface INotebookService { destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; updateActiveNotebookEditor(editor: IEditor | null): void; updateVisibleNotebookEditor(editors: string[]): void; + + fetchNotebookRawData(viewType: string, uri: URI, backupId?: string): Promise; save(viewType: string, resource: URI, token: CancellationToken): Promise; saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; backup(viewType: string, uri: URI, token: CancellationToken): Promise; + onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: unknown): void; setToCopy(items: NotebookCellTextModel[], isCopy: boolean): void; getToCopy(): { items: NotebookCellTextModel[], isCopy: boolean; } | undefined; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index ec6bc22d6cd..5614be8731e 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -18,7 +18,7 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellUri, INotebookEditorModel, NotebookCellMetadata, ICellRange, INotebookKernel, notebookDocumentMetadataDefaults, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, INotebookEditorModel, NotebookCellMetadata, ICellRange, INotebookKernel, notebookDocumentMetadataDefaults, IOutputDto, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; import { NotImplementedError } from 'vs/base/common/errors'; @@ -83,6 +83,9 @@ export class TestNotebookEditor implements INotebookEditor { getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel { throw new Error('Method not implemented.'); } + getCellById(cellId: string): ICellViewModel { + throw new Error('Method not implemented.'); + } updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, height: number, isInit: boolean): void { throw new Error('Method not implemented.'); } @@ -413,6 +416,10 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi return this._notebook; } + async load(): Promise { + return this; + } + async save(): Promise { if (this._notebook) { this._dirty = false; diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index ddd71be77d1..d7c7c6ddb69 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -36,6 +36,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { editorBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Dimension } from 'vs/base/browser/dom'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export class OutputViewPane extends ViewPane { diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 51d3d495873..4b810831bab 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -156,7 +156,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { if (metrics.meminfo) { md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / ByteSize.KB).toFixed(2)} MB working set(${(metrics.meminfo.privateBytes / ByteSize.KB).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / ByteSize.KB).toFixed(2)}MB shared)`); } - md.li(`VM(likelyhood): ${metrics.isVMLikelyhood}%`); + md.li(`VM(likelihood): ${metrics.isVMLikelyhood}%`); md.li(`Initial Startup: ${metrics.initialStartup}`); md.li(`Has ${metrics.windowCount - 1} other windows`); md.li(`Screen Reader Active: ${metrics.hasAccessibilitySupport}`); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 5f4734250dd..8d7405e04fe 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -61,7 +61,7 @@ export const enum SettingsFocusContext { SettingControl } -function createGroupIterator(group: SettingsTreeGroupElement): Iterable> { +export function createGroupIterator(group: SettingsTreeGroupElement): Iterable> { return Iterable.map(group.children, g => { return { element: g, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 10b63e2e4ec..298ac757dd9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -651,7 +651,6 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const toolbar = new ToolBar(container, this._contextMenuService, { toggleMenuTitle, renderDropdownAsChildElement: true, - respectOrientationForPreviousAndNextKey: true, moreIcon: settingsMoreActionIcon // change icon from ellipsis to gear }); return toolbar; @@ -1829,7 +1828,7 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate extends ObjectTreeModel { +export class NonCollapsibleObjectTreeModel extends ObjectTreeModel { isCollapsible(element: T): boolean { return false; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index aea2722c343..9994ccc1b5e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -374,7 +374,7 @@ abstract class AbstractListSettingWidget extends Dispo rowElement.setAttribute('tabindex', item.selected ? '0' : '-1'); rowElement.classList.toggle('selected', item.selected); - const actionBar = new ActionBar(rowElement, { respectOrientationForPreviousAndNextKey: true }); + const actionBar = new ActionBar(rowElement); this.listDisposables.add(actionBar); actionBar.push(this.getActionsForItem(item, idx), { icon: true, label: true }); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index bb18656a314..bbb6695f3c2 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -36,7 +36,7 @@ import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems'; -import { Action, IActionViewItem } from 'vs/base/common/actions'; +import { Action } from 'vs/base/common/actions'; import { isStringArray } from 'vs/base/common/types'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -53,6 +53,7 @@ import * as icons from 'vs/workbench/contrib/remote/browser/remoteIcons'; import { ILogService } from 'vs/platform/log/common/log'; import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; export interface HelpInformation { diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 78c3e421517..65019d73cba 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -6,8 +6,8 @@ import * as nls from 'vs/nls'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PortsAttributes, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { PORT_AUTO_FORWARD_SETTING, forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction } from 'vs/workbench/contrib/remote/browser/tunnelView'; +import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PortsAttributes, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -218,7 +218,7 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService, remoteExplorerService, configurationService, debugService, tunnelService, remoteAgentService, hostService, false)); } else if (environment?.os === OperatingSystem.Linux) { - const useProc = (this.configurationService.getValue('remote.autoForwardPortsSource') === 'process'); + const useProc = (this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS); if (useProc) { this._register(new ProcAutomaticPortForwarding(configurationService, remoteExplorerService, notificationService, openerService, externalOpenerService, tunnelService, hostService)); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 25e862a2a30..73778e899b9 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -47,7 +47,6 @@ import { ITableColumn, ITableContextMenuEvent, ITableMouseEvent, ITableRenderer, import { WorkbenchTable } from 'vs/platform/list/browser/listService'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); -export const PORT_AUTO_FORWARD_SETTING = 'remote.autoForwardPorts'; class TunnelTreeVirtualDelegate implements ITableVirtualDelegate { @@ -336,8 +335,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer .name > .monaco-icon-label > .actions { display: none; max-width: fit-content; + max-width: -moz-fit-content; } .scm-view .monaco-list .monaco-list-row:hover .resource-group > .actions, diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 803d80ee982..739853ebdb7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -10,7 +10,7 @@ import { ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IAction, IActionViewItemProvider } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { connectPrimaryMenu, isSCMRepository, StatusBarAction } from './util'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -21,6 +21,7 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IListRenderer } from 'vs/base/browser/ui/list/list'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { basename } from 'vs/base/common/resources'; +import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; interface RepositoryTemplate { readonly label: HTMLElement; @@ -58,7 +59,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer(QuickAccessExtensions.Quickaccess)); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index cdf55b05fb5..91f29c24520 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -7,13 +7,14 @@ import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; import type { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; -import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError, ITerminalNativeWindowsDelegate, LinuxDistro, IRemoteTerminalAttachTarget, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, ITerminalTabLayoutInfoById } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IWindowsShellHelper, ITerminalConfigHelper, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalNativeWindowsDelegate, LinuxDistro, IRemoteTerminalAttachTarget, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, ITerminalTabLayoutInfoById } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { URI } from 'vs/base/common/uri'; +import { IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalInstanceService = createDecorator('terminalInstanceService'); @@ -27,6 +28,14 @@ export const IRemoteTerminalService = createDecorator('r export interface ITerminalInstanceService { readonly _serviceBrand: undefined; + /** Fired when the ptyHost process goes down, losing all connections to the service's ptys. */ + onPtyHostExit: Event; + /** + * Fired when the ptyHost process becomes non-responsive, this should disable stdin for all + * terminals using this pty host connection and mark them as disconnected. + */ + onPtyHostUnresponsive: Event; + // These events are optional as the requests they make are only needed on the browser side onRequestDefaultShellAndArgs?: Event; @@ -35,7 +44,7 @@ export interface ITerminalInstanceService { getXtermUnicode11Constructor(): Promise; getXtermWebglConstructor(): Promise; createWindowsShellHelper(shellProcessId: number, xterm: XTermTerminal): IWindowsShellHelper; - createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; + createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): Promise; getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; getMainProcessParentEnv(): Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 105d4cbfc0b..8ae511a0893 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -8,7 +8,7 @@ import * as platform from 'vs/base/common/platform'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, IShellLaunchConfig, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_FONT_WEIGHT, DEFAULT_BOLD_FONT_WEIGHT, FontWeight } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_FONT_WEIGHT, DEFAULT_BOLD_FONT_WEIGHT, FontWeight } from 'vs/workbench/contrib/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -20,6 +20,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IProductService } from 'vs/platform/product/common/productService'; import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; +import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; const MINIMUM_FONT_SIZE = 6; const MAXIMUM_FONT_SIZE = 25; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index cdca5cdb93e..cbbab36147f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,7 +25,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { IShellLaunchConfig, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride, TERMINAL_CREATION_COMMANDS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; @@ -47,6 +47,7 @@ import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -949,6 +950,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e, true)); this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e)); this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e)); + this._processManager.onPtyDisconnect(() => { + this._safeSetOption('disableStdin', true); + // Use api source so it cannot be overridden + this.setTitle(nls.localize('ptyDisconnected', "{0} (disconnected)", this._title), TitleEventSource.Api); + }); if (this._shellLaunchConfig.name) { this.setTitle(this._shellLaunchConfig.name, TitleEventSource.Api); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index daefe15f7ef..23020b11468 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -4,25 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IWindowsShellHelper, ITerminalChildProcess, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IWindowsShellHelper, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; import type { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { IProcessEnvironment } from 'vs/base/common/platform'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ITerminalChildProcess } from 'vs/platform/terminal/common/terminal'; +import { Disposable } from 'vs/base/common/lifecycle'; let Terminal: typeof XTermTerminal; let SearchAddon: typeof XTermSearchAddon; let Unicode11Addon: typeof XTermUnicode11Addon; let WebglAddon: typeof XTermWebglAddon; -export class TerminalInstanceService implements ITerminalInstanceService { +export class TerminalInstanceService extends Disposable implements ITerminalInstanceService { public _serviceBrand: undefined; - private readonly _onRequestDefaultShellAndArgs = new Emitter(); - public get onRequestDefaultShellAndArgs(): Event { return this._onRequestDefaultShellAndArgs.event; } + private readonly _onPtyHostExit = this._register(new Emitter()); + readonly onPtyHostExit = this._onPtyHostExit.event; + private readonly _onPtyHostUnresponsive = this._register(new Emitter()); + readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event; + private readonly _onRequestDefaultShellAndArgs = this._register(new Emitter()); + readonly onRequestDefaultShellAndArgs = this._onRequestDefaultShellAndArgs.event; public async getXtermConstructor(): Promise { if (!Terminal) { @@ -56,7 +62,7 @@ export class TerminalInstanceService implements ITerminalInstanceService { throw new Error('Not implemented'); } - public createTerminalProcess(): ITerminalChildProcess { + public createTerminalProcess(): Promise { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index 0be5cf88be5..ce47964cff8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessExtHostProxy, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import * as nls from 'vs/nls'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IShellLaunchConfig, ITerminalLaunchError, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalDimensions } from 'vs/platform/terminal/common/terminal'; let hasReceivedResponseFromRemoteExtHost: boolean = false; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index d3ec81f5b1a..beeb6039876 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -6,7 +6,7 @@ import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { env as processEnv } from 'vs/base/common/process'; -import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride, FlowControlConstants } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ProcessState, ITerminalProcessManager, ITerminalConfigHelper, IBeforeProcessDataEvent } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -27,6 +27,7 @@ import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection, IEnv import { EnvironmentVariableInfoChangesActive, EnvironmentVariableInfoStale } from 'vs/workbench/contrib/terminal/browser/environmentVariableInfo'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { URI } from 'vs/base/common/uri'; +import { IShellLaunchConfig, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, ITerminalChildProcess, IProcessDataEvent, ITerminalDimensionsOverride } from 'vs/platform/terminal/common/terminal'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -68,6 +69,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce private _ackDataBufferer: AckDataBufferer; private _hasWrittenData: boolean = false; + private readonly _onPtyDisconnect = this._register(new Emitter()); + public get onPtyDisconnect(): Event { return this._onPtyDisconnect.event; } private readonly _onProcessReady = this._register(new Emitter()); public get onProcessReady(): Event { return this._onProcessReady.event; } private readonly _onBeforeProcessData = this._register(new Emitter()); @@ -309,7 +312,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const env = await this._setupEnvVariableInfo(activeWorkspaceRootUri, shellLaunchConfig); const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; - return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); + this._terminalInstanceService.onPtyHostUnresponsive(() => this._onPtyDisconnect.fire()); + return await this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); } public setDimensions(cols: number, rows: number): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 8671a19468b..3c83a0f4962 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -18,13 +18,14 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IShellLaunchConfig, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { TerminalConnectionState, IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; -import { IAvailableShellsRequest, IRemoteTerminalAttachTarget, IShellDefinition, IShellLaunchConfig, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalLaunchError, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, ITerminalsLayoutInfoById, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, LinuxDistro, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IAvailableShellsRequest, IRemoteTerminalAttachTarget, IShellDefinition, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, ITerminalsLayoutInfoById, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, LinuxDistro, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index 740a03e3662..d9135de0a43 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IShellLaunchConfig, ITerminalTabLayoutInfoById, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalTabLayoutInfoById, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -11,6 +11,7 @@ import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalInstance, Direction, ITerminalTab, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; const SPLIT_PANE_MIN_SIZE = 120; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 7674a991804..c24ce885c6b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; -import { Action, IAction, IActionViewItem } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,6 +35,8 @@ import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/c import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { equals } from 'vs/base/common/arrays'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -244,7 +246,11 @@ export class TerminalViewPane extends ViewPane { await terminal.copySelection(); terminal.clearSelection(); } else { - terminal.paste(); + if (BrowserFeatures.clipboard.readText) { + terminal.paste(); + } else { + this._notificationService.info(`This browser doesn't support the clipboard.readText API needed to trigger a paste, try ${platform.isMacintosh ? '⌘' : 'Ctrl'}+V instead.`); + } } // Clear selection after all click event bubbling is finished on Mac to prevent // right-click selecting a word which is seemed cannot be disabled. There is a diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index 7bb09335710..9358cbf6d6f 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -13,12 +13,13 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; -import { IRawTerminalTabLayoutInfo, ITerminalConfiguration, ITerminalEnvironment, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, ITerminalTabLayoutInfoById, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IRawTerminalTabLayoutInfo, ITerminalConfiguration, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, ITerminalTabLayoutInfoById, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Schemas } from 'vs/base/common/network'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; export const REMOTE_TERMINAL_CHANNEL_NAME = 'remoteterminal'; diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 1c9f51ea37c..7ad8acd2246 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -11,44 +11,45 @@ import { URI } from 'vs/base/common/uri'; import { OperatingSystem } from 'vs/base/common/platform'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; export const TERMINAL_VIEW_ID = 'terminal'; /** A context key that is set when there is at least one opened integrated terminal. */ -export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false); +export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false, true); /** A context key that is set when the integrated terminal has focus. */ -export const KEYBINDING_CONTEXT_TERMINAL_FOCUS = new RawContextKey('terminalFocus', false); +export const KEYBINDING_CONTEXT_TERMINAL_FOCUS = new RawContextKey('terminalFocus', false, nls.localize('terminalFocusContextKey', "Whether the terminal is focused")); export const KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY = 'terminalShellType'; /** A context key that is set to the detected shell for the most recently active terminal, this is set to the last known value when no terminals exist. */ -export const KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE = new RawContextKey(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, undefined); +export const KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE = new RawContextKey(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, undefined, { type: 'string', description: nls.localize('terminalShellTypeContextKey', "The shell type of the active terminal") }); -export const KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE = new RawContextKey('terminalAltBufferActive', false); +export const KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE = new RawContextKey('terminalAltBufferActive', false, true); /** A context key that is set when the integrated terminal does not have focus. */ export const KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED = KEYBINDING_CONTEXT_TERMINAL_FOCUS.toNegated(); /** A context key that is set when the user is navigating the accessibility tree */ -export const KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS = new RawContextKey('terminalA11yTreeFocus', false); +export const KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS = new RawContextKey('terminalA11yTreeFocus', false, true); /** A keybinding context key that is set when the integrated terminal has text selected. */ -export const KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED = new RawContextKey('terminalTextSelected', false); +export const KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED = new RawContextKey('terminalTextSelected', false, nls.localize('terminalTextSelectedContextKey', "Whether text is selected in the active terminal")); /** A keybinding context key that is set when the integrated terminal does not have text selected. */ export const KEYBINDING_CONTEXT_TERMINAL_TEXT_NOT_SELECTED = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.toNegated(); /** A context key that is set when the find widget in integrated terminal is visible. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE = new RawContextKey('terminalFindVisible', false); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE = new RawContextKey('terminalFindVisible', false, true); /** A context key that is set when the find widget in integrated terminal is not visible. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE = KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE.toNegated(); /** A context key that is set when the find widget find input in integrated terminal is focused. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_INPUT_FOCUSED = new RawContextKey('terminalFindInputFocused', false); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_INPUT_FOCUSED = new RawContextKey('terminalFindInputFocused', false, true); /** A context key that is set when the find widget in integrated terminal is focused. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED = new RawContextKey('terminalFindFocused', false); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED = new RawContextKey('terminalFindFocused', false, true); /** A context key that is set when the find widget find input in integrated terminal is not focused. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_INPUT_NOT_FOCUSED = KEYBINDING_CONTEXT_TERMINAL_FIND_INPUT_FOCUSED.toNegated(); -export const KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED = new RawContextKey('terminalProcessSupported', false); +export const KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED = new RawContextKey('terminalProcessSupported', false, nls.localize('terminalProcessSupportedContextKey', "Whether terminal processes can be launched")); export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed'; export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime'; @@ -168,10 +169,6 @@ export interface ITerminalFont { charHeight?: number; } -export interface ITerminalEnvironment { - [key: string]: string | null; -} - export interface IRemoteTerminalAttachTarget { id: number; pid: number; @@ -206,98 +203,6 @@ export interface IRawTerminalsLayoutInfo { export type ITerminalsLayoutInfo = IRawTerminalsLayoutInfo; export type ITerminalsLayoutInfoById = IRawTerminalsLayoutInfo; -export interface IShellLaunchConfig { - /** - * The name of the terminal, if this is not set the name of the process will be used. - */ - name?: string; - - /** - * The shell executable (bash, cmd, etc.). - */ - executable?: string; - - /** - * The CLI arguments to use with executable, a string[] is in argv format and will be escaped, - * a string is in "CommandLine" pre-escaped format and will be used as is. The string option is - * only supported on Windows and will throw an exception if used on macOS or Linux. - */ - args?: string[] | string; - - /** - * The current working directory of the terminal, this overrides the `terminal.integrated.cwd` - * settings key. - */ - cwd?: string | URI; - - /** - * A custom environment for the terminal, if this is not set the environment will be inherited - * from the VS Code process. - */ - env?: ITerminalEnvironment; - - /** - * Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (e.g. if the - * shell is being launched by an extension). - */ - ignoreConfigurationCwd?: boolean; - - /** Whether to wait for a key press before closing the terminal. */ - waitOnExit?: boolean | string; - - /** - * A string including ANSI escape sequences that will be written to the terminal emulator - * _before_ the terminal process has launched, a trailing \n is added at the end of the string. - * This allows for example the terminal instance to display a styled message as the first line - * of the terminal. Use \x1b over \033 or \e for the escape control character. - */ - initialText?: string; - - /** - * Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`. - */ - isExtensionTerminal?: boolean; - - /** - * A UUID generated by the extension host process for terminals created on the extension host process. - */ - extHostTerminalId?: string; - - /** - * This is a terminal that attaches to an already running remote terminal. - */ - remoteAttach?: { id: number; pid: number; title: string; cwd: string; }; - - /** - * Whether the terminal process environment should be exactly as provided in - * `TerminalOptions.env`. When this is false (default), the environment will be based on the - * window's environment and also apply configured platform settings like - * `terminal.integrated.windows.env` on top. When this is true, the complete environment must be - * provided as nothing will be inherited from the process or any configuration. - */ - strictEnv?: boolean; - - /** - * When enabled the terminal will run the process as normal but not be surfaced to the user - * until `Terminal.show` is called. The typical usage for this is when you need to run - * something that may need interactivity but only want to tell the user about it when - * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. - */ - hideFromUser?: boolean; - - /** - * Whether this terminal is not a terminal that the user directly created and uses, but rather - * a terminal used to drive some VS Code feature. - */ - isFeatureTerminal?: boolean; - - /** - * Whether flow control is enabled for this terminal. - */ - flowControl?: boolean; -} - /** * Provides access to native Windows calls that can be injected into non-native layers. */ @@ -318,25 +223,6 @@ export interface IShellDefinition { path: string; } -export interface ITerminalDimensions { - /** - * The columns of the terminal. - */ - readonly cols: number; - - /** - * The rows of the terminal. - */ - readonly rows: number; -} - -export interface ITerminalDimensionsOverride extends ITerminalDimensions { - /** - * indicate that xterm must receive these exact dimensions, even if they overflow the ui! - */ - forceExactSize?: boolean; -} - export interface ICommandTracker { scrollToPreviousCommand(): void; scrollToNextCommand(): void; @@ -360,11 +246,6 @@ export interface IBeforeProcessDataEvent { data: string; } -export interface IProcessDataEvent { - data: string; - sync: boolean; -} - export interface ITerminalProcessManager extends IDisposable { readonly processState: ProcessState; readonly ptyProcessReady: Promise; @@ -377,6 +258,7 @@ export interface ITerminalProcessManager extends IDisposable { /** Whether the process has had data written to it yet. */ readonly hasWrittenData: boolean; + readonly onPtyDisconnect: Event; readonly onProcessReady: Event; readonly onBeforeProcessData: Event; readonly onProcessData: Event; @@ -485,77 +367,6 @@ export interface IWindowsShellHelper extends IDisposable { getShellName(): Promise; } -export interface ITerminalLaunchError { - message: string; - code?: number; -} - -/** - * An interface representing a raw terminal child process, this contains a subset of the - * child_process.ChildProcess node.js interface. - */ -export interface ITerminalChildProcess { - onProcessData: Event; - onProcessExit: Event; - onProcessReady: Event<{ pid: number, cwd: string }>; - onProcessTitleChanged: Event; - onProcessOverrideDimensions?: Event; - onProcessResolvedShellLaunchConfig?: Event; - - /** - * Starts the process. - * - * @returns undefined when the process was successfully started, otherwise an object containing - * information on what went wrong. - */ - start(): Promise; - - /** - * Shutdown the terminal process. - * - * @param immediate When true the process will be killed immediately, otherwise the process will - * be given some time to make sure no additional data comes through. - */ - shutdown(immediate: boolean): void; - input(data: string): void; - resize(cols: number, rows: number): void; - - /** - * Acknowledge a data event has been parsed by the terminal, this is used to implement flow - * control to ensure remote processes to not get too far ahead of the client and flood the - * connection. - * @param charCount The number of characters being acknowledged. - */ - acknowledgeDataEvent(charCount: number): void; - - getInitialCwd(): Promise; - getCwd(): Promise; - getLatency(): Promise; -} - -export const enum FlowControlConstants { - /** - * The number of _unacknowledged_ chars to have been sent before the pty is paused in order for - * the client to catch up. - */ - HighWatermarkChars = 100000, - /** - * After flow control pauses the pty for the client the catch up, this is the number of - * _unacknowledged_ chars to have been caught up to on the client before resuming the pty again. - * This is used to attempt to prevent pauses in the flowing data; ideally while the pty is - * paused the number of unacknowledged chars would always be greater than 0 or the client will - * appear to stutter. In reality this balance is hard to accomplish though so heavy commands - * will likely pause as latency grows, not flooding the connection is the important thing as - * it's shared with other core functionality. - */ - LowWatermarkChars = 5000, - /** - * The number characters that are accumulated on the client side before sending an ack event. - * This must be less than or equal to LowWatermarkChars or the terminal max never unpause. - */ - CharCountAckSize = 5000 -} - export const enum TERMINAL_COMMAND_ID { FIND_NEXT = 'workbench.action.terminal.findNext', FIND_PREVIOUS = 'workbench.action.terminal.findPrevious', diff --git a/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts index db8c7f57d9b..ff1e611b93c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalDataBuffering.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IProcessDataEvent } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IProcessDataEvent } from 'vs/platform/terminal/common/terminal'; interface TerminalDataBuffer extends IDisposable { data: string[]; diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 0af9ae95069..17b81211dfd 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -7,7 +7,7 @@ import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { URI as Uri } from 'vs/base/common/uri'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 2692cf9e171..ac7b80ade72 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IWindowsShellHelper, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal'; import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/electron-browser/windowsShellHelper'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, platform, Platform } from 'vs/base/common/platform'; -import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; @@ -22,15 +21,27 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILogService } from 'vs/platform/log/common/log'; import { getSystemShell } from 'vs/base/node/shell'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; +import { IShellLaunchConfig, ITerminalChildProcess } from 'vs/platform/terminal/common/terminal'; +import { LocalPty } from 'vs/workbench/contrib/terminal/electron-sandbox/localPty'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; +import { localize } from 'vs/nls'; let Terminal: typeof XTermTerminal; let SearchAddon: typeof XTermSearchAddon; let Unicode11Addon: typeof XTermUnicode11Addon; let WebglAddon: typeof XTermWebglAddon; -export class TerminalInstanceService implements ITerminalInstanceService { +export class TerminalInstanceService extends Disposable implements ITerminalInstanceService { public _serviceBrand: undefined; + private readonly _onPtyHostExit = this._register(new Emitter()); + readonly onPtyHostExit = this._onPtyHostExit.event; + private readonly _onPtyHostUnresponsive = this._register(new Emitter()); + readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -38,8 +49,32 @@ export class TerminalInstanceService implements ITerminalInstanceService { @IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IHistoryService private readonly _historyService: IHistoryService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @ILocalPtyService private readonly _localPtyService: ILocalPtyService, + @INotificationService notificationService: INotificationService ) { + super(); + if (this._localPtyService.onPtyHostExit) { + this._localPtyService.onPtyHostExit(e => { + this._onPtyHostExit.fire(); + notificationService.error(`The terminal's pty host process exited, the connection to all terminal processes was lost`); + }); + } + if (this._localPtyService.onPtyHostStart) { + this._localPtyService.onPtyHostStart(() => { + this._logService.info(`ptyHost restarted`); + }); + } + if (this._localPtyService.onPtyHostUnresponsive) { + this._localPtyService.onPtyHostUnresponsive(() => { + const choices: IPromptChoice[] = [{ + label: localize('restartPtyHost', "Restart pty host"), + run: () => this._localPtyService.restartPtyHost!() + }]; + notificationService.prompt(Severity.Error, localize('nonResponsivePtyHost', "The connection to the terminal's pty host process is unresponsive, the terminals may stop working."), choices); + this._onPtyHostUnresponsive.fire(); + }); + } } public async getXtermConstructor(): Promise { @@ -74,8 +109,9 @@ export class TerminalInstanceService implements ITerminalInstanceService { return new WindowsShellHelper(shellProcessId, xterm); } - public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess { - return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, process.env as IProcessEnvironment, windowsEnableConpty); + public async createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): Promise { + const id = await this._localPtyService.createProcess(shellLaunchConfig, cwd, cols, rows, env, process.env as IProcessEnvironment, windowsEnableConpty); + return this._instantiationService.createInstance(LocalPty, id); } private _isWorkspaceShellAllowed(): boolean { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts index 91a52764186..9e67808966f 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts @@ -7,7 +7,7 @@ import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { INativeOpenFileRequest } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; -import { getWindowsBuildNumber, linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal'; +import { linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { execFile } from 'child_process'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -17,6 +17,7 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment'; export class TerminalNativeContribution extends Disposable implements IWorkbenchContribution { public _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts new file mode 100644 index 00000000000..09bbb9a43fc --- /dev/null +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IShellLaunchConfig, ITerminalLaunchError, ITerminalChildProcess, ITerminalDimensionsOverride, IProcessDataEvent } from 'vs/platform/terminal/common/terminal'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; + +/** + * Responsible for establishing and maintaining a connection with an existing terminal process + * created on the local pty host. + */ +export class LocalPty extends Disposable implements ITerminalChildProcess { + private readonly _onProcessData = this._register(new Emitter()); + public readonly onProcessData = this._onProcessData.event; + private readonly _onProcessExit = this._register(new Emitter()); + public readonly onProcessExit = this._onProcessExit.event; + private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); + public readonly onProcessReady = this._onProcessReady.event; + private readonly _onProcessTitleChanged = this._register(new Emitter()); + public readonly onProcessTitleChanged = this._onProcessTitleChanged.event; + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); + public readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event; + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); + public readonly onProcessResolvedShellLaunchConfig = this._onProcessResolvedShellLaunchConfig.event; + + constructor( + private readonly _localPtyId: number, + @ILocalPtyService private readonly _localPtyService: ILocalPtyService + ) { + super(); + this._localPtyService.onProcessData(e => e.id === this._localPtyId && this._onProcessData.fire(e.event)); + this._localPtyService.onProcessExit(e => e.id === this._localPtyId && this._onProcessExit.fire(e.event)); + this._localPtyService.onProcessReady(e => e.id === this._localPtyId && this._onProcessReady.fire(e.event)); + this._localPtyService.onProcessTitleChanged(e => e.id === this._localPtyId && this._onProcessTitleChanged.fire(e.event)); + this._localPtyService.onProcessOverrideDimensions(e => e.id === this._localPtyId && this._onProcessOverrideDimensions.fire(e.event)); + this._localPtyService.onProcessResolvedShellLaunchConfig(e => e.id === this._localPtyId && this._onProcessResolvedShellLaunchConfig.fire(e.event)); + if (this._localPtyService.onPtyHostExit) { + this._localPtyService.onPtyHostExit(() => { + this._onProcessExit.fire(undefined); + }); + } + } + + start(): Promise { + return this._localPtyService.start(this._localPtyId); + } + + shutdown(immediate: boolean): void { + this._localPtyService.shutdown(this._localPtyId, immediate); + } + + input(data: string): void { + this._localPtyService.input(this._localPtyId, data); + } + + resize(cols: number, rows: number): void { + this._localPtyService.resize(this._localPtyId, cols, rows); + } + + acknowledgeDataEvent(charCount: number): void { + this._localPtyService.acknowledgeDataEvent(this._localPtyId, charCount); + } + + getInitialCwd(): Promise { + return this._localPtyService.getInitialCwd(this._localPtyId); + } + + getCwd(): Promise { + return this._localPtyService.getCwd(this._localPtyId); + } + + getLatency(): Promise { + return this._localPtyService.getLatency(this._localPtyId); + } +} diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts new file mode 100644 index 00000000000..181c5f37ab6 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal'; +import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; + +registerSharedProcessRemoteService(ILocalPtyService, TerminalIpcChannels.LocalPty, { supportsDelayedInstantiation: true }); diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index b949c038eac..11e429a69d6 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; import * as fs from 'fs'; import * as platform from 'vs/base/common/platform'; import { SymlinkSupport } from 'vs/base/node/pfs'; @@ -11,6 +10,7 @@ import { LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/com import { coalesce } from 'vs/base/common/arrays'; import { normalize, basename } from 'vs/base/common/path'; import { enumeratePowerShellInstallations } from 'vs/base/node/powershell'; +import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment'; let detectedDistro = LinuxDistro.Unknown; if (platform.isLinux) { @@ -31,15 +31,6 @@ if (platform.isLinux) { export const linuxDistro = detectedDistro; -export function getWindowsBuildNumber(): number { - const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); - let buildNumber: number = 0; - if (osVersion && osVersion.length === 4) { - buildNumber = parseInt(osVersion[3]); - } - return buildNumber; -} - export function detectAvailableShells(): Promise { return platform.isWindows ? detectAvailableWindowsShells() : detectAvailableUnixShells(); } diff --git a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts index 9a4c932546a..2a3821ab7e9 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts @@ -6,9 +6,6 @@ import * as fs from 'fs'; import * as path from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { exists } from 'vs/base/node/pfs'; -import { isString } from 'vs/base/common/types'; -import { getCaseInsensitive } from 'vs/base/common/objects'; let mainProcessParentEnv: IProcessEnvironment | undefined; @@ -80,56 +77,3 @@ export async function getMainProcessParentEnv(baseEnvironment: IProcessEnvironme return mainProcessParentEnv!; } - -export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment): Promise { - // If we have an absolute path then we take it. - if (path.isAbsolute(command)) { - return await exists(command) ? command : undefined; - } - if (cwd === undefined) { - cwd = process.cwd(); - } - const dir = path.dirname(command); - if (dir !== '.') { - // We have a directory and the directory is relative (see above). Make the path absolute - // to the current working directory. - const fullPath = path.join(cwd, command); - return await exists(fullPath) ? fullPath : undefined; - } - const envPath = getCaseInsensitive(env, 'PATH'); - if (paths === undefined && isString(envPath)) { - paths = envPath.split(path.delimiter); - } - // No PATH environment. Make path absolute to the cwd. - if (paths === undefined || paths.length === 0) { - const fullPath = path.join(cwd, command); - return await exists(fullPath) ? fullPath : undefined; - } - // We have a simple file name. We get the path variable from the env - // and try to find the executable on the path. - for (let pathEntry of paths) { - // The path entry is absolute. - let fullPath: string; - if (path.isAbsolute(pathEntry)) { - fullPath = path.join(pathEntry, command); - } else { - fullPath = path.join(cwd, pathEntry, command); - } - - if (await exists(fullPath)) { - return fullPath; - } - if (isWindows) { - let withExtension = fullPath + '.com'; - if (await exists(withExtension)) { - return withExtension; - } - withExtension = fullPath + '.exe'; - if (await exists(withExtension)) { - return withExtension; - } - } - } - const fullPath = path.join(cwd, command); - return await exists(fullPath) ? fullPath : undefined; -} diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts index 47cbf47ad77..6cab52d3a1b 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation.ts @@ -7,6 +7,7 @@ import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { Emitter } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IndexedSet } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; @@ -39,11 +40,13 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes private readonly updateEmitter = new Emitter(); private readonly changes = new NodeChangeList(); private readonly locations = new TestLocationStore(); + private readonly itemSource = new IndexedSet(); + private readonly itemsByExtId = this.itemSource.index(v => v.test.item.extId); /** * Map of item IDs to test item objects. */ - protected readonly items = new Map(); + protected readonly items = this.itemSource.index(v => v.test.id); /** * Root folders @@ -82,15 +85,13 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes // when test states change, reflect in the tree // todo: optimize this to avoid needing to iterate this._register(results.onTestChanged(({ item: result }) => { - for (const i of this.items.values()) { - if (i.test.item.extId === result.item.extId) { - i.ownState = result.state.state; - i.retired = result.retired; - refreshComputedState(computedStateAccessor, i, this.addUpdated, result.computedState); - this.addUpdated(i); - this.updateEmitter.fire(); - return; - } + const item = this.itemsByExtId.get(result.item.extId); + if (item) { + item.ownState = result.state.state; + item.retired = result.retired; + refreshComputedState(computedStateAccessor, item, this.addUpdated, result.computedState); + this.addUpdated(item); + this.updateEmitter.fire(); } })); @@ -219,14 +220,14 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes protected unstoreItem(item: HierarchicalElement) { item.parentItem.children.delete(item); - this.items.delete(item.test.id); + this.itemSource.delete(item); this.locations.remove(item); return item.children; } protected storeItem(item: HierarchicalElement) { item.parentItem.children.add(item); - this.items.set(item.test.id, item); + this.itemSource.add(item); this.locations.add(item); const prevState = this.results.getStateByExtId(item.test.item.extId)?.[1]; diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 19896bdbd8d..52988718b5a 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -12,6 +12,7 @@ .test-explorer > .test-explorer-tree { flex-grow: 1; height: 0px; + position: relative; } .test-explorer .test-item { @@ -60,8 +61,20 @@ margin-right: 8px; } -.monaco-workbench .part > .title > .title-actions .action-label.codicon-testing-autorun::before { - padding: 2px; +.monaco-workbench .part > .title > .title-actions .action-label.codicon-testing-autorun::after { + content: ''; + display: none; + position: absolute; + width: 0.4em; + height: 0.4em; + top: 50%; + left: 50%; + margin: 0.1em 0 0 0.05em; + border-radius: 100%; +} + +.monaco-workbench .part > .title > .title-actions .action-label.codicon-testing-autorun.checked::after { + display: block; } .codicon-testing-loading-icon::before { @@ -69,6 +82,20 @@ animation: codicon-spin 1.25s steps(30) infinite; } +.testing-no-test-placeholder { + display: none; + padding: 0 20px; + position: absolute; + left: 0; + right: 0; + top: 0; + z-index: 1; +} + +.testing-no-test-placeholder.visible { + display: block; +} + /** -- peek */ .monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget { border-top-width: 2px; diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 2dac0056fb6..8772613e971 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -14,21 +14,25 @@ import { ContextKeyAndExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { FocusedViewContext } from 'vs/workbench/common/views'; +import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import * as icons from 'vs/workbench/contrib/testing/browser/icons'; import { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; import { TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants'; import { InternalTestItem, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection'; import { ITestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; +import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; import { ITestResult, ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService, waitForAllRoots, waitForAllTests } from 'vs/workbench/contrib/testing/common/testService'; import { IWorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; const category = localize('testing.category', 'Test'); @@ -182,6 +186,16 @@ export class DebugSelectedAction extends RunOrDebugSelectedAction { } } +const showDiscoveringWhile = (progress: IProgressService, task: Promise): Promise => { + return progress.withProgress( + { + location: ProgressLocation.Window, + title: localize('discoveringTests', 'Discovering Tests'), + }, + () => task, + ); +}; + abstract class RunOrDebugAllAllAction extends Action2 { constructor(id: string, title: string, icon: ThemeIcon, private readonly debug: boolean, private noTestsFoundError: string) { super({ @@ -196,7 +210,10 @@ abstract class RunOrDebugAllAllAction extends Action2 { group: 'navigation', when: ContextKeyAndExpr.create([ ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId), - ContextKeyEqualsExpr.create(TestingContextKeys.isRunning.serialize(), false), + TestingContextKeys.isRunning.isEqualTo(false), + debug + ? TestingContextKeys.hasDebuggableTests.isEqualTo(true) + : TestingContextKeys.hasRunnableTests.isEqualTo(true), ]) } }); @@ -206,13 +223,13 @@ abstract class RunOrDebugAllAllAction extends Action2 { const testService = accessor.get(ITestService); const workspace = accessor.get(IWorkspaceContextService); const notifications = accessor.get(INotificationService); + const progress = accessor.get(IProgressService); const tests: TestIdWithProvider[] = []; - await Promise.all(workspace.getWorkspace().folders.map(async (folder) => { + const todo = workspace.getWorkspace().folders.map(async (folder) => { const ref = testService.subscribeToDiffs(ExtHostTestingResource.Workspace, folder.uri); try { await waitForAllRoots(ref.object); - for (const root of ref.object.rootIds) { const node = ref.object.getNodeById(root); if (node && (this.debug ? node.item.debuggable : node.item.runnable)) { @@ -222,7 +239,9 @@ abstract class RunOrDebugAllAllAction extends Action2 { } finally { ref.dispose(); } - })); + }); + + await showDiscoveringWhile(progress, Promise.all(todo)); if (tests.length === 0) { notifications.info(this.noTestsFoundError); @@ -517,7 +536,6 @@ abstract class RunOrDebugAtCursor extends Action2 { return; } - const testService = accessor.get(ITestService); const collection = testService.subscribeToDiffs(ExtHostTestingResource.TextDocument, model.uri); @@ -525,7 +543,8 @@ abstract class RunOrDebugAtCursor extends Action2 { let bestNode: InternalTestItem | undefined; try { - await waitForAllTests(collection.object); + await showDiscoveringWhile(accessor.get(IProgressService), waitForAllTests(collection.object)); + const queue: [depth: number, nodes: Iterable][] = [[0, collection.object.rootIds]]; while (queue.length > 0) { const [depth, candidates] = queue.pop()!; @@ -593,7 +612,6 @@ export class DebugAtCursor extends RunOrDebugAtCursor { } } - abstract class RunOrDebugCurrentFile extends Action2 { /** * @override @@ -610,7 +628,7 @@ abstract class RunOrDebugCurrentFile extends Action2 { const collection = testService.subscribeToDiffs(ExtHostTestingResource.TextDocument, model.uri); try { - await waitForAllTests(collection.object); + await showDiscoveringWhile(accessor.get(IProgressService), waitForAllTests(collection.object)); const roots = [...collection.object.rootIds] .map(r => collection.object.getNodeById(r)) @@ -667,3 +685,180 @@ export class DebugCurrentFile extends RunOrDebugCurrentFile { return service.runTests({ debug: true, tests: nodes.map(node => ({ testId: node.id, providerId: node.providerId })) }); } } + +abstract class RunOrDebugTestResults extends Action2 { + /** + * @override + */ + public async run(accessor: ServicesAccessor) { + const testService = accessor.get(ITestService); + const extIds = this.getTestExtIdsToRun(accessor); + if (extIds.size === 0) { + return; + } + + const workspaceTests = accessor.get(IWorkspaceTestCollectionService).subscribeToWorkspaceTests(); + + try { + const todo = Promise.all(workspaceTests.workspaceFolderCollections.map(([, c]) => waitForAllTests(c))); + await showDiscoveringWhile(accessor.get(IProgressService), todo); + + const toRun: InternalTestItem[] = []; + for (const [, collection] of workspaceTests.workspaceFolderCollections) { + for (const node of collection.all) { + if (extIds.has(node.item.extId) && this.filter(node)) { + toRun.push(node); + extIds.delete(node.item.extId); + } + } + } + + if (toRun.length) { + await this.runTest(testService, toRun); + } + } finally { + workspaceTests.dispose(); + } + } + + protected abstract getTestExtIdsToRun(accessor: ServicesAccessor): Set; + + protected abstract filter(node: InternalTestItem): boolean; + + protected abstract runTest(service: ITestService, node: InternalTestItem[]): Promise; +} + +abstract class RunOrDebugFailedTests extends RunOrDebugTestResults { + /** + * @inheritdoc + */ + protected getTestExtIdsToRun(accessor: ServicesAccessor): Set { + const { results } = accessor.get(ITestResultService); + const extIds = new Set(); + for (let i = results.length - 1; i >= 0; i--) { + for (const test of results[i].tests) { + if (isFailedState(test.state.state)) { + extIds.add(test.item.extId); + } else { + extIds.delete(test.item.extId); + } + } + } + + return extIds; + } +} + +abstract class RunOrDebugLastRun extends RunOrDebugTestResults { + /** + * @inheritdoc + */ + protected getTestExtIdsToRun(accessor: ServicesAccessor): Set { + const lastResult = accessor.get(ITestResultService).results[0]; + const extIds = new Set(); + if (!lastResult) { + return extIds; + } + + for (const test of lastResult.tests) { + if (test.direct) { + extIds.add(test.item.extId); + } + } + + return extIds; + } +} + +export class ReRunFailedTests extends RunOrDebugFailedTests { + constructor() { + super({ + id: 'testing.reRunFailTests', + title: localize('testing.reRunFailTests', "Re-run Failed Tests"), + f1: true, + category, + }); + } + + protected filter(node: InternalTestItem): boolean { + return node.item.runnable; + } + + protected runTest(service: ITestService, nodes: InternalTestItem[]): Promise { + return service.runTests({ debug: false, tests: nodes.map(node => ({ testId: node.id, providerId: node.providerId })) }); + } +} + +export class DebugFailedTests extends RunOrDebugFailedTests { + constructor() { + super({ + id: 'testing.debugFailTests', + title: localize('testing.debugFailTests', "Debug Failed Tests"), + f1: true, + category, + }); + } + + protected filter(node: InternalTestItem): boolean { + return node.item.debuggable; + } + + protected runTest(service: ITestService, nodes: InternalTestItem[]): Promise { + return service.runTests({ debug: true, tests: nodes.map(node => ({ testId: node.id, providerId: node.providerId })) }); + } +} + +export class ReRunLastRun extends RunOrDebugLastRun { + constructor() { + super({ + id: 'testing.reRunLastRun', + title: localize('testing.reRunLastRun', "Re-run Last Run"), + f1: true, + category, + }); + } + + protected filter(node: InternalTestItem): boolean { + return node.item.runnable; + } + + protected runTest(service: ITestService, nodes: InternalTestItem[]): Promise { + return service.runTests({ debug: false, tests: nodes.map(node => ({ testId: node.id, providerId: node.providerId })) }); + } +} + +export class DebugLastRun extends RunOrDebugLastRun { + constructor() { + super({ + id: 'testing.debugLastRun', + title: localize('testing.debugLastRun', "Debug Last Run"), + f1: true, + category, + }); + } + + protected filter(node: InternalTestItem): boolean { + return node.item.debuggable; + } + + protected runTest(service: ITestService, nodes: InternalTestItem[]): Promise { + return service.runTests({ debug: true, tests: nodes.map(node => ({ testId: node.id, providerId: node.providerId })) }); + } +} + +export class SearchForTestExtension extends Action2 { + constructor() { + super({ + id: 'testing.searchForTestExtension', + title: localize('testing.searchForTestExtension', "Search for Test Extension"), + f1: false, + }); + } + + public async run(accessor: ServicesAccessor) { + const viewletService = accessor.get(IViewletService); + const viewlet = (await viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer; + viewlet.search('tag:testing @sort:installs'); + viewlet.focus(); + } +} diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 6ef650fcedf..3919c1f1500 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -51,7 +51,6 @@ const viewContainer = Registry.as(ViewContainerExtensio hideIfEmpty: true, }, ViewContainerLocation.Sidebar); - const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(Testing.ExplorerViewId, { @@ -80,7 +79,7 @@ viewsRegistry.registerViews([{ order: -999, containerIcon: testingViewIcon, // temporary until release, at which point we can show the welcome view: - when: ContextKeyExpr.greater(TestingContextKeys.providerCount.serialize(), 0), + when: ContextKeyExpr.greater(TestingContextKeys.providerCount.key, 0), }], viewContainer); registerAction2(Action.TestingViewAsListAction); @@ -101,6 +100,11 @@ registerAction2(Action.DebugAtCursor); registerAction2(Action.RunAtCursor); registerAction2(Action.DebugCurrentFile); registerAction2(Action.RunCurrentFile); +registerAction2(Action.ReRunFailedTests); +registerAction2(Action.DebugFailedTests); +registerAction2(Action.ReRunLastRun); +registerAction2(Action.DebugLastRun); +registerAction2(Action.SearchForTestExtension); registerAction2(CloseTestPeek); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 567258a3597..54f94ebe446 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -26,13 +26,14 @@ import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from import { testingRunAllIcon, testingRunIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons'; import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme'; -import { IncrementalTestCollectionItem, IRichLocation, ITestMessage } from 'vs/workbench/contrib/testing/common/testCollection'; +import { IncrementalTestCollectionItem, IRichLocation, ITestMessage, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri'; -import { ITestResultService, TestResultItem } from 'vs/workbench/contrib/testing/common/testResultService'; +import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { IMainThreadTestCollection, ITestService } from 'vs/workbench/contrib/testing/common/testService'; export class TestingDecorations extends Disposable implements IEditorContribution { private collection = this._register(new MutableDisposable>()); + private currentUri?: URI; private lastDecorations: ITestDecoration[] = []; constructor( @@ -52,9 +53,22 @@ export class TestingDecorations extends Disposable implements IEditorContributio } } })); + + this._register(this.results.onTestChanged(({ item: result }) => { + if (this.currentUri && result.item.location?.uri.toString() === this.currentUri.toString()) { + this.setDecorations(this.currentUri); + } + })); + this._register(this.results.onResultsChanged(() => { + if (this.currentUri) { + this.setDecorations(this.currentUri); + } + })); } private attachModel(uri?: URI) { + this.currentUri = uri; + if (!uri) { this.collection.value = undefined; this.clearDecorations(); @@ -62,15 +76,6 @@ export class TestingDecorations extends Disposable implements IEditorContributio } this.collection.value = this.testService.subscribeToDiffs(ExtHostTestingResource.TextDocument, uri, () => this.setDecorations(uri)); - this._register(this.results.onTestChanged(({ item: result }) => { - if (result.item.location?.uri.toString() === uri.toString()) { - this.setDecorations(uri); - } - })); - this._register(this.results.onResultsChanged(() => { - this.setDecorations(uri); - })); - this.setDecorations(uri); } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index bcbd917f11f..08eb41d1ddb 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -4,29 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; +import { Button } from 'vs/base/browser/ui/button/button'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; -import { Action, IAction, IActionViewItem } from 'vs/base/common/actions'; -import { DeferredPromise } from 'vs/base/common/async'; +import { ITreeContextMenuEvent, ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { Action, IAction } from 'vs/base/common/actions'; +import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async'; import { Color, RGBA } from 'vs/base/common/color'; import { throttle } from 'vs/base/common/decorators'; import { Event } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; import { splitGlobAware } from 'vs/base/common/glob'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable, DisposableStore, dispose, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/testing'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { localize } from 'vs/nls'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -39,6 +43,7 @@ import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { foreground } from 'vs/platform/theme/common/colorRegistry'; +import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { TestRunState } from 'vs/workbench/api/common/extHostTypes'; import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels'; @@ -145,7 +150,10 @@ export class TestingExplorerView extends ViewPane { } private createFilterActionBar() { - const bar = new ActionBar(this.container, { actionViewItemProvider: action => this.getActionViewItem(action) }); + const bar = new ActionBar(this.container, { + actionViewItemProvider: action => this.getActionViewItem(action), + triggerKeys: { keyDown: false, keys: [] }, + }); bar.push(new Action(Testing.FilterActionId)); bar.getContainer().classList.add('testing-filter-action-bar'); return bar; @@ -177,11 +185,35 @@ export class TestingExplorerView extends ViewPane { } } +class EmptyTestsWidget extends Disposable { + private readonly el: HTMLElement; + constructor( + container: HTMLElement, + @ICommandService commandService: ICommandService, + @IThemeService themeService: IThemeService, + ) { + super(); + const el = this.el = dom.append(container, dom.$('.testing-no-test-placeholder')); + const emptyParagraph = dom.append(el, dom.$('p')); + emptyParagraph.innerText = localize('testingNoTest', 'No tests have been found in this workspace yet.'); + const buttonLabel = localize('testingFindExtension', 'Find Test Extensions'); + const button = this._register(new Button(el, { title: buttonLabel })); + button.label = buttonLabel; + this._register(attachButtonStyler(button, themeService)); + this._register(button.onDidClick(() => commandService.executeCommand('testing.searchForTestExtension'))); + } + + public setVisible(isVisible: boolean) { + this.el.classList.toggle('visible', isVisible); + } +} + export class TestingExplorerViewModel extends Disposable { public tree: ObjectTree; private filter: TestsFilter; public projection!: ITestTreeProjection; + private readonly emptyTestsWidget: EmptyTestsWidget; private readonly _viewMode = TestingContextKeys.viewMode.bindTo(this.contextKeyService); private readonly _viewSorting = TestingContextKeys.viewSorting.bindTo(this.contextKeyService); @@ -223,6 +255,9 @@ export class TestingExplorerViewModel extends Disposable { listContainer: HTMLElement, onDidChangeVisibility: Event, private listener: TestSubscriptionListener | undefined, + @IMenuService private readonly menuService: IMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @ITestService private readonly testService: ITestService, @ITestExplorerFilterState filterState: TestExplorerFilterState, @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @@ -233,6 +268,7 @@ export class TestingExplorerViewModel extends Disposable { ) { super(); + this.emptyTestsWidget = this._register(instantiationService.createInstance(EmptyTestsWidget, listContainer)); this._viewMode.set(this.storageService.get('testing.viewMode', StorageScope.WORKSPACE, TestExplorerViewMode.Tree) as TestExplorerViewMode); this._viewSorting.set(this.storageService.get('testing.viewSorting', StorageScope.WORKSPACE, TestExplorerViewSorting.ByLocation) as TestExplorerViewSorting); @@ -257,11 +293,18 @@ export class TestingExplorerViewModel extends Disposable { filter: this.filter, }) as WorkbenchObjectTree; - this._register(Event.any( - filterState.currentDocumentOnly.onDidChange, - filterState.text.onDidChange, - filterState.stateFilter.onDidChange, - )(this.tree.refilter, this.tree)); + this._register(filterState.currentDocumentOnly.onDidChange(() => { + if (!filterState.currentDocumentOnly.value) { + this.filter.filterToUri(undefined); + } else if (editorService.activeEditor?.resource && this.projection.hasTestInDocument(editorService.activeEditor.resource)) { + this.filter.filterToUri(editorService.activeEditor.resource); + } + + this.tree.refilter(); + })); + + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(editorService.onDidActiveEditorChange(() => { if (filterState.currentDocumentOnly.value && editorService.activeEditor?.resource) { if (this.projection.hasTestInDocument(editorService.activeEditor.resource)) { @@ -271,15 +314,28 @@ export class TestingExplorerViewModel extends Disposable { } })); + this._register(Event.any( + filterState.text.onDidChange, + filterState.stateFilter.onDidChange, + )(this.tree.refilter, this.tree)); + this._register(this.tree); this._register(dom.addStandardDisposableListener(this.tree.getHTMLElement(), 'keydown', evt => { - if (DefaultKeyboardNavigationDelegate.mightProducePrintableCharacter(evt)) { + if (evt.equals(KeyCode.Enter)) { + this.handleExecuteKeypress(evt); + } else if (DefaultKeyboardNavigationDelegate.mightProducePrintableCharacter(evt)) { filterState.text.value = evt.browserEvent.key; filterState.focusInput(); } })); + this._register(onDidChangeVisibility(visible => { + if (visible) { + filterState.focusInput(); + } + })); + this.updatePreferredProjection(); this.onDidChangeSelection = this.tree.onDidChangeSelection; @@ -395,6 +451,47 @@ export class TestingExplorerViewModel extends Disposable { : false; } + private onContextMenu(evt: ITreeContextMenuEvent) { + if (!evt.element) { + return; + } + + const actions = getTestItemActions(this.instantiationService, this.contextKeyService, this.menuService, evt.element); + this.contextMenuService.showContextMenu({ + getAnchor: () => evt.anchor, + getActions: () => actions.value.secondary, + getActionsContext: () => evt.element?.test?.item.extId, + onHide: () => actions.dispose(), + }); + } + + private handleExecuteKeypress(evt: IKeyboardEvent) { + const focused = this.tree.getFocus(); + const selected = this.tree.getSelection(); + let targeted: (ITestTreeElement | null)[]; + if (focused.length === 1 && selected.includes(focused[0])) { + evt.browserEvent?.preventDefault(); + targeted = selected; + } else { + targeted = focused; + } + + const toRun = targeted + .map(e => e?.test) + .filter(isDefined) + .filter(e => e.item.runnable); + + if (toRun.length) { + this.testService.runTests({ debug: false, tests: toRun.map(t => ({ providerId: t.providerId, testId: t.id })) }); + } + } + + private shouldShowEmptyPlaceholder() { + return !!this.listener + && this.listener.subscription.busyProviders === 0 + && this.listener.subscription.isEmpty; + } + private updatePreferredProjection() { this.projection?.dispose(); if (!this.listener) { @@ -408,12 +505,18 @@ export class TestingExplorerViewModel extends Disposable { this.projection = this.instantiationService.createInstance(HierarchicalByLocationProjection, this.listener); } - this.projection.onUpdate(this.deferUpdate, this); - this.projection.applyTo(this.tree); + const scheduler = new RunOnceScheduler(() => this.applyProjectionChanges(), 200); + this.projection.onUpdate(() => { + if (!scheduler.isScheduled()) { + scheduler.schedule(); + } + }); + + this.applyProjectionChanges(); } - @throttle(200) - private deferUpdate() { + private applyProjectionChanges() { + this.emptyTestsWidget.setVisible(this.shouldShowEmptyPlaceholder()); this.projection.applyTo(this.tree); } @@ -515,8 +618,8 @@ class TestsFilter implements ITreeFilter { } } - public filterToUri(uri: URI) { - this._filterToUri = uri.toString(); + public filterToUri(uri: URI | undefined) { + this._filterToUri = uri?.toString(); } /** @@ -659,25 +762,32 @@ interface TestTemplateData { label: IResourceLabel; icon: HTMLElement; actionBar: ActionBar; + elementDisposable: IDisposable[]; + templateDisposable: IDisposable[]; } -class TestsRenderer implements ITreeRenderer { +class TestsRenderer extends Disposable implements ITreeRenderer { public static readonly ID = 'testExplorer'; constructor( private labels: ResourceLabels, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService - ) { } - - renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: TestTemplateData): void { - const element = node.element.elements[node.element.elements.length - 1]; - this.renderElementDirect(element, templateData); + ) { + super(); } + /** + * @inheritdoc + */ get templateId(): string { return TestsRenderer.ID; } + /** + * @inheritdoc + */ public renderTemplate(container: HTMLElement): TestTemplateData { const wrapper = dom.append(container, dom.$('.test-item')); @@ -689,18 +799,16 @@ class TestsRenderer implements ITreeRenderer action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action) - : undefined, - respectOrientationForPreviousAndNextKey: true + : undefined }); - return { label, actionBar, icon }; + return { label, actionBar, icon, elementDisposable: [], templateDisposable: [label, actionBar] }; } - public renderElement(node: ITreeNode, index: number, data: TestTemplateData): void { - this.renderElementDirect(node.element, data); - } - - private renderElementDirect(element: ITestTreeElement, data: TestTemplateData) { + /** + * @inheritdoc + */ + public renderElement({ element }: ITreeNode, _: number, data: TestTemplateData): void { const label: IResourceLabelProps = { name: element.label }; const options: IResourceLabelOptions = {}; data.actionBar.clear(); @@ -729,30 +837,65 @@ class TestsRenderer implements ITreeRenderer, _: number, templateData: TestTemplateData): void { + dispose(templateData.elementDisposable); + templateData.elementDisposable = []; + } + + private fillActionBar(element: ITestTreeElement, data: TestTemplateData) { + const actions = getTestItemActions(this.instantiationService, this.contextKeyService, this.menuService, element); + data.elementDisposable.push(actions); + data.actionBar.clear(); + data.actionBar.push(actions.value.primary, { icon: true, label: false }); } } +const getTestItemActions = (instantionService: IInstantiationService, contextKeyService: IContextKeyService, menuService: IMenuService, element: ITestTreeElement) => { + const contextOverlay = contextKeyService.createOverlay([ + ['view', Testing.ExplorerViewId], + [TestingContextKeys.testItemExtId.key, element.test?.item.extId] + ]); + const menu = menuService.createMenu(MenuId.TestItem, contextOverlay); + + try { + const primary: IAction[] = []; + const running = element.state === TestRunState.Running; + if (!Iterable.isEmpty(element.runnable)) { + primary.push(instantionService.createInstance(RunAction, element.runnable, running)); + } + + if (!Iterable.isEmpty(element.debuggable)) { + primary.push(instantionService.createInstance(DebugAction, element.debuggable, running)); + } + + const secondary: IAction[] = []; + const result = { primary, secondary }; + const actionsDisposable = createAndFillInActionBarActions(menu, { + arg: element.test?.item.extId, + shouldForwardArgs: true, + }, result, g => /^inline/.test(g)); + + return { value: result, dispose: () => actionsDisposable.dispose }; + } finally { + menu.dispose(); + } +}; + type CountSummary = ReturnType; const collectCounts = (count: TestStateCount) => { @@ -822,7 +965,7 @@ class TestRunProgress { } private updateProgress() { - const running = this.resultService.results.filter(r => !r.isComplete); + const running = this.resultService.results.filter(r => r.completedAt === undefined); if (!running.length) { this.setIdleText(this.resultService.results[0]?.counts); this.current?.deferred.complete(); @@ -871,7 +1014,7 @@ class TestRunProgress { return; } - if (!result.isComplete) { + if (result.completedAt === undefined) { const badge = new ProgressBadge(() => localize('testBadgeRunning', 'Test run in progress')); this.badge.value = this.activityService.showViewActivity(Testing.ExplorerViewId, { badge, clazz: 'progress-badge' }); return; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 3ee275e166a..b3715669718 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { alert } from 'vs/base/browser/ui/aria/aria'; import { Codicon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -30,11 +31,11 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { testingPeekBorder } from 'vs/workbench/contrib/testing/browser/theme'; import { AutoOpenPeekViewWhen, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; -import { ITestItem, ITestMessage, ITestState } from 'vs/workbench/contrib/testing/common/testCollection'; +import { ITestItem, ITestMessage, ITestState, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates'; import { buildTestUri, parseTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri'; -import { ITestResult, ITestResultService, TestResultItem, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResultService'; +import { ITestResult, ITestResultService, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResultService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; interface ITestDto { @@ -203,6 +204,7 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo this.peek.value!.create(); } + alert(message.message.toString()); this.peek.value!.setModel(dto); } @@ -321,6 +323,8 @@ const diffEditorOptions: IDiffEditorOptions = { renderOverviewRuler: false, ignoreTrimWhitespace: false, renderSideBySide: true, + originalAriaLabel: localize('testingOutputExpected', 'Expected result'), + modifiedAriaLabel: localize('testingOutputActual', 'Actual result'), }; class TestingDiffOutputPeek extends TestingOutputPeek { diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts index 3c80d02eae0..635baca1d34 100644 --- a/src/vs/workbench/contrib/testing/browser/theme.ts +++ b/src/vs/workbench/contrib/testing/browser/theme.ts @@ -8,6 +8,7 @@ import { localize } from 'vs/nls'; import { editorErrorForeground, editorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TestMessageSeverity, TestRunState } from 'vs/workbench/api/common/extHostTypes'; +import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; export const testingColorIconFailed = registerColor('testing.iconFailed', { dark: '#f14c4c', @@ -135,15 +136,17 @@ registerThemingParticipant((theme, collector) => { //#region active buttons const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); if (inputActiveOptionBorderColor) { - collector.addRule(`.testing-filter-button.checked, .codicon-testing-autorun.checked::before { border-color: ${inputActiveOptionBorderColor}; }`); + collector.addRule(`.testing-filter-button.checked { border-color: ${inputActiveOptionBorderColor}; }`); } const inputActiveOptionForegroundColor = theme.getColor(inputActiveOptionForeground); if (inputActiveOptionForegroundColor) { - collector.addRule(`.testing-filter-button.checked, .codicon-testing-autorun.checked::before { color: ${inputActiveOptionForegroundColor}; }`); + collector.addRule(`.testing-filter-button.checked { color: ${inputActiveOptionForegroundColor}; }`); } const inputActiveOptionBackgroundColor = theme.getColor(inputActiveOptionBackground); if (inputActiveOptionBackgroundColor) { - collector.addRule(`.testing-filter-button.checked, .codicon-testing-autorun.checked::before { background-color: ${inputActiveOptionBackgroundColor}; }`); + collector.addRule(`.testing-filter-button.checked { background-color: ${inputActiveOptionBackgroundColor}; }`); } + const badgeColor = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND); + collector.addRule(`.monaco-workbench .part > .title > .title-actions .action-label.codicon-testing-autorun::after { background-color: ${badgeColor}; }`); //#endregion }); diff --git a/src/vs/workbench/contrib/testing/common/testCollection.ts b/src/vs/workbench/contrib/testing/common/testCollection.ts index e42f8a207a9..7d4ede8fc71 100644 --- a/src/vs/workbench/contrib/testing/common/testCollection.ts +++ b/src/vs/workbench/contrib/testing/common/testCollection.ts @@ -79,12 +79,32 @@ export interface InternalTestItem { item: ITestItem; } -export interface InternalTestItemWithChildren extends InternalTestItem { - children: this[]; +/** + * Test result item used in the main thread. + */ +export interface TestResultItem extends IncrementalTestCollectionItem { + /** Current state of this test */ + state: ITestState; + /** Computed state based on children */ + computedState: TestRunState; + /** True if the test is outdated */ + retired: boolean; + /** True if the test was directly requested by the run (is not a child or parent) */ + direct?: boolean; } -export interface InternalTestResults { - tests: InternalTestItemWithChildren[]; +export type SerializedTestResultItem = Omit & { children: string[], retired: undefined }; + +/** + * Test results serialized for transport and storage. + */ +export interface ISerializedTestResults { + /** ID of these test results */ + id: string; + /** Time the results were compelted */ + completedAt: number; + /** Subset of test result items */ + items: SerializedTestResultItem[]; } export const enum TestDiffOpType { diff --git a/src/vs/workbench/contrib/testing/common/testResultService.ts b/src/vs/workbench/contrib/testing/common/testResultService.ts index c380e53708b..85bd7cd44b7 100644 --- a/src/vs/workbench/contrib/testing/common/testResultService.ts +++ b/src/vs/workbench/contrib/testing/common/testResultService.ts @@ -3,7 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { findFirstInSorted } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { Lazy } from 'vs/base/common/lazy'; +import { equals } from 'vs/base/common/objects'; +import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { Range } from 'vs/editor/common/core/range'; @@ -13,7 +18,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { TestRunState } from 'vs/workbench/api/common/extHostTypes'; import { IComputedStateAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState'; import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; -import { IncrementalTestCollectionItem, ITestState, RunTestsRequest } from 'vs/workbench/contrib/testing/common/testCollection'; +import { IncrementalTestCollectionItem, ISerializedTestResults, ITestState, RunTestsRequest, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; import { statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates'; import { IMainThreadTestCollection } from 'vs/workbench/contrib/testing/common/testService'; @@ -47,15 +52,21 @@ export interface ITestResult { readonly id: string; /** - * Gets whether the test run has finished. + * If the test is completed, the unix milliseconds time at which it was + * completed. If undefined, the test is still running. */ - readonly isComplete: boolean; + readonly completedAt: number | undefined; /** * Whether this test result is triggered from an auto run. */ readonly isAutoRun?: boolean; + /** + * Gets all tests involved in the run. + */ + tests: IterableIterator; + /** * Gets the state of the test by its extension-assigned ID. */ @@ -65,7 +76,7 @@ export interface ITestResult { * Serializes the test result. Used to save and restore results * in the workspace. */ - toJSON(): ISerializedResults; + toJSON(): ISerializedTestResults | undefined; } export const makeEmptyCounts = () => { @@ -147,6 +158,7 @@ const makeNodeAndChildren = ( test: IncrementalTestCollectionItem, byExtId: Map, byInternalId: Map, + isExecutedDirectly = true, ): TestResultItem => { const existing = byInternalId.get(test.id); if (existing) { @@ -154,27 +166,20 @@ const makeNodeAndChildren = ( } const mapped = itemToNode(test, byExtId, byInternalId); + if (isExecutedDirectly) { + mapped.direct = true; + } + for (const childId of test.children) { const child = collection.getNodeById(childId); if (child) { - makeNodeAndChildren(collection, child, byExtId, byInternalId); + makeNodeAndChildren(collection, child, byExtId, byInternalId, false); } } return mapped; }; -interface ISerializedResults { - id: string; - items: (Omit & { children: string[], retired: undefined })[]; -} - -export interface TestResultItem extends IncrementalTestCollectionItem { - state: ITestState; - computedState: TestRunState; - retired: boolean; -} - /** * Results of a test. These are created when the test initially started running * and marked as "complete" when the run finishes. @@ -207,7 +212,7 @@ export class LiveTestResult implements ITestResult { private readonly completeEmitter = new Emitter(); private readonly changeEmitter = new Emitter(); - private _complete = false; + private _completedAt?: number; public readonly onChange = this.changeEmitter.event; public readonly onComplete = this.completeEmitter.event; @@ -220,8 +225,8 @@ export class LiveTestResult implements ITestResult { /** * @inheritdoc */ - public get isComplete() { - return this._complete; + public get completedAt() { + return this._completedAt; } /** @@ -230,7 +235,7 @@ export class LiveTestResult implements ITestResult { public readonly counts: { [K in TestRunState]: number } = makeEmptyCounts(); /** - * Gets all tests involved in the run by ID. + * @inheritdoc */ public get tests() { return this.testByInternalId.values(); @@ -376,35 +381,38 @@ export class LiveTestResult implements ITestResult { * Notifies the service that all tests are complete. */ public markComplete() { - if (this._complete) { + if (this._completedAt !== undefined) { throw new Error('cannot complete a test result multiple times'); } // un-queue any tests that weren't explicitly updated this.setAllToState(unsetState, t => t.state.state === TestRunState.Queued); - this._complete = true; + this._completedAt = Date.now(); this.completeEmitter.fire(); } /** * @inheritdoc */ - public toJSON(): ISerializedResults { - return { - id: this.id, - items: [...this.testByExtId.values()].map(entry => ({ - ...entry, - retired: undefined, - children: [...entry.children], - })), - }; + public toJSON(): ISerializedTestResults | undefined { + return this.completedAt ? this.doSerialize.getValue() : undefined; } + + private readonly doSerialize = new Lazy((): ISerializedTestResults => ({ + id: this.id, + completedAt: this.completedAt!, + items: [...this.testByExtId.values()].map(entry => ({ + ...entry, + retired: undefined, + children: [...entry.children], + })), + })); } /** * Test results hydrated from a previously-serialized test run. */ -class HydratedTestResult implements ITestResult { +export class HydratedTestResult implements ITestResult { /** * @inheritdoc */ @@ -418,12 +426,20 @@ class HydratedTestResult implements ITestResult { /** * @inheritdoc */ - public readonly isComplete = true; + public readonly completedAt: number; + + /** + * @inheritdoc + */ + public get tests() { + return this.byExtId.values(); + } private readonly byExtId = new Map(); - constructor(private readonly serialized: ISerializedResults) { + constructor(private readonly serialized: ISerializedTestResults, private readonly persist = true) { this.id = serialized.id; + this.completedAt = serialized.completedAt; for (const item of serialized.items) { const cast: TestResultItem = { ...item, retired: true, children: new Set(item.children) }; @@ -454,14 +470,15 @@ class HydratedTestResult implements ITestResult { /** * @inheritdoc */ - public toJSON(): ISerializedResults { - return this.serialized; + public toJSON(): ISerializedTestResults | undefined { + return this.persist ? this.serialized : undefined; } } export type ResultChangeEvent = | { completed: LiveTestResult } | { started: LiveTestResult } + | { inserted: ITestResult } | { removed: ITestResult[] }; export interface ITestResultService { @@ -489,7 +506,7 @@ export interface ITestResultService { /** * Adds a new test result to the collection. */ - push(result: LiveTestResult): LiveTestResult; + push(result: T): T; /** * Looks up a set of test results by ID. @@ -506,6 +523,14 @@ export const ITestResultService = createDecorator('testResul const RETAIN_LAST_RESULTS = 64; +/** + * Returns if the tests in the results are exactly equal. Check the counts + * first as a cheap check before starting to iterate. + */ +const resultsEqual = (a: ITestResult, b: ITestResult) => + a.completedAt === b.completedAt && equals(a.counts, b.counts) && Iterable.equals(a.tests, b.tests, + (at, bt) => equals(at.state, bt.state) && equals(at.item, bt.item)); + export class TestResultService implements ITestResultService { declare _serviceBrand: undefined; private changeResultEmitter = new Emitter(); @@ -527,7 +552,7 @@ export class TestResultService implements ITestResultService { public readonly onTestChanged = this.testChangeEmitter.event; private readonly isRunning: IContextKey; - private readonly serializedResults: StoredValue; + private readonly serializedResults: StoredValue; constructor(@IContextKeyService contextKeyService: IContextKeyService, @IStorageService storage: IStorageService) { this.isRunning = TestingContextKeys.isRunning.bindTo(contextKeyService); @@ -539,7 +564,10 @@ export class TestResultService implements ITestResultService { try { for (const value of this.serializedResults.get([])) { - this.results.push(new HydratedTestResult(value)); + // todo@connor4312: temp to migrate old insiders + if (value.completedAt) { + this.results.push(new HydratedTestResult(value)); + } } } catch (e) { // outdated structure @@ -563,17 +591,47 @@ export class TestResultService implements ITestResultService { /** * @inheritdoc */ - public push(result: LiveTestResult): LiveTestResult { - this.results.unshift(result); + public push(result: T): T { + if (result.completedAt === undefined) { + this.results.unshift(result); + } else { + const index = findFirstInSorted(this.results, r => r.completedAt !== undefined && r.completedAt <= result.completedAt!); + const prev = this.results[index]; + if (prev && resultsEqual(result, prev)) { + return result; + } + + this.results.splice(index, 0, result); + this.persist(); + } + if (this.results.length > RETAIN_LAST_RESULTS) { this.results.pop(); } - result.onComplete(() => this.onComplete(result)); - result.onChange(this.testChangeEmitter.fire, this.testChangeEmitter); - this.isRunning.set(true); - this.changeResultEmitter.fire({ started: result }); - result.setAllToState(queuedState, () => true); + if (result instanceof LiveTestResult) { + result.onComplete(() => this.onComplete(result)); + result.onChange(this.testChangeEmitter.fire, this.testChangeEmitter); + this.isRunning.set(true); + this.changeResultEmitter.fire({ started: result }); + result.setAllToState(queuedState, () => true); + } else { + this.changeResultEmitter.fire({ inserted: result }); + // If this is not a new result, go through each of its tests. For each + // test for which the new result is the most recently inserted, fir + // a change event so that UI updates. + for (const item of result.tests) { + for (const otherResult of this.results) { + if (otherResult === result) { + this.testChangeEmitter.fire({ item, result, reason: TestResultItemChangeReason.ComputedStateChange }); + break; + } else if (otherResult.getStateByExtId(item.item.extId) !== undefined) { + break; + } + } + } + } + return result; } @@ -591,7 +649,7 @@ export class TestResultService implements ITestResultService { const keep: ITestResult[] = []; const removed: ITestResult[] = []; for (const result of this.results) { - if (result.isComplete) { + if (result.completedAt !== undefined) { removed.push(result); } else { keep.push(result); @@ -599,20 +657,26 @@ export class TestResultService implements ITestResultService { } this.results = keep; - this.serializedResults.store(this.results.map(r => r.toJSON())); + this.persist(); this.changeResultEmitter.fire({ removed }); } private onComplete(result: LiveTestResult) { - // move the complete test run down behind any still-running ones - for (let i = 0; i < this.results.length - 1; i++) { - if (this.results[i].isComplete && !this.results[i + 1].isComplete) { - [this.results[i], this.results[i + 1]] = [this.results[i + 1], this.results[i]]; - } - } - - this.isRunning.set(!this.results[0]?.isComplete); - this.serializedResults.store(this.results.map(r => r.toJSON())); + this.resort(); + this.updateIsRunning(); + this.persist(); this.changeResultEmitter.fire({ completed: result }); } + + private resort() { + this.results.sort((a, b) => (b.completedAt ?? Number.MAX_SAFE_INTEGER) - (a.completedAt ?? Number.MAX_SAFE_INTEGER)); + } + + private updateIsRunning() { + this.isRunning.set(this.results.length > 0 && this.results[0].completedAt === undefined); + } + + private persist() { + this.serializedResults.store(this.results.map(r => r.toJSON()).filter(isDefined)); + } } diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index 8d1e5221dde..6b9e801b46b 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; -import { IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { DisposableStore, IReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol'; @@ -57,38 +57,40 @@ export interface IMainThreadTestCollection extends AbstractIncrementalTestCollec getReviverDiff(): TestsDiff; } -export const waitForAllRoots = (collection: IMainThreadTestCollection, timeout = 3000) => { - if (collection.pendingRootProviders === 0) { +export const waitForAllRoots = (collection: IMainThreadTestCollection, ct = CancellationToken.None) => { + if (collection.pendingRootProviders === 0 || ct.isCancellationRequested) { return Promise.resolve(); } - let listener: IDisposable; + const disposable = new DisposableStore(); return new Promise(resolve => { - listener = collection.onPendingRootProvidersChange(count => { + disposable.add(collection.onPendingRootProvidersChange(count => { if (count === 0) { resolve(); } - }); + })); - setTimeout(resolve, timeout); - }).finally(() => listener.dispose()); + disposable.add(ct.onCancellationRequested(() => resolve())); + }).finally(() => disposable.dispose()); }; -export const waitForAllTests = (collection: IMainThreadTestCollection, timeout = 3000) => { - if (collection.busyProviders === 0) { - return Promise.resolve(); +export const waitForAllTests = async (collection: IMainThreadTestCollection, ct = CancellationToken.None) => { + await waitForAllRoots(collection, ct); + + if (collection.busyProviders === 0 || ct.isCancellationRequested) { + return; } - let listener: IDisposable; + const disposable = new DisposableStore(); return new Promise(resolve => { - listener = collection.onBusyProvidersChange(count => { + disposable.add(collection.onBusyProvidersChange(count => { if (count === 0) { resolve(); } - }); + })); - setTimeout(resolve, timeout); - }).finally(() => listener.dispose()); + disposable.add(ct.onCancellationRequested(() => resolve())); + }).finally(() => disposable.dispose()); }; export interface ITestService { diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts index 95d4a9e7bc8..8b65c2c83ff 100644 --- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts +++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts @@ -39,12 +39,16 @@ export class TestService extends Disposable implements ITestService { private readonly busyStateChangeEmitter = new Emitter(); private readonly changeProvidersEmitter = new Emitter<{ delta: number }>(); private readonly providerCount: IContextKey; + private readonly hasRunnable: IContextKey; + private readonly hasDebuggable: IContextKey; private readonly runningTests = new Map(); private rootProviderCount = 0; constructor(@IContextKeyService contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService, @ITestResultService private readonly testResults: ITestResultService) { super(); this.providerCount = TestingContextKeys.providerCount.bindTo(contextKeyService); + this.hasDebuggable = TestingContextKeys.hasDebuggableTests.bindTo(contextKeyService); + this.hasRunnable = TestingContextKeys.hasRunnableTests.bindTo(contextKeyService); } /** @@ -216,11 +220,14 @@ export class TestService extends Disposable implements ITestService { */ public publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff) { const sub = this.testSubscriptions.get(getTestSubscriptionKey(resource, URI.revive(uri))); - if (sub) { - sub.collection.apply(diff); - // console.log('accept', sub.collection, diff); - sub.onDiff.fire(diff); + if (!sub) { + return; } + + sub.collection.apply(diff); + sub.onDiff.fire(diff); + this.hasDebuggable.set(!!this.findTest(t => t.item.debuggable)); + this.hasRunnable.set(!!this.findTest(t => t.item.runnable)); } /** @@ -240,6 +247,18 @@ export class TestService extends Disposable implements ITestService { this.providerCount.set(this.testControllers.size); this.changeProvidersEmitter.fire({ delta: -1 }); } + + private findTest(predicate: (t: InternalTestItem) => boolean): InternalTestItem | undefined { + for (const { collection } of this.testSubscriptions.values()) { + for (const test of collection.all) { + if (predicate(test)) { + return test; + } + } + } + + return undefined; + } } export class MainThreadTestCollection extends AbstractIncrementalTestCollection implements IMainThreadTestCollection { diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index 3a1f40ad723..c2a97327607 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { TestExplorerViewMode, TestExplorerViewSorting } from 'vs/workbench/contrib/testing/common/constants'; export namespace TestingContextKeys { export const providerCount = new RawContextKey('testing.providerCount', 0); + export const hasDebuggableTests = new RawContextKey('testing.hasDebuggableTests', false); + export const hasRunnableTests = new RawContextKey('testing.hasRunnableTests', false); export const viewMode = new RawContextKey('testing.explorerViewMode', TestExplorerViewMode.List); export const viewSorting = new RawContextKey('testing.explorerViewSorting', TestExplorerViewSorting.ByLocation); export const isRunning = new RawContextKey('testing.isRunning', false); @@ -16,4 +19,8 @@ export namespace TestingContextKeys { export const isPeekVisible = new RawContextKey('testing.isPeekVisible', false); export const explorerLocation = new RawContextKey('testing.explorerLocation', ViewContainerLocation.Sidebar); export const autoRun = new RawContextKey('testing.autoRun', false); + export const testItemExtId = new RawContextKey('testId', undefined, { + type: 'string', + description: localize('testing.testId', 'ID of the current test item, set when creating or opening menus on test items') + }); } diff --git a/src/vs/workbench/contrib/testing/common/workspaceTestCollectionService.ts b/src/vs/workbench/contrib/testing/common/workspaceTestCollectionService.ts index e38fb6d1ae6..dcd1f333174 100644 --- a/src/vs/workbench/contrib/testing/common/workspaceTestCollectionService.ts +++ b/src/vs/workbench/contrib/testing/common/workspaceTestCollectionService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; @@ -148,6 +149,19 @@ class TestSubscription extends Disposable { return [...this.collectionsForWorkspaces.values()].map(v => [v.folder, v.collection] as const); } + /** + * Returns whether there are any subscriptions with non-empty providers. + */ + public get isEmpty() { + for (const { collection } of this.collectionsForWorkspaces.values()) { + if (Iterable.some(collection.all, t => !!t.parent)) { + return false; + } + } + + return true; + } + constructor( @IWorkspaceContextService workspaceContext: IWorkspaceContextService, @ITestService private readonly testService: ITestService, diff --git a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts index 86851a62f51..031eaa9c24d 100644 --- a/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts +++ b/src/vs/workbench/contrib/testing/test/common/testResultService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { InternalTestItem } from 'vs/workbench/contrib/testing/common/testCollection'; -import { LiveTestResult, makeEmptyCounts, TestResultItemChange, TestResultItemChangeReason, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; +import { HydratedTestResult, LiveTestResult, makeEmptyCounts, TestResultItemChange, TestResultItemChangeReason, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ReExportedTestRunState as TestRunState } from 'vs/workbench/contrib/testing/common/testStubs'; import { getInitializedMainTestCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -161,7 +161,7 @@ suite('Workbench - Test Results Service', () => { assert.deepStrictEqual(actual, { ...expected, retired: true }); assert.deepStrictEqual(rehydrated.counts, r.counts); - assert.strictEqual(rehydrated.isComplete, true); + assert.strictEqual(typeof rehydrated.completedAt, 'number'); }); test('clears results but keeps ongoing tests', () => { @@ -190,5 +190,51 @@ suite('Workbench - Test Results Service', () => { r.markComplete(); assert.deepStrictEqual(results.results, [r, r2]); }); + + const makeHydrated = (completedAt = 42, state = TestRunState.Passed) => new HydratedTestResult({ + completedAt, + id: 'some-id', + items: [{ + ...getInitializedMainTestCollection().getNodeById('2')!, + state: { state, duration: 0, messages: [] }, + computedState: state, + retired: undefined, + children: [], + }] + }); + + test('pushes hydrated results', () => { + results.push(r); + const hydrated = makeHydrated(); + results.push(hydrated); + assert.deepStrictEqual(results.results, [r, hydrated]); + }); + + test('deduplicates identical results', () => { + results.push(r); + const hydrated1 = makeHydrated(); + results.push(hydrated1); + const hydrated2 = makeHydrated(); + results.push(hydrated2); + assert.deepStrictEqual(results.results, [r, hydrated1]); + }); + + test('does not deduplicate if different completedAt', () => { + results.push(r); + const hydrated1 = makeHydrated(); + results.push(hydrated1); + const hydrated2 = makeHydrated(30); + results.push(hydrated2); + assert.deepStrictEqual(results.results, [r, hydrated1, hydrated2]); + }); + + test('does not deduplicate if different tests', () => { + results.push(r); + const hydrated1 = makeHydrated(); + results.push(hydrated1); + const hydrated2 = makeHydrated(undefined, TestRunState.Failed); + results.push(hydrated2); + assert.deepStrictEqual(results.results, [r, hydrated2, hydrated1]); + }); }); }); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index f3277edc2fa..a0b5aa73388 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/timelinePane'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; -import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { fromNow } from 'vs/base/common/date'; import { debounce } from 'vs/base/common/decorators'; @@ -36,7 +36,7 @@ import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService' import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { createAndFillInContextMenuActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 63cfe385dca..2315cdb25b0 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -21,12 +21,17 @@ import { ReleaseNotesManager } from './releaseNotesEditor'; import { isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ShowCurrentReleaseNotesActionId, CheckForVSCodeUpdateActionId } from 'vs/workbench/contrib/update/common/update'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; +import { IUserDataAutoSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, SyncStatus, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { Promises } from 'vs/base/common/async'; +import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { Event } from 'vs/base/common/event'; export const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Idle); @@ -524,27 +529,93 @@ export class SwitchProductQualityContribution extends Disposable implements IWor if (productQualityChangeHandler && (quality === 'stable' || quality === 'insider')) { const newQuality = quality === 'stable' ? 'insider' : 'stable'; const commandId = `update.switchQuality.${newQuality}`; - CommandsRegistry.registerCommand(commandId, async accessor => { - const dialogService = accessor.get(IDialogService); - - const res = await dialogService.confirm({ - type: 'info', - message: nls.localize('relaunchMessage', "Changing the version requires a reload to take effect"), - detail: newQuality === 'insider' ? - nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the nightly pre-production version of VSCode.") : - nls.localize('relaunchDetailStable', "Press the reload button to switch to the monthly released stable version of VSCode."), - primaryButton: nls.localize('reload', "&&Reload") - }); - - if (res.confirmed) { - productQualityChangeHandler(newQuality); + const isSwitchingToInsiders = newQuality === 'insider'; + registerAction2(class SwitchQuality extends Action2 { + constructor() { + super({ + id: commandId, + title: isSwitchingToInsiders ? nls.localize('switchToInsiders', "Switch to Insiders Version...") : nls.localize('switchToStable', "Switch to Stable Version..."), + precondition: IsWebContext, + menu: { + id: MenuId.GlobalActivity, + when: IsWebContext, + group: '7_update', + } + }); } - }); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '7_update', - command: { - id: commandId, - title: newQuality === 'insider' ? nls.localize('switchToInsiders', "Switch to Insiders Version...") : nls.localize('switchToStable', "Switch to Stable Version...") + + async run(accessor: ServicesAccessor): Promise { + const dialogService = accessor.get(IDialogService); + const userDataAutoSyncEnablementService = accessor.get(IUserDataAutoSyncEnablementService); + const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService); + const storageService = accessor.get(IStorageService); + const userDataSyncWorkbenchService = accessor.get(IUserDataSyncWorkbenchService); + const userDataSyncService = accessor.get(IUserDataSyncService); + + const selectSettingsSyncServiceDialogShownKey = 'switchQuality.selectSettingsSyncServiceDialogShown'; + const userDataSyncStore = userDataSyncStoreManagementService.userDataSyncStore; + let userDataSyncStoreType: UserDataSyncStoreType | undefined; + if (userDataSyncStore && isSwitchingToInsiders && userDataAutoSyncEnablementService.isEnabled() + && !storageService.getBoolean(selectSettingsSyncServiceDialogShownKey, StorageScope.GLOBAL, false)) { + userDataSyncStoreType = await this.selectSettingsSyncService(dialogService); + if (!userDataSyncStoreType) { + return; + } + storageService.store(selectSettingsSyncServiceDialogShownKey, true, StorageScope.GLOBAL, StorageTarget.USER); + if (userDataSyncStoreType === 'stable') { + // Update the stable service type in the current window, so that it uses stable service after switched to insiders version (after reload). + await userDataSyncStoreManagementService.switch(userDataSyncStoreType); + } + } + + const res = await dialogService.confirm({ + type: 'info', + message: nls.localize('relaunchMessage', "Changing the version requires a reload to take effect"), + detail: newQuality === 'insider' ? + nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the nightly pre-production version of VSCode.") : + nls.localize('relaunchDetailStable', "Press the reload button to switch to the monthly released stable version of VSCode."), + primaryButton: nls.localize('reload', "&&Reload") + }); + + if (res.confirmed) { + const promises: Promise[] = []; + + // If sync is happening wait until it is finished before reload + if (userDataSyncService.status === SyncStatus.Syncing) { + promises.push(Event.toPromise(Event.filter(userDataSyncService.onDidChangeStatus, status => status !== SyncStatus.Syncing))); + } + + // Synchronise the store type option in insiders service, so that other clients using insiders service are also updated. + if (isSwitchingToInsiders) { + promises.push(userDataSyncWorkbenchService.synchroniseUserDataSyncStoreType()); + } + + await Promises.settled(promises); + + productQualityChangeHandler(newQuality); + } else { + // Reset + if (userDataSyncStoreType) { + storageService.remove(selectSettingsSyncServiceDialogShownKey, StorageScope.GLOBAL); + } + } + } + + private async selectSettingsSyncService(dialogService: IDialogService): Promise { + const res = await dialogService.show( + Severity.Info, + nls.localize('selectSyncService.message', "Choose the settings sync service to use after changing the version"), + [ + nls.localize('use insiders', "Insiders"), + nls.localize('use stable', "Stable (current)"), + nls.localize('cancel', "Cancel"), + ], + { + detail: nls.localize('selectSyncService.detail', "Insiders version of VSCode will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), + cancelId: 2 + } + ); + return res.choice === 0 ? 'insiders' : res.choice === 1 ? 'stable' : undefined; } }); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 33b6202653d..2524044c559 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -111,7 +111,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IOutputService private readonly outputService: IOutputService, @IUserDataSyncAccountService readonly authTokenService: IUserDataSyncAccountService, @IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, - @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, + @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, @ITextModelService textModelResolverService: ITextModelService, @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -288,7 +288,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo case UserDataSyncErrorCode.TurnedOff: this.notificationService.notify({ severity: Severity.Info, - message: localize('turned off', "Settings sync was turned off from another device, please sign in again to turn on sync."), + message: localize('turned off', "Settings sync was turned off from another device, please turn on sync again."), actions: { primary: [new Action('turn on sync', localize('turn on sync', "Turn on Settings Sync..."), undefined, true, () => this.turnOn())] } @@ -323,11 +323,34 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } }); return; + + case UserDataSyncErrorCode.ServiceChanged: + this.notificationService.notify({ + severity: Severity.Info, + message: this.userDataSyncStoreManagementService.userDataSyncStore?.type === 'insiders' ? + localize('service switched to insiders', "Settings Sync has been switched to insiders service") : + localize('service switched to stable', "Settings Sync has been switched to stable service"), + }); + + return; + case UserDataSyncErrorCode.DefaultServiceChanged: - if (isEqual(this.userDataSyncStoreManagementService.userDataSyncStore?.url, this.userDataSyncStoreManagementService.userDataSyncStore?.insidersUrl)) { + // Settings sync is using separate service + if (this.userDataAutoSyncEnablementService.isEnabled()) { this.notificationService.notify({ severity: Severity.Info, - message: localize('switched to insiders', "Settings sync now uses a separate service, more information is available in the [release notes](https://code.visualstudio.com/updates/v1_48#_settings-sync)."), + message: localize('using separate service', "Settings sync now uses a separate service, more information is available in the [Settings Sync Documentation](https://aka.ms/vscode-settings-sync-help#_syncing-stable-versus-insiders)."), + }); + } + + // If settings sync got turned off then ask user to turn on sync again. + else { + this.notificationService.notify({ + severity: Severity.Info, + message: localize('service changed and turned off', "Settings sync was turned off because {0} now uses a separate service. Please turn on sync again.", this.productService.nameLong), + actions: { + primary: [new Action('turn on sync', localize('turn on sync', "Turn on Settings Sync..."), undefined, true, () => this.turnOn())] + } }); } return; @@ -1030,7 +1053,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } run(accessor: ServicesAccessor): Promise { - return that.userDataAutoSyncService.triggerSync([syncNowCommand.id], false, true); + return that.userDataSyncWorkbenchService.syncNow(); } })); } diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 84a19799aab..c4ece0e2bff 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -158,7 +158,11 @@ export abstract class BaseWebview extends Disposable { // Electron: workaround for https://github.com/electron/electron/issues/14258 // We have to detect keyboard events in the and dispatch them to our // keybinding service because these events do not bubble to the parent window anymore. - this.handleKeyDown(data); + this.handleKeyEvent('keydown', data); + })); + + this._register(this.on('did-keyup', (data: KeyboardEvent) => { + this.handleKeyEvent('keyup', data); })); this.style(); @@ -330,9 +334,9 @@ export abstract class BaseWebview extends Disposable { } } - private handleKeyDown(event: IKeydownEvent) { + private handleKeyEvent(type: 'keydown' | 'keyup', event: IKeydownEvent) { // Create a fake KeyboardEvent from the data provided - const emulatedKeyboardEvent = new KeyboardEvent('keydown', event); + const emulatedKeyboardEvent = new KeyboardEvent(type, event); // Force override the target Object.defineProperty(emulatedKeyboardEvent, 'target', { get: () => this.element, diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 2d4ac83084a..052dce0ce3b 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -312,6 +312,21 @@ repeat: e.repeat }); }; + /** + * @param {KeyboardEvent} e + */ + const handleInnerUp = (e) => { + host.postMessage('did-keyup', { + key: e.key, + keyCode: e.keyCode, + code: e.code, + shiftKey: e.shiftKey, + altKey: e.altKey, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + repeat: e.repeat + }); + }; /** * @param {KeyboardEvent} e @@ -508,12 +523,15 @@ newFrame.setAttribute('id', 'pending-frame'); newFrame.setAttribute('frameborder', '0'); newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin allow-pointer-lock allow-downloads' : 'allow-same-origin allow-pointer-lock'); + newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : ''); if (host.fakeLoad) { // We should just be able to use srcdoc, but I wasn't // seeing the service worker applying properly. // Fake load an empty on the correct origin and then write real html // into it to get around this. newFrame.src = `./fake.html?id=${ID}`; + } else { + newFrame.src = `about:blank?webviewFrame`; } newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; document.body.appendChild(newFrame); @@ -630,6 +648,7 @@ newFrame.contentWindow.addEventListener('click', handleInnerClick); newFrame.contentWindow.addEventListener('auxclick', handleAuxClick); newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); + newFrame.contentWindow.addEventListener('keyup', handleInnerUp); newFrame.contentWindow.addEventListener('contextmenu', e => e.preventDefault()); if (host.onIframeLoaded) { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index d006ebb3243..f6ff3b6e7be 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -92,6 +92,7 @@ export class IFrameWebview extends BaseWebview implements Web const element = document.createElement('iframe'); element.className = `webview ${options.customClasses || ''}`; element.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms', 'allow-pointer-lock', 'allow-downloads'); + element.setAttribute('allow', 'clipboard-read; clipboard-write;'); element.style.border = 'none'; element.style.width = '100%'; element.style.height = '100%'; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index 5b4d1fcb93d..0e86fc3b0f6 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -112,6 +112,12 @@ font-size: 32px; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category img.category-icon { + margin-right: 10px; + max-width: 32px; + max-height: 32px; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail { display: flex; flex-direction: column; @@ -150,12 +156,11 @@ display: flex; width: 100%; overflow: hidden; - transition: max-height .1s linear; - max-height: 180px; + transition: height .1s linear; } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) { - max-height: 54px; + height: 54px; background: none; opacity: 0.8; } @@ -169,10 +174,12 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) .actions, .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) button, .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task:not(.expanded) a { - display: none; + visibility: hidden; } - +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description-container { + width: 100%; +} .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description { padding-top: 8px; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index f890c8f273e..20cf9e44220 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -27,6 +27,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IGettingStartedCategoryDescriptor } from 'vs/workbench/services/gettingStarted/common/gettingStartedRegistry'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -104,7 +105,11 @@ export class GettingStartedPage extends EditorPane { this.gettingStartedCategories = this.gettingStartedService.getCategories(); this._register(this.dispatchListeners); - this._register(this.gettingStartedService.onDidAddTask(task => console.log('added new task', task, 'that isnt being rendered yet'))); + this._register(this.gettingStartedService.onDidAddTask(task => { + this.gettingStartedCategories = this.gettingStartedService.getCategories(); + this.buildCategoriesSlide(); + })); + this._register(this.gettingStartedService.onDidAddCategory(category => console.log('added new category', category, 'that isnt being rendered yet'))); this._register(this.gettingStartedService.onDidProgressTask(task => { const category = this.gettingStartedCategories.find(category => category.id === task.category); @@ -214,12 +219,14 @@ export class GettingStartedPage extends EditorPane { const mediaElement = assertIsDefined(this.container.querySelector('.getting-started-media') as HTMLImageElement); this.taskDisposables.clear(); if (id) { - const taskElement = assertIsDefined(this.container.querySelector(`[data-task-id="${id}"]`)); - taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => { + const taskElement = assertIsDefined(this.container.querySelector(`[data-task-id="${id}"]`)); + taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => { node.classList.remove('expanded'); + node.style.height = ``; node.setAttribute('aria-expanded', 'false'); }); - setTimeout(() => (taskElement as HTMLDivElement).focus(), delayFocus ? SLIDE_TRANSITION_TIME_MS : 0); + taskElement.style.height = `${taskElement.scrollHeight}px`; + setTimeout(() => (taskElement as HTMLElement).focus(), delayFocus ? SLIDE_TRANSITION_TIME_MS : 0); if (this.editorInput.selectedTask === id && contractIfAlreadySelected) { this.editorInput.selectedTask = undefined; return; @@ -253,7 +260,7 @@ export class GettingStartedPage extends EditorPane { private updateMediaSourceForColorMode(element: HTMLImageElement, sources: { hc: URI, dark: URI, light: URI }) { const themeType = this.themeService.getColorTheme().type; - element.src = sources[themeType].toString(); + element.src = sources[themeType].toString(true); } createEditor(parent: HTMLElement) { @@ -273,7 +280,7 @@ export class GettingStartedPage extends EditorPane { const tasksSlide = $('.gettingStartedSlideDetails.gettingStartedSlide.detail', {}, - $('button.prev-button.button-link', { 'x-dispatch': 'scrollPrev' }, $('span.scroll-button.codicon.codicon-chevron-left'), localize('back', "Back")), + $('button.prev-button.button-link', { 'x-dispatch': 'scrollPrev' }, $('span.scroll-button.codicon.codicon-chevron-left'), localize('more', "More")), tasksContent ); @@ -307,9 +314,7 @@ export class GettingStartedPage extends EditorPane { $('.category-description.description', { 'aria-label': category.description + ' ' + localize('pressEnterToSelect', "Press Enter to Select") }, category.description), $('.category-progress', { 'x-data-category-id': category.id, }, $('.message'), - $('.progress-bar-outer', { - 'role': 'progressbar' - }, + $('.progress-bar-outer', { 'role': 'progressbar' }, $('.progress-bar-inner')))) : $('.category-description-container', {}, @@ -321,7 +326,8 @@ export class GettingStartedPage extends EditorPane { 'x-dispatch': 'selectCategory:' + category.id, 'role': 'listitem', }, - $(ThemeIcon.asCSSSelector(category.icon), {}), categoryDescriptionElement); + this.iconWidgetFor(category), + categoryDescriptionElement); }); const categoryScrollContainer = $('.getting-started-categories-scrolling-container'); @@ -364,14 +370,20 @@ export class GettingStartedPage extends EditorPane { assertIsDefined(this.container.querySelector('.product-name')).textContent = this.productService.nameLong; this.registerDispatchListeners(); + const someItemsComplete = this.gettingStartedCategories.some(categry => categry.content.type === 'items' && categry.content.stepsComplete); if (this.editorInput.selectedCategory) { this.currentCategory = this.gettingStartedCategories.find(category => category.id === this.editorInput.selectedCategory); if (!this.currentCategory) { throw Error('Could not restore to category ' + this.editorInput.selectedCategory + ' as it was not found'); } - this.setSlide('details'); this.buildCategorySlide(this.editorInput.selectedCategory, this.editorInput.selectedTask); + this.setSlide('details'); + } else if (!someItemsComplete) { + this.currentCategory = assertIsDefined(this.gettingStartedCategories.find(category => category.content.type === 'items')); + this.editorInput.selectedCategory = this.currentCategory?.id; + this.buildCategorySlide(this.editorInput.selectedCategory); + this.setSlide('details'); } else { this.setSlide('categories'); } @@ -381,6 +393,16 @@ export class GettingStartedPage extends EditorPane { this.categoriesScrollbar?.scanDomNode(); this.detailsScrollbar?.scanDomNode(); this.detailImageScrollbar?.scanDomNode(); + + // Don't let our spacer elements move around based on internal content changes, only when the external size changes + this.container.querySelectorAll('.gap').forEach(element => { + element.style.width = ''; + element.style.height = ''; + setTimeout(() => { + element.style.width = `${element.clientWidth}px`; + element.style.height = `${element.clientHeight}px`; + }, 0); + }); } private updateCategoryProgress() { @@ -419,8 +441,19 @@ export class GettingStartedPage extends EditorPane { }); } + private iconWidgetFor(category: IGettingStartedCategoryDescriptor) { + return category.icon.type === 'icon' ? $(ThemeIcon.asCSSSelector(category.icon.icon)) : $('img.category-icon', { src: category.icon.path }); + } + private buildCategorySlide(categoryID: string, selectedItem?: string) { const category = this.gettingStartedCategories.find(category => category.id === categoryID); + let foundNext = false; + const nextCategory = this.gettingStartedCategories.find(category => { + if (foundNext && category.content.type === 'items') { return true; } + if (category.id === categoryID) { foundNext = true; } + return false; + }); + if (!category) { throw Error('could not find category with ID ' + categoryID); } if (category.content.type !== 'items') { throw Error('category with ID ' + categoryID + ' is not of items type'); } @@ -432,7 +465,7 @@ export class GettingStartedPage extends EditorPane { detailTitle.appendChild( $('.getting-started-category', {}, - $(ThemeIcon.asCSSSelector(category.icon), {}), + this.iconWidgetFor(category), $('.category-description-container', {}, $('h2.category-title', {}, category.title), $('.category-description.description', {}, category.description)))); @@ -459,10 +492,10 @@ export class GettingStartedPage extends EditorPane { : []), ...( arr[i + 1] - ? [ - $('button.task-next', - { 'x-dispatch': 'selectTask:' + arr[i + 1].id }, localize('next', "Next")), - ] : [] + ? [$('button.task-next', { 'x-dispatch': 'selectTask:' + arr[i + 1].id }, localize('next', "Next")),] + : nextCategory + ? [$('button.task-next', { 'x-dispatch': 'selectCategory:' + nextCategory.id }, localize('nextPage', "Next Page")),] + : [] )) ))); diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 8d6cb051ee9..6240c337b3a 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./workspaceTrustEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceTrustService, WorkspaceTrustState, WorkspaceTrustStateChangeEvent, workspaceTrustStateToString } from 'vs/platform/workspace/common/workspaceTrust'; @@ -18,13 +20,16 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { WorkspaceTrustFileSystemProvider } from 'vs/workbench/contrib/workspace/common/workspaceTrustFileSystemProvider'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; -import { WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED, WORKSPACE_TRUST_URI } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { WorkspaceTrustEditor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustEditor'; +import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; +import { WorkspaceTrustContext, WORKSPACE_TRUST_ENABLED } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { EditorInput, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; const workspaceTrustIcon = registerIcon('workspace-trust-icon', Codicon.shield, localize('workspaceTrustIcon', "Icon for workspace trust badge.")); @@ -173,12 +178,36 @@ Registry.as(WorkbenchExtensions.Workbench).regi LifecyclePhase.Starting ); -/* - * Trusted Workspace JSON Editor +/** + * Trusted Workspace GUI Editor */ -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( - WorkspaceTrustFileSystemProvider, - LifecyclePhase.Ready +class WorkspaceTrustEditorInputFactory implements IEditorInputFactory { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(input: WorkspaceTrustEditorInput): string { + return '{}'; + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): WorkspaceTrustEditorInput { + return instantiationService.createInstance(WorkspaceTrustEditorInput); + } +} + +Registry.as(EditorInputExtensions.EditorInputFactories) + .registerEditorInputFactory(WorkspaceTrustEditorInput.ID, WorkspaceTrustEditorInputFactory); + +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + WorkspaceTrustEditor, + WorkspaceTrustEditor.ID, + localize('workspaceTrustEditor', "Workspace Trust Editor") + ), + [ + new SyncDescriptor(WorkspaceTrustEditorInput) + ] ); /* @@ -285,7 +314,11 @@ registerAction2(class extends Action2 { run(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); - editorService.openEditor({ resource: WORKSPACE_TRUST_URI, mode: 'jsonc', options: { pinned: true } }); + const instantiationService = accessor.get(IInstantiationService); + + const input = instantiationService.createInstance(WorkspaceTrustEditorInput); + + editorService.openEditor(input, { pinned: true, revealIfOpened: true }); return; } }); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css new file mode 100644 index 00000000000..45ec02a5da4 --- /dev/null +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-icon-label.file-icon.workspacetrusteditor-name-file-icon.ext-file-icon.tab-label::before { + font-family: 'codicon'; + content: '\eb53'; +} + +.workspace-trust-editor.settings-editor { + max-width: 1000px; + padding-top: 11px; + padding-left: 15px; + padding-right: 15px; + margin: auto; +} + +.workspace-trust-editor.settings-editor > .workspace-trust-header { + padding: 14px; + border: solid 1px; +} + +.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-title { + font-size: 24px; + font-weight: 600; + padding: 10px 0px; +} + +/** Buttons Container */ +.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-buttons-row { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 20px 0 10px 0; + overflow: hidden; /* buttons row should never overflow */ + white-space: nowrap; +} + +/** Buttons */ +.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-buttons-row .workspace-trust-buttons { + display: flex; + overflow: hidden; +} + +.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button { + width: fit-content; + width: -moz-fit-content; + padding: 5px 10px; + overflow: hidden; + text-overflow: ellipsis; + margin: 4px 5px; /* allows button focus outline to be visible */ + outline-offset: 2px !important; +} + +.monaco-workbench.vs .workspace-trust-editor.settings-editor > .workspace-trust-header.workspace-trust-unknown { + border-color: #cccccc; +} + +.monaco-workbench.vs-dark .workspace-trust-editor.settings-editor > .workspace-trust-header.workspace-trust-unknown { + border-color: #3c3c3c; +} + +.monaco-workbench.vs .workspace-trust-editor.settings-editor > .workspace-trust-header.workspace-trust-untrusted, +.monaco-workbench.vs-dark .workspace-trust-editor.settings-editor > .workspace-trust-header.workspace-trust-untrusted { + border-color: #FF0000; +} + +.monaco-workbench.vs .workspace-trust-editor.settings-editor > .workspace-trust-header.workspace-trust-trusted, +.monaco-workbench.vs-dark .workspace-trust-editor.settings-editor > .workspace-trust-header.workspace-trust-trusted { + border-color: #327E36; +} + +/** Settings */ +.workspace-trust-editor.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { + padding-left: 0; + padding-right: 0; +} + +.workspace-trust-editor.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents .setting-list-edit-row { + margin-top: 4px; +} + +.workspace-trust-editor.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents .setting-list-edit-row > .setting-list-valueInput { + width: 100%; + max-width: 100%; +} diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts new file mode 100644 index 00000000000..47e60a806c1 --- /dev/null +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -0,0 +1,189 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $, append, clearNode, Dimension, EventHelper } from 'vs/base/browser/dom'; +import { ButtonBar } from 'vs/base/browser/ui/button/button'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Iterable } from 'vs/base/common/iterator'; +import { isArray } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { IWorkspaceTrustSettingChangeEvent, WorkspaceTrustSettingArrayRenderer, WorkspaceTrustTree, WorkspaceTrustTreeModel } from 'vs/workbench/contrib/workspace/browser/workspaceTrustTree'; +import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput'; +import { WorkspaceTrustEditorModel } from 'vs/workbench/services/workspaces/common/workspaceTrust'; + +export class WorkspaceTrustEditor extends EditorPane { + static readonly ID: string = 'workbench.editor.workspaceTrust'; + private rootElement!: HTMLElement; + private headerContainer!: HTMLElement; + private headerTitle!: HTMLElement; + private headerDescription!: HTMLElement; + private headerButtons!: HTMLElement; + private configurationContainer!: HTMLElement; + private trustSettingsTree!: WorkspaceTrustTree; + private workspaceTrustSettingsTreeModel!: WorkspaceTrustTreeModel; + private workspaceTrustEditorModel!: WorkspaceTrustEditorModel; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @ICommandService private readonly commandService: ICommandService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); } + + protected createEditor(parent: HTMLElement): void { + this.rootElement = append(parent, $('.workspace-trust-editor.settings-editor', { tabindex: '-1' })); + + this.createHeaderElement(this.rootElement); + this.createConfigurationElement(this.rootElement); + } + + async setInput(input: WorkspaceTrustEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + + await super.setInput(input, options, context, token); + if (token.isCancellationRequested) { return; } + + const model = await input.resolve(); + if (token.isCancellationRequested || !(model instanceof WorkspaceTrustEditorModel)) { + return; + } + + this._register(model.dataModel.onDidChangeTrustState(() => { + this.render(model); + })); + + this.render(model); + + this.workspaceTrustEditorModel = model; + } + + private getHeaderContainerClass(trustState: WorkspaceTrustState): string { + switch (trustState) { + case WorkspaceTrustState.Trusted: + return 'workspace-trust-header workspace-trust-trusted'; + case WorkspaceTrustState.Untrusted: + return 'workspace-trust-header workspace-trust-untrusted'; + case WorkspaceTrustState.Unknown: + return 'workspace-trust-header workspace-trust-unknown'; + } + } + + private getHeaderTitleText(trustState: WorkspaceTrustState): string { + switch (trustState) { + case WorkspaceTrustState.Trusted: + return localize('trustedHeader', "This Workspace is Trusted"); + case WorkspaceTrustState.Untrusted: + return localize('untrustedHeader', "This Workspace is Not Trusted"); + case WorkspaceTrustState.Unknown: + return localize('unknownHeader', "This Workspace has Not Been Trusted"); + } + } + + private getHeaderDescriptionText(trustState: WorkspaceTrustState): string { + switch (trustState) { + case WorkspaceTrustState.Trusted: + return localize('trustedHeaderDescription', "All features requiring trust in this workspace are enabled. Below is the current list of features that will be disabled you grant trust to the workspace. Note that after trust is given, new features requiring trust will automatically inheret the current workspace trust status."); + case WorkspaceTrustState.Untrusted: + return localize('untrustedHeaderDescription', "This workspace has limited functionality as some features will not work until trust is given to the current workspace. Below is the current list of features that will be disabled you grant trust to the workspace. Note that after trust is given, new features requiring trust will automatically inherit the current workspace trust status."); + case WorkspaceTrustState.Unknown: + return localize('unknownHeaderDescription', "This workspace has limited functionality as some features will not work until trust is given to the current workspace. Below is the current list of features that will be disabled you grant trust to the workspace. Note that after trust is given, new features requiring trust will automatically inherit the current workspace trust status."); + } + } + + private render(model: WorkspaceTrustEditorModel): void { + this.headerTitle.innerText = this.getHeaderTitleText(model.currentWorkspaceTrustState); + this.headerDescription.innerText = this.getHeaderDescriptionText(model.currentWorkspaceTrustState); + this.headerContainer.className = this.getHeaderContainerClass(model.currentWorkspaceTrustState); + + clearNode(this.headerButtons); + const buttonBar = this._register(new ButtonBar(this.headerButtons)); + + const createButton = (label: string, command: string) => { + const button = buttonBar.addButton({ title: true }); + button.label = label; + this._register(button.onDidClick(e => { + if (e) { + EventHelper.stop(e); + } + + this.commandService.executeCommand(command); + })); + }; + + if (model.currentWorkspaceTrustState !== WorkspaceTrustState.Trusted) { + createButton(localize('trustButton', "Trust"), 'workbench.trust.grant'); + } + + if (model.currentWorkspaceTrustState !== WorkspaceTrustState.Untrusted) { + createButton(localize('doNotTrustButton', "Don't Trust"), 'workbench.trust.deny'); + } + + createButton(localize('learnMore', "Learn More"), 'workbench.trust.learnMore'); + + this.workspaceTrustSettingsTreeModel.update(model.dataModel.getTrustStateInfo()); + + this.trustSettingsTree.setChildren(null, Iterable.map(this.workspaceTrustSettingsTreeModel.settings, s => { return { element: s }; })); + } + + private createHeaderElement(parent: HTMLElement): void { + this.headerContainer = append(parent, $('.workspace-trust-header')); + this.headerTitle = append(this.headerContainer, $('.workspace-trust-title')); + this.headerDescription = append(this.headerContainer, $('.workspace-trust-description')); + + const buttonsRow = append(this.headerContainer, $('.workspace-trust-buttons-row')); + this.headerButtons = append(buttonsRow, $('.workspace-trust-buttons')); + } + + private createConfigurationElement(parent: HTMLElement): void { + this.configurationContainer = append(parent, $('.workspace-trust-settings.settings-body')); + + const workspaceTrustTreeContainer = append(this.configurationContainer, $('.workspace-trust-settings-tree-container.settings-tree-container')); + const renderer = this.instantiationService.createInstance(WorkspaceTrustSettingArrayRenderer,); + + this.trustSettingsTree = this._register(this.instantiationService.createInstance(WorkspaceTrustTree, + workspaceTrustTreeContainer, + [renderer])); + + this.workspaceTrustSettingsTreeModel = this.instantiationService.createInstance(WorkspaceTrustTreeModel); + + this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e))); + } + + private onDidChangeSetting(change: IWorkspaceTrustSettingChangeEvent) { + if (this.workspaceTrustEditorModel) { + if (isArray(change.value)) { + console.log(change.key); + console.log(change.value); + if (change.key === 'trustedFolders') { + this.workspaceTrustEditorModel.dataModel.setTrustedFolders(change.value.map(item => URI.file(item))); + } + + if (change.key === 'untrustedFolders') { + this.workspaceTrustEditorModel.dataModel.setUntrustedFolders(change.value.map(item => URI.file(item))); + } + } + } + } + + layout(dimension: Dimension): void { + if (!this.isVisible()) { + return; + } + + const listHeight = dimension.height - this.configurationContainer.offsetTop; + this.configurationContainer.style.height = `${listHeight}`; + + this.trustSettingsTree.layout(listHeight, dimension.width); + } +} diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts new file mode 100644 index 00000000000..1abfc3cb2d7 --- /dev/null +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts @@ -0,0 +1,471 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener, append, EventType, $, createStyleSheet, trackFocus } from 'vs/base/browser/dom'; +import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; +import { IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; +import { ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { Color, RGBA } from 'vs/base/common/color'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { isArray } from 'vs/base/common/types'; +import { localize } from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { NonCollapsibleObjectTreeModel } from 'vs/workbench/contrib/preferences/browser/settingsTree'; +import { focusedRowBackground, focusedRowBorder, IListDataItem, ISettingListChangeEvent, ListSettingWidget, rowHoverBackground, settingsHeaderForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { attachStyler } from 'vs/platform/theme/common/styler'; +import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IWorkspaceTrustStateInfo, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; + + +export class WorkspaceTrustSettingsTreeEntry { + id: string; + displayLabel: string; + setting: { + key: string; + description: string; + }; + value: string[]; + + constructor(key: string, displayLabel: string, description: string, value: string[]) { + this.setting = { key, description }; + this.displayLabel = displayLabel; + this.value = value; + this.id = key; + } +} + +export interface IWorkspaceTrustSettingItemTemplate { + onChange?: (value: T) => void; + + toDispose: DisposableStore; + context?: WorkspaceTrustSettingsTreeEntry; + containerElement: HTMLElement; + labelElement: HTMLElement; + descriptionElement: HTMLElement; + controlElement: HTMLElement; + elementDisposables: DisposableStore; +} + +interface IWorkspaceTrustSettingListItemTemplate extends IWorkspaceTrustSettingItemTemplate { + listWidget: ListSettingWidget; + validationErrorMessageElement: HTMLElement; +} + +export interface IWorkspaceTrustSettingChangeEvent { + key: string; + value: any; // undefined => reset/unconfigure +} + + +export class WorkspaceTrustSettingArrayRenderer extends Disposable implements ITreeRenderer { + templateId = 'template.setting.array'; + + static readonly CONTROL_CLASS = 'setting-control-focus-target'; + static readonly CONTROL_SELECTOR = '.' + WorkspaceTrustSettingArrayRenderer.CONTROL_CLASS; + static readonly CONTENTS_CLASS = 'setting-item-contents'; + static readonly CONTENTS_SELECTOR = '.' + WorkspaceTrustSettingArrayRenderer.CONTENTS_CLASS; + static readonly ALL_ROWS_SELECTOR = '.monaco-list-row'; + + static readonly SETTING_KEY_ATTR = 'data-key'; + static readonly SETTING_ID_ATTR = 'data-id'; + static readonly ELEMENT_FOCUSABLE_ATTR = 'data-focusable'; + + protected readonly _onDidChangeSetting = this._register(new Emitter()); + readonly onDidChangeSetting: Event = this._onDidChangeSetting.event; + + private readonly _onDidFocusSetting = this._register(new Emitter()); + readonly onDidFocusSetting: Event = this._onDidFocusSetting.event; + + private readonly _onDidChangeIgnoredSettings = this._register(new Emitter()); + readonly onDidChangeIgnoredSettings: Event = this._onDidChangeIgnoredSettings.event; + + constructor( + @IThemeService protected readonly _themeService: IThemeService, + @IContextViewService protected readonly _contextViewService: IContextViewService, + @IOpenerService protected readonly _openerService: IOpenerService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @ICommandService protected readonly _commandService: ICommandService, + @IContextMenuService protected readonly _contextMenuService: IContextMenuService, + @IKeybindingService protected readonly _keybindingService: IKeybindingService, + @IConfigurationService protected readonly _configService: IConfigurationService, + ) { + super(); + } + + renderCommonTemplate(tree: any, _container: HTMLElement, typeClass: string): IWorkspaceTrustSettingItemTemplate { + _container.classList.add('setting-item'); + _container.classList.add('setting-item-' + typeClass); + + const container = append(_container, $(WorkspaceTrustSettingArrayRenderer.CONTENTS_SELECTOR)); + container.classList.add('settings-row-inner-container'); + const titleElement = append(container, $('.setting-item-title')); + const labelCategoryContainer = append(titleElement, $('.setting-item-cat-label-container')); + const labelElement = append(labelCategoryContainer, $('span.setting-item-label')); + const descriptionElement = append(container, $('.setting-item-description')); + const modifiedIndicatorElement = append(container, $('.setting-item-modified-indicator')); + modifiedIndicatorElement.title = localize('modified', "Modified"); + + const valueElement = append(container, $('.setting-item-value')); + const controlElement = append(valueElement, $('div.setting-item-control')); + const toDispose = new DisposableStore(); + + const template: IWorkspaceTrustSettingItemTemplate = { + toDispose, + elementDisposables: new DisposableStore(), + containerElement: container, + labelElement, + descriptionElement, + controlElement + }; + + // Prevent clicks from being handled by list + toDispose.add(addDisposableListener(controlElement, EventType.MOUSE_DOWN, e => e.stopPropagation())); + + toDispose.add(addDisposableListener(titleElement, EventType.MOUSE_ENTER, e => container.classList.add('mouseover'))); + toDispose.add(addDisposableListener(titleElement, EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover'))); + + return template; + } + + addSettingElementFocusHandler(template: IWorkspaceTrustSettingItemTemplate): void { + const focusTracker = trackFocus(template.containerElement); + template.toDispose.add(focusTracker); + focusTracker.onDidBlur(() => { + if (template.containerElement.classList.contains('focused')) { + template.containerElement.classList.remove('focused'); + } + }); + + focusTracker.onDidFocus(() => { + template.containerElement.classList.add('focused'); + + if (template.context) { + this._onDidFocusSetting.fire(template.context); + } + }); + } + + renderTemplate(container: HTMLElement): IWorkspaceTrustSettingListItemTemplate { + const common = this.renderCommonTemplate(null, container, 'list'); + const descriptionElement = common.containerElement.querySelector('.setting-item-description')!; + const validationErrorMessageElement = $('.setting-item-validation-message'); + descriptionElement.after(validationErrorMessageElement); + + const listWidget = this._instantiationService.createInstance(ListSettingWidget, common.controlElement); + listWidget.domNode.classList.add(WorkspaceTrustSettingArrayRenderer.CONTROL_CLASS); + common.toDispose.add(listWidget); + + const template: IWorkspaceTrustSettingListItemTemplate = { + ...common, + listWidget, + validationErrorMessageElement + }; + + this.addSettingElementFocusHandler(template); + + common.toDispose.add( + listWidget.onDidChangeList(e => { + const newList = this.computeNewList(template, e); + if (newList !== null && template.onChange) { + template.onChange(newList); + } + }) + ); + + return template; + } + + private computeNewList(template: IWorkspaceTrustSettingListItemTemplate, e: ISettingListChangeEvent): string[] | undefined | null { + if (template.context) { + let newValue: string[] = []; + if (isArray(template.context.value)) { + newValue = [...template.context.value]; + } + + if (e.targetIndex !== undefined) { + // Delete value + if (!e.item?.value && e.originalItem.value && e.targetIndex > -1) { + newValue.splice(e.targetIndex, 1); + } + // Update value + else if (e.item?.value && e.originalItem.value) { + if (e.targetIndex > -1) { + newValue[e.targetIndex] = e.item.value; + } + // For some reason, we are updating and cannot find original value + // Just append the value in this case + else { + newValue.push(e.item.value); + } + } + // Add value + else if (e.item?.value && !e.originalItem.value && e.targetIndex >= newValue.length) { + newValue.push(e.item.value); + } + } + + return newValue; + } + + return undefined; + } + + renderElement(node: ITreeNode, index: number, template: IWorkspaceTrustSettingListItemTemplate): void { + const element = node.element; + template.context = element; + + template.containerElement.setAttribute(WorkspaceTrustSettingArrayRenderer.SETTING_KEY_ATTR, element.setting.key); + template.containerElement.setAttribute(WorkspaceTrustSettingArrayRenderer.SETTING_ID_ATTR, element.id); + + template.labelElement.textContent = element.displayLabel; + + template.descriptionElement.innerText = element.setting.description; + + const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value }); + this.renderValue(element, template, onChange); + } + + protected renderValue(dataElement: WorkspaceTrustSettingsTreeEntry, template: IWorkspaceTrustSettingListItemTemplate, onChange: (value: string[] | undefined) => void): void { + const value = getListDisplayValue(dataElement); + template.listWidget.setValue(value); + template.context = dataElement; + + template.onChange = (v) => { + onChange(v); + renderArrayValidations(dataElement, template, v, false); + }; + + renderArrayValidations(dataElement, template, value.map(v => v.value), true); + } + + disposeTemplate(template: IWorkspaceTrustSettingItemTemplate): void { + dispose(template.toDispose); + } + + disposeElement(_element: ITreeNode, _index: number, template: IWorkspaceTrustSettingItemTemplate, _height: number | undefined): void { + if (template.elementDisposables) { + template.elementDisposables.clear(); + } + } +} + +export class WorkspaceTrustTree extends WorkbenchObjectTree { + constructor( + container: HTMLElement, + renderers: ITreeRenderer[], + @IContextKeyService contextKeyService: IContextKeyService, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super('WorkspaceTrustTree', container, + new WorkspaceTrustTreeDelegate(), + renderers, + { + horizontalScrolling: false, + supportDynamicHeights: true, + identityProvider: { + getId(e) { + return e.id; + } + }, + accessibilityProvider: new WorkspaceTrustTreeAccessibilityProvider(), + styleController: id => new DefaultStyleController(createStyleSheet(container), id), + smoothScrolling: configurationService.getValue('workbench.list.smoothScrolling'), + multipleSelectionSupport: false, + }, + contextKeyService, + listService, + themeService, + configurationService, + keybindingService, + accessibilityService, + ); + + this.disposables.add(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + const foregroundColor = theme.getColor(foreground); + if (foregroundColor) { + // Links appear inside other elements in markdown. CSS opacity acts like a mask. So we have to dynamically compute the description color to avoid + // applying an opacity to the link color. + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.9)); + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-description { color: ${fgWithOpacity}; }`); + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings .settings-toc-container .monaco-list-row:not(.selected) { color: ${fgWithOpacity}; }`); + + // Hack for subpixel antialiasing + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides, + .workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored { color: ${fgWithOpacity}; }`); + } + + const errorColor = theme.getColor(errorForeground); + if (errorColor) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-deprecation-message { color: ${errorColor}; }`); + } + + const invalidInputBackground = theme.getColor(inputValidationErrorBackground); + if (invalidInputBackground) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-validation-message { background-color: ${invalidInputBackground}; }`); + } + + const invalidInputForeground = theme.getColor(inputValidationErrorForeground); + if (invalidInputForeground) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-validation-message { color: ${invalidInputForeground}; }`); + } + + const invalidInputBorder = theme.getColor(inputValidationErrorBorder); + if (invalidInputBorder) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-validation-message { border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item.invalid-input .setting-item-control .monaco-inputbox.idle { outline-width: 0; border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); + } + + const focusedRowBackgroundColor = theme.getColor(focusedRowBackground); + if (focusedRowBackgroundColor) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .monaco-list-row.focused .settings-row-inner-container { background-color: ${focusedRowBackgroundColor}; }`); + } + + const rowHoverBackgroundColor = theme.getColor(rowHoverBackground); + if (rowHoverBackgroundColor) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .monaco-list-row:not(.focused) .settings-row-inner-container:hover { background-color: ${rowHoverBackgroundColor}; }`); + } + + const focusedRowBorderColor = theme.getColor(focusedRowBorder); + if (focusedRowBorderColor) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .monaco-list:focus-within .monaco-list-row.focused .setting-item-contents::before, + .workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .monaco-list:focus-within .monaco-list-row.focused .setting-item-contents::after { border-top: 1px solid ${focusedRowBorderColor} }`); + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .monaco-list:focus-within .monaco-list-row.focused .settings-group-title-label::before, + .workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .monaco-list:focus-within .monaco-list-row.focused .settings-group-title-label::after { border-top: 1px solid ${focusedRowBorderColor} }`); + } + + const headerForegroundColor = theme.getColor(settingsHeaderForeground); + if (headerForegroundColor) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .settings-group-title-label { color: ${headerForegroundColor}; }`); + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-label { color: ${headerForegroundColor}; }`); + } + + const focusBorderColor = theme.getColor(focusBorder); + if (focusBorderColor) { + collector.addRule(`.workspace-trust-editor > .workspace-trust-settings > .workspace-trust-settings-tree-container .setting-item-contents .setting-item-markdown a:focus { outline-color: ${focusBorderColor} }`); + } + })); + + this.getHTMLElement().classList.add('settings-editor-tree'); + + this.disposables.add(attachStyler(themeService, { + listBackground: editorBackground, + listActiveSelectionBackground: editorBackground, + listActiveSelectionForeground: foreground, + listFocusAndSelectionBackground: editorBackground, + listFocusAndSelectionForeground: foreground, + listFocusBackground: editorBackground, + listFocusForeground: foreground, + listHoverForeground: foreground, + listHoverBackground: editorBackground, + listHoverOutline: editorBackground, + listFocusOutline: editorBackground, + listInactiveSelectionBackground: editorBackground, + listInactiveSelectionForeground: foreground, + listInactiveFocusBackground: editorBackground, + listInactiveFocusOutline: editorBackground + }, colors => { + this.style(colors); + })); + + this.disposables.add(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.list.smoothScrolling')) { + this.updateOptions({ + smoothScrolling: configurationService.getValue('workbench.list.smoothScrolling') + }); + } + })); + } + + protected createModel(user: string, view: IList>, options: IObjectTreeOptions): ITreeModel { + return new NonCollapsibleObjectTreeModel(user, view, options); + } +} + +export class WorkspaceTrustTreeModel { + + settings: WorkspaceTrustSettingsTreeEntry[] = []; + + update(trustInfo: IWorkspaceTrustStateInfo): void { + this.settings = []; + if (trustInfo.localFolders) { + const trustedFolders = trustInfo.localFolders.filter(folder => folder.trustState === WorkspaceTrustState.Trusted).map(folder => folder.uri); + const untrustedFolders = trustInfo.localFolders.filter(folder => folder.trustState === WorkspaceTrustState.Untrusted).map(folder => folder.uri); + + this.settings.push(new WorkspaceTrustSettingsTreeEntry( + 'trustedFolders', + localize('trustedFolders', "Trusted Folders"), + localize('trustedFoldersDescription', "All workspaces under the following folders will be trusted. In the event of a conflict with untrusted folders, the nearest parent will determine trust."), + trustedFolders)); + + this.settings.push(new WorkspaceTrustSettingsTreeEntry( + 'untrustedFolders', + localize('untrustedFolders', "Untrusted Folders"), + localize('untrustedFoldersDescription', "All workspaces under the following folders will not be trusted. In the event of a conflict with trusted folders, the nearest parent will determine trust."), + untrustedFolders)); + } + } +} + +class WorkspaceTrustTreeAccessibilityProvider implements IListAccessibilityProvider { + getAriaLabel(element: WorkspaceTrustSettingsTreeEntry) { + if (element instanceof WorkspaceTrustSettingsTreeEntry) { + return `element.displayLabel`; + } + + return null; + } + + getWidgetAriaLabel() { + return localize('settings', "Workspace Trust Setting"); + } +} + +class WorkspaceTrustTreeDelegate extends CachedListVirtualDelegate { + + getTemplateId(element: WorkspaceTrustSettingsTreeEntry): string { + return 'template.setting.array'; + } + + hasDynamicHeight(element: WorkspaceTrustSettingsTreeEntry): boolean { + return true; + } + + protected estimateHeight(element: WorkspaceTrustSettingsTreeEntry): number { + return 104; + } +} + +function getListDisplayValue(element: WorkspaceTrustSettingsTreeEntry): IListDataItem[] { + if (!element.value || !isArray(element.value)) { + return []; + } + + return element.value.map((key: string) => { + return { + value: key + }; + }); +} + +function renderArrayValidations(dataElement: WorkspaceTrustSettingsTreeEntry, template: IWorkspaceTrustSettingListItemTemplate, v: string[] | undefined, arg3: boolean) { +} + diff --git a/src/vs/workbench/contrib/workspace/common/workspaceTrustFileSystemProvider.ts b/src/vs/workbench/contrib/workspace/common/workspaceTrustFileSystemProvider.ts deleted file mode 100644 index 4c4c49f9480..00000000000 --- a/src/vs/workbench/contrib/workspace/common/workspaceTrustFileSystemProvider.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IStat, IWatchOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { WORKSPACE_TRUST_STORAGE_KEY } from 'vs/workbench/services/workspaces/common/workspaceTrust'; - -const WORKSPACE_TRUST_SCHEMA = 'workspaceTrust'; - -const TRUSTED_WORKSPACES_STAT: IStat = { - type: FileType.File, - ctime: Date.now(), - mtime: Date.now(), - size: 0 -}; - -const PREPENDED_TEXT = `// The following file is a placeholder UX for managing trusted workspaces. It will be replaced by a rich editor and provide -// additonal information about what trust means. e.g. enabling trust will unblock automatic tasks on startup and list the tasks. It will enable certain extensions -// and list the extensions with associated functionality. -`; - -export class WorkspaceTrustFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IWorkbenchContribution { - readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; - - readonly onDidChangeCapabilities = Event.None; - readonly onDidChangeFile = Event.None; - - constructor( - @IFileService private readonly fileService: IFileService, - @IStorageService private readonly storageService: IStorageService, - ) { - this.fileService.registerProvider(WORKSPACE_TRUST_SCHEMA, this); - } - - stat(resource: URI): Promise { - return Promise.resolve(TRUSTED_WORKSPACES_STAT); - } - - async readFile(resource: URI): Promise { - let workspacesTrustContent = this.storageService.get(WORKSPACE_TRUST_STORAGE_KEY, StorageScope.GLOBAL); - - let objectForm = {}; - try { - objectForm = JSON.parse(workspacesTrustContent || '{}'); - } catch { } - - const buffer = VSBuffer.fromString(PREPENDED_TEXT + JSON.stringify(objectForm, undefined, 2)).buffer; - return buffer; - } - - writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { - try { - const workspacesTrustContent = VSBuffer.wrap(content).toString().replace(PREPENDED_TEXT, ''); - this.storageService.store(WORKSPACE_TRUST_STORAGE_KEY, workspacesTrustContent, StorageScope.GLOBAL, StorageTarget.MACHINE); - } catch (err) { } - - return Promise.resolve(); - } - - watch(resource: URI, opts: IWatchOptions): IDisposable { - return { - dispose() { - return; - } - }; - } - mkdir(resource: URI): Promise { - return Promise.resolve(undefined!); - } - readdir(resource: URI): Promise<[string, FileType][]> { - return Promise.resolve(undefined!); - } - delete(resource: URI, opts: FileDeleteOptions): Promise { - return Promise.resolve(undefined!); - } - rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { - return Promise.resolve(undefined!); - } -} diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 103816e95c0..8ffa5670f52 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -52,8 +52,8 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; -import { LoggerService } from 'vs/workbench/services/log/electron-sandbox/loggerService'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; class DesktopMain extends Disposable { @@ -170,7 +170,7 @@ class DesktopMain extends Disposable { serviceCollection.set(IProductService, this.productService); // Logger - const loggerService = new LoggerService(mainProcessService); + const loggerService = new LoggerChannelClient(mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); // Log @@ -329,11 +329,11 @@ class DesktopMain extends Disposable { storageService = new NativeStorageService2(payload, mainProcessService, this.environmentService); } else { const storageDataBaseClient = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), payload); - storageService = new NativeStorageService(storageDataBaseClient.globalStorage, logService, this.environmentService); + storageService = new NativeStorageService(storageDataBaseClient.globalStorage, payload, logService, this.environmentService); } try { - await storageService.initialize(payload); + await storageService.initialize(); return storageService; } catch (error) { diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index f153e787029..c6567c33e06 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/actions'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { applyZoom } from 'vs/platform/windows/electron-sandbox/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getZoomLevel } from 'vs/base/browser/browser'; @@ -25,7 +25,7 @@ import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/pla export class CloseCurrentWindowAction extends Action { static readonly ID = 'workbench.action.closeWindow'; - static readonly LABEL = nls.localize('closeWindow', "Close Window"); + static readonly LABEL = localize('closeWindow', "Close Window"); constructor( id: string, @@ -71,7 +71,7 @@ export abstract class BaseZoomAction extends Action { export class ZoomInAction extends BaseZoomAction { static readonly ID = 'workbench.action.zoomIn'; - static readonly LABEL = nls.localize('zoomIn', "Zoom In"); + static readonly LABEL = localize('zoomIn', "Zoom In"); constructor( id: string, @@ -89,7 +89,7 @@ export class ZoomInAction extends BaseZoomAction { export class ZoomOutAction extends BaseZoomAction { static readonly ID = 'workbench.action.zoomOut'; - static readonly LABEL = nls.localize('zoomOut', "Zoom Out"); + static readonly LABEL = localize('zoomOut', "Zoom Out"); constructor( id: string, @@ -107,7 +107,7 @@ export class ZoomOutAction extends BaseZoomAction { export class ZoomResetAction extends BaseZoomAction { static readonly ID = 'workbench.action.zoomReset'; - static readonly LABEL = nls.localize('zoomReset', "Reset Zoom"); + static readonly LABEL = localize('zoomReset', "Reset Zoom"); constructor( id: string, @@ -126,12 +126,12 @@ export abstract class BaseSwitchWindow extends Action { private readonly closeWindowAction: IQuickInputButton = { iconClass: Codicon.removeClose.classNames, - tooltip: nls.localize('close', "Close Window") + tooltip: localize('close', "Close Window") }; private readonly closeDirtyWindowAction: IQuickInputButton = { iconClass: 'dirty-window ' + Codicon.closeDirty, - tooltip: nls.localize('close', "Close Window"), + tooltip: localize('close', "Close Window"), alwaysVisible: true }; @@ -153,16 +153,16 @@ export abstract class BaseSwitchWindow extends Action { const currentWindowId = this.nativeHostService.windowId; const windows = await this.nativeHostService.getWindows(); - const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to"); + const placeHolder = localize('switchWindowPlaceHolder', "Select a window to switch to"); const picks = windows.map(window => { const resource = window.filename ? URI.file(window.filename) : isSingleFolderWorkspaceIdentifier(window.workspace) ? window.workspace.uri : isWorkspaceIdentifier(window.workspace) ? window.workspace.configPath : undefined; const fileKind = window.filename ? FileKind.FILE : isSingleFolderWorkspaceIdentifier(window.workspace) ? FileKind.FOLDER : isWorkspaceIdentifier(window.workspace) ? FileKind.ROOT_FOLDER : FileKind.FILE; return { payload: window.id, label: window.title, - ariaLabel: window.dirty ? nls.localize('windowDirtyAriaLabel', "{0}, dirty window", window.title) : window.title, + ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, dirty window", window.title) : window.title, iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind), - description: (currentWindowId === window.id) ? nls.localize('current', "Current Window") : undefined, + description: (currentWindowId === window.id) ? localize('current', "Current Window") : undefined, buttons: currentWindowId !== window.id ? window.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined }; }); @@ -188,7 +188,7 @@ export abstract class BaseSwitchWindow extends Action { export class SwitchWindow extends BaseSwitchWindow { static readonly ID = 'workbench.action.switchWindow'; - static readonly LABEL = nls.localize('switchWindow', "Switch Window..."); + static readonly LABEL = localize('switchWindow', "Switch Window..."); constructor( id: string, @@ -210,7 +210,7 @@ export class SwitchWindow extends BaseSwitchWindow { export class QuickSwitchWindow extends BaseSwitchWindow { static readonly ID = 'workbench.action.quickSwitchWindow'; - static readonly LABEL = nls.localize('quickSwitchWindow', "Quick Switch Window..."); + static readonly LABEL = localize('quickSwitchWindow', "Quick Switch Window..."); constructor( id: string, diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 709c3ab30c1..7b045ab3243 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -35,16 +35,15 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { basename } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; -import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; -import { LoggerService } from 'vs/workbench/services/log/electron-sandbox/loggerService'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { SimpleConfigurationService, simpleFileSystemProvider, SimpleSignService, SimpleNativeWorkbenchEnvironmentService, SimpleWorkspaceService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; +import { SimpleConfigurationService, simpleFileSystemProvider, SimpleSignService, SimpleNativeWorkbenchEnvironmentService, SimpleWorkspaceService, SimpleLogService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; class DesktopMain extends Disposable { @@ -153,11 +152,11 @@ class DesktopMain extends Disposable { serviceCollection.set(IProductService, this.productService); // Logger - const loggerService = new LoggerService(mainProcessService); + const loggerService = new LoggerChannelClient(mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); // Log - const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, loggerService, mainProcessService, this.environmentService)); + const logService = new SimpleLogService(); serviceCollection.set(ILogService, logService); // Remote diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts index 6e9b7b93f98..3805a175897 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { fromNow } from 'vs/base/common/date'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { isLinux, isLinuxSnap, isWindows } from 'vs/base/common/platform'; @@ -58,13 +58,13 @@ export class NativeDialogHandler implements IDialogHandler { if (confirmation.primaryButton) { buttons.push(confirmation.primaryButton); } else { - buttons.push(nls.localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes")); + buttons.push(localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes")); } if (confirmation.secondaryButton) { buttons.push(confirmation.secondaryButton); } else if (typeof confirmation.secondaryButton === 'undefined') { - buttons.push(nls.localize('cancelButton', "Cancel")); + buttons.push(localize('cancelButton', "Cancel")); } const opts: MessageBoxOptions = { @@ -166,7 +166,7 @@ export class NativeDialogHandler implements IDialogHandler { const osProps = await this.nativeHostService.getOSProperties(); const detailString = (useAgo: boolean): string => { - return nls.localize({ key: 'aboutDetail', comment: ['Electron, Chrome, Node.js and V8 are product names that need no translation'] }, + return localize({ key: 'aboutDetail', comment: ['Electron, Chrome, Node.js and V8 are product names that need no translation'] }, "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", version, this.productService.commit || 'Unknown', @@ -182,8 +182,8 @@ export class NativeDialogHandler implements IDialogHandler { const detail = detailString(true); const detailToCopy = detailString(false); - const ok = nls.localize('okButton', "OK"); - const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); + const ok = localize('okButton', "OK"); + const copy = mnemonicButtonLabel(localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); let buttons: string[]; if (isLinux) { buttons = [copy, ok]; diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index c2258de1123..10a7e668c9d 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { getZoomFactor } from 'vs/base/browser/browser'; -import * as DOM from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, Dimension, EventType, hide, prepend, runAtThisOrScheduleAtNextAnimationFrame, show } from 'vs/base/browser/dom'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -86,9 +86,9 @@ export class TitlebarPart extends BrowserTitleBarPart { if (this.resizer) { if (maximized) { - DOM.hide(this.resizer); + hide(this.resizer); } else { - DOM.show(this.resizer); + show(this.resizer); } } @@ -98,9 +98,9 @@ export class TitlebarPart extends BrowserTitleBarPart { private onMenubarFocusChanged(focused: boolean) { if ((isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { if (focused) { - DOM.hide(this.dragRegion); + hide(this.dragRegion); } else { - DOM.show(this.dragRegion); + show(this.dragRegion); } } } @@ -110,8 +110,8 @@ export class TitlebarPart extends BrowserTitleBarPart { if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) { // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor if (this.dragRegion) { - DOM.hide(this.dragRegion); - setTimeout(() => DOM.show(this.dragRegion!), 50); + hide(this.dragRegion); + setTimeout(() => show(this.dragRegion!), 50); } } @@ -173,27 +173,27 @@ export class TitlebarPart extends BrowserTitleBarPart { if (this.appIcon) { this.onUpdateAppIconDragBehavior(); - this._register(DOM.addDisposableListener(this.appIcon, DOM.EventType.DBLCLICK, (e => { + this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => { this.nativeHostService.closeWindow(); }))); } // Draggable region that we can manipulate for #52522 - this.dragRegion = DOM.prepend(this.element, DOM.$('div.titlebar-drag-region')); + this.dragRegion = prepend(this.element, $('div.titlebar-drag-region')); // Window Controls (Native Windows/Linux) if (!isMacintosh) { - this.windowControls = DOM.append(this.element, DOM.$('div.window-controls-container')); + this.windowControls = append(this.element, $('div.window-controls-container')); // Minimize - const minimizeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-minimize' + Codicon.chromeMinimize.cssSelector)); - this._register(DOM.addDisposableListener(minimizeIcon, DOM.EventType.CLICK, e => { + const minimizeIcon = append(this.windowControls, $('div.window-icon.window-minimize' + Codicon.chromeMinimize.cssSelector)); + this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { this.nativeHostService.minimizeWindow(); })); // Restore - this.maxRestoreControl = DOM.append(this.windowControls, DOM.$('div.window-icon.window-max-restore')); - this._register(DOM.addDisposableListener(this.maxRestoreControl, DOM.EventType.CLICK, async e => { + this.maxRestoreControl = append(this.windowControls, $('div.window-icon.window-max-restore')); + this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => { const maximized = await this.nativeHostService.isMaximized(); if (maximized) { return this.nativeHostService.unmaximizeWindow(); @@ -203,13 +203,13 @@ export class TitlebarPart extends BrowserTitleBarPart { })); // Close - const closeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-close' + Codicon.chromeClose.cssSelector)); - this._register(DOM.addDisposableListener(closeIcon, DOM.EventType.CLICK, e => { + const closeIcon = append(this.windowControls, $('div.window-icon.window-close' + Codicon.chromeClose.cssSelector)); + this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { this.nativeHostService.closeWindow(); })); // Resizer - this.resizer = DOM.append(this.element, DOM.$('div.resizer')); + this.resizer = append(this.element, $('div.resizer')); this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); @@ -218,7 +218,7 @@ export class TitlebarPart extends BrowserTitleBarPart { return ret; } - updateLayout(dimension: DOM.Dimension): void { + updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; if (getTitleBarStyle(this.configurationService) === 'custom') { @@ -247,10 +247,10 @@ export class TitlebarPart extends BrowserTitleBarPart { } } - DOM.runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); + runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); if (this.customMenubar) { - const menubarDimension = new DOM.Dimension(0, dimension.height); + const menubarDimension = new Dimension(0, dimension.height); this.customMenubar.layout(menubarDimension); } } diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index 7b7312d9575..fb480250176 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -51,6 +51,7 @@ import { BrowserKeyboardLayoutService } from 'vs/workbench/services/keybinding/b import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { ConsoleLogger, LogService } from 'vs/platform/log/common/log'; //#region Environment @@ -195,6 +196,16 @@ export class SimpleSignService implements ISignService { //#endregion +//#region Logger + +export class SimpleLogService extends LogService { + + constructor() { + super(new ConsoleLogger()); + } + +} + //#region Files diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 6c1efc704be..dfd68dd6ff9 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -108,7 +108,8 @@ export interface IAuthenticationService { getProviderIds(): string[]; registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void; unregisterAuthenticationProvider(id: string): void; - isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean; + isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined; + updatedAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): Promise; showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise; selectSession(providerId: string, extensionId: string, extensionName: string, possibleSessions: readonly AuthenticationSession[]): Promise; requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: readonly AuthenticationSession[]): void; @@ -137,6 +138,7 @@ export interface IAuthenticationService { export interface AllowedExtension { id: string; name: string; + allowed?: boolean; } export function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { @@ -396,11 +398,22 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean { + /** + * Check extension access to an account + * @param providerId The id of the authentication provider + * @param accountName The account name that access is checked for + * @param extensionId The id of the extension requesting access + * @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined + * if they haven't made a choice yet + */ + isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { const allowList = readAllowedExtensions(this.storageService, providerId, accountName); const extensionData = allowList.find(extension => extension.id === extensionId); if (extensionData) { - return true; + // This property didn't exist on this data previously, inclusion in the list at all indicates allowance + return extensionData.allowed !== undefined + ? extensionData.allowed + : true; } const remoteConnection = this.remoteAgentService.getConnection(); @@ -412,7 +425,19 @@ export class AuthenticationService extends Disposable implements IAuthentication return true; } - return false; + return undefined; + } + + async updatedAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): Promise { + const allowList = readAllowedExtensions(this.storageService, providerId, accountName); + const index = allowList.findIndex(extension => extension.id === extensionId); + if (index === -1) { + allowList.push({ id: extensionId, name: extensionName, allowed: isAllowed }); + } else { + allowList[index].allowed = isAllowed; + } + + await this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); } async showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { @@ -420,21 +445,20 @@ export class AuthenticationService extends Disposable implements IAuthentication const { choice } = await this.dialogService.show( Severity.Info, nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName), - [nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")], + [nls.localize('allow', "Allow"), nls.localize('deny', "Deny"), nls.localize('cancel', "Cancel")], { - cancelId: 1 + cancelId: 2 } ); - const allow = choice === 0; - if (allow) { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); + const cancelled = choice === 2; + const allowed = choice === 0; + if (!cancelled) { + this.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, allowed); } this.removeAccessRequest(providerId, extensionId); - return allow; + return allowed; } async selectSession(providerId: string, extensionId: string, extensionName: string, availableSessions: AuthenticationSession[]): Promise { @@ -475,11 +499,7 @@ export class AuthenticationService extends Disposable implements IAuthentication const session = quickPick.selectedItems[0].session ?? await this.createSession(providerId, availableSessions[0].scopes as string[]); const accountName = session.account.label; - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - if (!allowList.find(allowed => allowed.id === extensionId)) { - allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); - } + this.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true); this.removeAccessRequest(providerId, extensionId); this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); @@ -612,11 +632,7 @@ export class AuthenticationService extends Disposable implements IAuthentication const session = await authenticationService.createSession(providerId, scopes); // Add extension to allow list since user explicitly signed in on behalf of it - const allowList = readAllowedExtensions(storageService, providerId, session.account.label); - if (!allowList.find(allowed => allowed.id === extensionId)) { - allowList.push({ id: extensionId, name: extensionName }); - storageService.store(`${providerId}-${session.account.label}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); - } + this.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); // And also set it as the preferred account for the extension storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 04f37bd3a1a..91d485e0233 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -22,6 +22,7 @@ import { IConfigurationModel } from 'vs/platform/configuration/common/configurat import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { hash } from 'vs/base/common/hash'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { ILogService } from 'vs/platform/log/common/log'; export class UserConfiguration extends Disposable { @@ -38,6 +39,7 @@ export class UserConfiguration extends Disposable { private readonly scopes: ConfigurationScope[] | undefined, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, + private readonly logService: ILogService, ) { super(); this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, uriIdentityService.extUri, this.fileService); @@ -56,7 +58,7 @@ export class UserConfiguration extends Disposable { const folder = this.uriIdentityService.extUri.dirname(this.userSettingsResource); const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, this.uriIdentityService.extUri.joinPath(folder, `${name}.json`)])); - const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService, this.uriIdentityService); + const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService, this.uriIdentityService, this.logService); const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(); this.userConfiguration.value = fileServiceBasedConfiguration; @@ -89,8 +91,9 @@ class FileServiceBasedConfiguration extends Disposable { private readonly settingsResources: URI[], private readonly standAloneConfigurationResources: [string, URI][], private readonly scopes: ConfigurationScope[] | undefined, - private fileService: IFileService, - private uriIdentityService: IUriIdentityService + private readonly fileService: IFileService, + private readonly uriIdentityService: IUriIdentityService, + private readonly logService: ILogService, ) { super(); this.allResources = [...this.settingsResources, ...this.standAloneConfigurationResources.map(([, resource]) => resource)]; @@ -107,9 +110,13 @@ class FileServiceBasedConfiguration extends Disposable { const resolveContents = async (resources: URI[]): Promise<(string | undefined)[]> => { return Promise.all(resources.map(async resource => { try { - const content = await this.fileService.readFile(resource); - return content.value.toString(); + const content = (await this.fileService.readFile(resource)).value.toString(); + if (!content) { + this.logService.debug(`Configuration file '${resource.toString()}' is empty`); + } + return content; } catch (error) { + this.logService.trace(`Error while resolving configuration file '${resource.toString()}': ${errors.getErrorMessage(error)}`); if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND && (error).fileOperationResult !== FileOperationResult.FILE_NOT_DIRECTORY) { errors.onUnexpectedError(error); @@ -694,6 +701,7 @@ export class FolderConfiguration extends Disposable { private readonly workbenchState: WorkbenchState, fileService: IFileService, uriIdentityService: IUriIdentityService, + logService: ILogService, private readonly configurationCache: IConfigurationCache ) { super(); @@ -704,12 +712,12 @@ export class FolderConfiguration extends Disposable { this.folderConfiguration = this.cachedFolderConfiguration; whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { - this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService)); + this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService)); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); this.onDidFolderConfigurationChange(); }); } else { - this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService)); + this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService)); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); } } @@ -727,10 +735,10 @@ export class FolderConfiguration extends Disposable { this._onDidChange.fire(); } - private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService) { + private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) { const settingsResources = [uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`)]; const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)])); - return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService, uriIdentityService); + return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService, uriIdentityService, logService); } private updateCache(): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index fa4552e36f9..a21ce8f01ec 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -12,7 +12,7 @@ import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -104,7 +104,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.logService = logService; this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); - this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService)); + this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService, logService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); if (remoteAuthority) { const remoteUserConfiguration = this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, fileService, uriIdentityService, remoteAgentService)); @@ -693,7 +693,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return Promise.all([...folders.map(folder => { let folderConfiguration = this.cachedFolderConfigs.get(folder.uri); if (!folderConfiguration) { - folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.uriIdentityService, this.configurationCache); + folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.uriIdentityService, this.logService, this.configurationCache); this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder))); this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); } @@ -796,6 +796,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private triggerConfigurationChange(change: IConfigurationChange, previous: { data: IConfigurationData, workspace?: Workspace } | undefined, target: ConfigurationTarget): void { if (change.keys.length) { + if (target !== ConfigurationTarget.DEFAULT) { + this.logService.debug(`Configuration keys changed in ${ConfigurationTargetToString(target)} target`, ...change.keys); + } const configurationChangeEvent = new ConfigurationChangeEvent(change, previous, this._configuration, this.workspace); configurationChangeEvent.source = target; configurationChangeEvent.sourceConfig = this.getTargetConfiguration(target); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index b99e476e2df..2d1a99a455c 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane } from 'vs/workbench/common/editor'; @@ -1182,7 +1182,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { infos.forEach(info => { enumValues.push(info.id); - enumDescriptions.push(nls.localize('editorAssociations.viewType.sourceDescription', "Source: {0}", info.providerDisplayName)); + enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", info.providerDisplayName)); }); updateViewTypeSchema(enumValues, enumDescriptions); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index c8742070a12..94f39dc0dbf 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -21,7 +21,7 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; -import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension, ExtensionKind } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension, ExtensionKind, IExtensionContributions } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -535,14 +535,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx }); } - public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { + public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { return this._installedExtensionsReady.wait().then(() => { const availableExtensions = this._registry.getAllExtensionDescriptions(); const result: ExtensionPointContribution[] = []; for (const desc of availableExtensions) { if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { - result.push(new ExtensionPointContribution(desc, desc.contributes[extPoint.name as keyof typeof desc.contributes])); + result.push(new ExtensionPointContribution(desc, desc.contributes[extPoint.name as keyof typeof desc.contributes] as T)); } } @@ -691,13 +691,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { + private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { const users: IExtensionPointUser[] = []; for (const desc of availableExtensions) { if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { users.push({ description: desc, - value: desc.contributes[extensionPoint.name as keyof typeof desc.contributes], + value: desc.contributes[extensionPoint.name as keyof typeof desc.contributes] as T, collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) }); } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 33883bedb25..ed224cb6eca 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -8,7 +8,7 @@ import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, IExtensionContributions } from 'vs/platform/extensions/common/extensions'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; @@ -224,7 +224,7 @@ export interface IExtensionService { /** * Read all contributions to an extension point. */ - readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]>; + readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]>; /** * Get information about extensions status. diff --git a/src/vs/workbench/services/gettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/services/gettingStarted/common/gettingStartedContent.ts index 64e10c2eb92..01ee03ee851 100644 --- a/src/vs/workbench/services/gettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/services/gettingStarted/common/gettingStartedContent.ts @@ -270,5 +270,4 @@ export const content: GettingStartedContent = [ ] } } - ]; diff --git a/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts b/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts index c6f32091b3e..b8a4867d9d0 100644 --- a/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts +++ b/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts @@ -7,9 +7,11 @@ import { Emitter, Event } from 'vs/base/common/event'; import { FileAccess } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { content } from 'vs/workbench/services/gettingStarted/common/gettingStartedContent'; +import { localize } from 'vs/nls'; export const enum GettingStartedCategory { Beginner = 'Beginner', @@ -35,7 +37,9 @@ export interface IGettingStartedCategoryDescriptor { id: GettingStartedCategory | string title: string description: string - icon: ThemeIcon + icon: + | { type: 'icon', icon: ThemeIcon } + | { type: 'image', path: string } when: ContextKeyExpression content: | { type: 'items' } @@ -46,7 +50,9 @@ export interface IGettingStartedCategory { id: GettingStartedCategory | string title: string description: string - icon: ThemeIcon + icon: + | { type: 'icon', icon: ThemeIcon } + | { type: 'image', path: string } when: ContextKeyExpression content: | { type: 'items', items: IGettingStartedTask[] } @@ -127,6 +133,7 @@ content.forEach(category => { registryImpl.registerCategory({ ...category, + icon: { type: 'icon', icon: category.icon }, when: ContextKeyExpr.deserialize(category.when) ?? ContextKeyExpr.true() }); @@ -165,3 +172,96 @@ content.forEach(category => { Registry.add(GettingStartedRegistryID, registryImpl); export const GettingStartedRegistry: IGettingStartedRegistry = Registry.as(GettingStartedRegistryID); + +ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'gettingStarted', + jsonSchema: { + doNotSuggest: true, + description: localize('gettingStarted', "Contribute items to help users in getting started with your extension. Rendering and progression through these items is managed by core. Experimental, requires proposedApi."), + type: 'array', + items: { + type: 'object', + required: ['id', 'title', 'description', 'button', 'media'], + properties: { + id: { + type: 'string', + description: localize('gettingStarted.id', "Unique identifier for this item."), + }, + title: { + type: 'string', + description: localize('gettingStarted.title', "Title of item.") + }, + description: { + type: 'string', + description: localize('gettingStarted.description', "Description of item.") + }, + button: { + description: localize('gettingStarted.button', "The item's button, which can either link to an external resource or run a command"), + oneOf: [ + { + type: 'object', + required: ['title', 'command'], + properties: { + title: { + type: 'string', + description: localize('gettingStarted.button.title', "Title of button.") + }, + command: { + type: 'string', + description: localize('gettingStarted.button.command', "Command to run when button is clicked. Running this command will mark the item completed.") + } + } + }, + { + type: 'object', + required: ['title', 'link'], + properties: { + title: { + type: 'string', + description: localize('gettingStarted.button.title', "Title of button.") + }, + link: { + type: 'string', + description: localize('gettingStarted.button.link', "Link to open when button is clicked. Opening this link will mark the item completed.") + } + } + } + ] + }, + media: { + type: 'object', + required: ['path', 'altText'], + description: localize('gettingStarted.media', "Image to show alongside this item."), + properties: { + path: { + description: localize('gettingStarted.media.path', "Either a single string path to an image to be used on all color themes, or separate paths for light, dark, and high contrast themes."), + + oneOf: [ + { + type: 'string', + }, + { + type: 'object', + required: ['hc', 'light', 'dark'], + properties: { + hc: { type: 'string' }, + light: { type: 'string' }, + dark: { type: 'string' }, + } + }, + ] + }, + altText: { + type: 'string', + description: localize('gettingStarted.media.altText', "Alternate text to display when the image cannot be loaded or in screen readers.") + } + } + }, + when: { + type: 'string', + description: localize('gettingStarted.when', "Context key expression to control the visibility of this getting started item.") + } + } + } + } +}); diff --git a/src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts b/src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts index b5784ee9ddb..fbf2f212572 100644 --- a/src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts +++ b/src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts @@ -11,9 +11,16 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { Memento } from 'vs/workbench/common/memento'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Disposable } from 'vs/base/common/lifecycle'; import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { FileAccess } from 'vs/base/common/network'; +import { localize } from 'vs/nls'; +import { DefaultIconPath } from 'vs/platform/extensionManagement/common/extensionManagement'; export const IGettingStartedService = createDecorator('gettingStartedService'); @@ -63,11 +70,14 @@ export class GettingStartedService extends Disposable implements IGettingStarted private commandListeners = new Map(); private eventListeners = new Map(); + private trackedExtensions = new Set(); + constructor( @IStorageService private readonly storageService: IStorageService, @ICommandService private readonly commandService: ICommandService, @IContextKeyService private readonly contextService: IContextKeyService, @IUserDataAutoSyncEnablementService readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, + @IExtensionService private readonly extensionService: IExtensionService, ) { super(); @@ -80,7 +90,20 @@ export class GettingStartedService extends Disposable implements IGettingStarted } }); - this._register(this.registry.onDidAddCategory(category => this._onDidAddCategory.fire(this.getCategoryProgress(category)))); + this.extensionService.getExtensions().then(extensions => { + extensions.forEach(extension => this.registerExtensionContributions(extension)); + }); + + this.extensionService.onDidChangeExtensions(() => { + this.extensionService.getExtensions().then(extensions => { + extensions.forEach(extension => this.registerExtensionContributions(extension)); + }); + }); + + this._register(this.registry.onDidAddCategory(category => + this._onDidAddCategory.fire(this.getCategoryProgress(category)) + )); + this._register(this.registry.onDidAddTask(task => { this.registerDoneListeners(task); this._onDidAddTask.fire(this.getTaskProgress(task)); @@ -93,6 +116,65 @@ export class GettingStartedService extends Disposable implements IGettingStarted })); } + private registerExtensionContributions(extension: IExtensionDescription) { + const convertPaths = (path: string | { hc: string, dark: string, light: string }): { hc: URI, dark: URI, light: URI } => { + const convertPath = (path: string) => path.startsWith('https://') + ? URI.parse(path, true) + : FileAccess.asBrowserUri(joinPath(extension.extensionLocation, path)); + + if (typeof path === 'string') { + const converted = convertPath(path); + return { hc: converted, dark: converted, light: converted }; + } else { + return { + hc: convertPath(path.hc), + light: convertPath(path.light), + dark: convertPath(path.dark) + }; + } + }; + + if (!this.trackedExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) { + this.trackedExtensions.add(ExtensionIdentifier.toKey(extension.identifier)); + + if (extension.contributes?.gettingStarted?.length) { + if (!extension.enableProposedApi) { + console.warn('Extension', extension.identifier.value, 'contributes getting started content but has not enabled proposedApi. The contributed content will be disregarded.'); + return; + } + + const categoryID = `EXTContrib-${extension.identifier.value}`; + + this.registry.registerCategory({ + content: { type: 'items' }, + description: localize('extContrib', "Learn more about {0}!", extension.displayName ?? extension.name), + title: extension.displayName || extension.name, + id: categoryID, + icon: { + type: 'image', + path: extension.icon + ? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true) + : DefaultIconPath + }, + when: ContextKeyExpr.true(), + }); + extension.contributes?.gettingStarted.forEach((content, index) => { + this.registry.registerTask({ + button: content.button, + description: content.description, + media: { type: 'image', altText: content.media.altText, path: convertPaths(content.media.path) }, + doneOn: content.button.command ? { commandExecuted: content.button.command } : { eventFired: `linkOpened:${content.button.link}` }, + id: content.id, + title: content.title, + when: ContextKeyExpr.deserialize(content.when) ?? ContextKeyExpr.true(), + category: categoryID, + order: index, + }); + }); + } + } + } + private registerDoneListeners(task: IGettingStartedTask) { if (task.doneOn.commandExecuted) { const existing = this.commandListeners.get(task.doneOn.commandExecuted); diff --git a/src/vs/workbench/services/log/electron-sandbox/logService.ts b/src/vs/workbench/services/log/electron-sandbox/logService.ts index d0fba0e728c..4ace2bc3fba 100644 --- a/src/vs/workbench/services/log/electron-sandbox/logService.ts +++ b/src/vs/workbench/services/log/electron-sandbox/logService.ts @@ -5,14 +5,13 @@ import { LogService, ConsoleLogger, MultiplexLogService, ILogger } from 'vs/platform/log/common/log'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { LogLevelChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LogLevelChannelClient, FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { LoggerService } from 'vs/workbench/services/log/electron-sandbox/loggerService'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; export class NativeLogService extends LogService { - constructor(name: string, loggerService: LoggerService, mainProcessService: IMainProcessService, environmentService: INativeWorkbenchEnvironmentService) { + constructor(name: string, loggerService: LoggerChannelClient, mainProcessService: IMainProcessService, environmentService: INativeWorkbenchEnvironmentService) { const disposables = new DisposableStore(); const loggerClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel')); @@ -37,5 +36,4 @@ export class NativeLogService extends LogService { this._register(disposables); } - } diff --git a/src/vs/workbench/services/log/electron-sandbox/loggerService.ts b/src/vs/workbench/services/log/electron-sandbox/loggerService.ts deleted file mode 100644 index 50795cdf780..00000000000 --- a/src/vs/workbench/services/log/electron-sandbox/loggerService.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { LogLevel, ILoggerService, ILogger, AbstractMessageLogger, ILoggerOptions } from 'vs/platform/log/common/log'; -import { URI } from 'vs/base/common/uri'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; - -export class LoggerService implements ILoggerService { - - declare readonly _serviceBrand: undefined; - private readonly channel: IChannel; - - constructor( - @IMainProcessService mainProcessService: IMainProcessService - ) { - this.channel = mainProcessService.getChannel('logger'); - } - - createConsoleMainLogger(): ILogger { - return new Logger(this.channel); - } - - createLogger(file: URI, options?: ILoggerOptions): ILogger { - return new Logger(this.channel, file, options); - } - -} - -class Logger extends AbstractMessageLogger { - - private isLoggerCreated: boolean = false; - private buffer: [LogLevel, string][] = []; - - constructor( - private readonly channel: IChannel, - private readonly file?: URI, - loggerOptions?: ILoggerOptions, - ) { - super(loggerOptions?.always); - (this.file ? this.channel.call('createLogger', [file, loggerOptions]) : this.channel.call('createConsoleMainLogger', [file, loggerOptions])) - .then(() => { - this._log(this.buffer); - this.isLoggerCreated = true; - }); - } - - protected log(level: LogLevel, message: string) { - this._log([[level, message]]); - } - - private _log(messages: [LogLevel, string][]) { - if (this.isLoggerCreated) { - this.channel.call('log', [this.file, messages]); - } else { - this.buffer.push(...messages); - } - } -} diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 2c52dc75ffe..122f6e79b35 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NeverShowAgainScope, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -75,7 +75,7 @@ export class NotificationService extends Disposable implements INotificationServ const neverShowAgainAction = toDispose.add(new Action( 'workbench.notification.neverShowAgain', - nls.localize('neverShowAgain', "Don't Show Again"), + localize('neverShowAgain', "Don't Show Again"), undefined, true, async () => { // Close notification @@ -123,7 +123,7 @@ export class NotificationService extends Disposable implements INotificationServ } const neverShowAgainChoice = { - label: nls.localize('neverShowAgain', "Don't Show Again"), + label: localize('neverShowAgain', "Don't Show Again"), run: () => this.storageService.store(id, true, scope, StorageTarget.USER), isSecondary: options.neverShowAgain.isSecondary }; diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index afe18caf17f..e90d4794abe 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; import { CompositeScope, CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -27,7 +27,6 @@ class TestViewlet implements IViewlet { getActions(): IAction[] { return []; } getSecondaryActions(): IAction[] { return []; } getContextMenuActions(): IAction[] { return []; } - getActionViewItem(action: IAction): IActionViewItem { return null!; } getControl(): IEditorControl { return null!; } focus(): void { } getOptimalWidth(): number { return 10; } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 5c71435ecb2..420202d008a 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -23,6 +23,10 @@ export const IRemoteExplorerService = createDecorator('r export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; export const TUNNEL_VIEW_ID = '~remote.forwardedPorts'; +export const PORT_AUTO_FORWARD_SETTING = 'remote.autoForwardPorts'; +export const PORT_AUTO_SOURCE_SETTING = 'remote.autoForwardPortsSource'; +export const PORT_AUTO_SOURCE_SETTING_PROCESS = 'process'; +export const PORT_AUTO_SOURCE_SETTING_OUTPUT = 'output'; export enum TunnelType { Candidate = 'Candidate', diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 8e31df89bb1..9fe5b186c31 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, IResourceEncoding, stringToSnapshot, ITextFileSaveAsOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; @@ -143,7 +143,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // validate binary if (options?.acceptTextOnly && decoder.detected.seemsBinary) { - throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); + throw new TextFileOperationError(localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); } return [bufferStream, decoder]; @@ -414,9 +414,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex private async confirmOverwrite(resource: URI): Promise { const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the name '{0}' already exists in the folder '{1}'. Replacing it will overwrite its current contents.", basename(resource), basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + message: localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: localize('irreversible', "A file or folder with the name '{0}' already exists in the folder '{1}'. Replacing it will overwrite its current contents.", basename(resource), basename(dirname(resource))), + primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), type: 'warning' }; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index a6f0de819d3..cfde3de4dad 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; @@ -987,7 +987,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Decode: Load with encoding else { if (this.isDirty()) { - this.notificationService.info(nls.localize('saveFileFirst', "The file is dirty. Please save it first before reopening it with another encoding.")); + this.notificationService.info(localize('saveFileFirst', "The file is dirty. Please save it first before reopening it with another encoding.")); return; } diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index 380f47eafde..c001c65863c 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -5,7 +5,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionsInitializer } from 'vs/platform/userDataSync/common/extensionsSync'; -import { GlobalStateInitializer } from 'vs/platform/userDataSync/common/globalStateSync'; +import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; import { SnippetsInitializer } from 'vs/platform/userDataSync/common/snippetsSync'; @@ -16,8 +16,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; -import { ISyncExtension, IUserDataInitializer, IUserDataSyncLogService, IUserDataSyncStoreClient, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { ISyncExtension, IUserData, IUserDataInitializer, IUserDataSyncLogService, IUserDataSyncStoreClient, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; import { getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -30,6 +30,8 @@ import { IExtensionService, toExtensionDescription } from 'vs/workbench/services import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { mark } from 'vs/base/common/performance'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); export interface IUserDataInitializationService { @@ -47,6 +49,7 @@ export class UserDataInitializationService implements IUserDataInitializationSer private readonly initialized: SyncResource[] = []; private readonly initializationFinished = new Barrier(); + private globalStateUserData: IUserData | null = null; constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @@ -88,12 +91,6 @@ export class UserDataInitializationService implements IUserDataInitializationSer return; } - const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; - if (!userDataSyncStore) { - this.logService.trace(`Skipping initializing user data as sync service is not provided`); - return; - } - if (!this.environmentService.options?.credentialsProvider) { this.logService.trace(`Skipping initializing user data as credentials provider is not provided`); return; @@ -110,6 +107,15 @@ export class UserDataInitializationService implements IUserDataInitializationSer return; } + await this.initializeUserDataSyncStore(authenticationSession); + + const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; + if (!userDataSyncStore) { + this.logService.trace(`Skipping initializing user data as sync service is not provided`); + return; + } + + this.logService.info(`Using settings sync service ${userDataSyncStore.url.toString()} for initialization`); const userDataSyncStoreClient = new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); return userDataSyncStoreClient; @@ -119,6 +125,37 @@ export class UserDataInitializationService implements IUserDataInitializationSer return this._userDataSyncStoreClientPromise; } + private async initializeUserDataSyncStore(authenticationSession: AuthenticationSessionInfo): Promise { + const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; + if (!userDataSyncStore?.canSwitch) { + return; + } + + const disposables = new DisposableStore(); + try { + const userDataSyncStoreClient = disposables.add(new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService)); + userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); + + // Cache global state data for global state initialization + this.globalStateUserData = await userDataSyncStoreClient.read(SyncResource.GlobalState, null); + + if (this.globalStateUserData) { + const userDataSyncStoreType = new UserDataSyncStoreTypeSynchronizer(userDataSyncStoreClient, this.storageService, this.environmentService, this.fileService, this.logService).getSyncStoreType(this.globalStateUserData); + if (userDataSyncStoreType) { + await this.userDataSyncStoreManagementService.switch(userDataSyncStoreType); + + // Unset cached global state data if urls are changed + if (!isEqual(userDataSyncStore.url, this.userDataSyncStoreManagementService.userDataSyncStore?.url)) { + this.logService.info('Switched settings sync store'); + this.globalStateUserData = null; + } + } + } + } finally { + disposables.dispose(); + } + } + async whenInitializationFinished(): Promise { await this.initializationFinished.wait(); } @@ -158,7 +195,7 @@ export class UserDataInitializationService implements IUserDataInitializationSer this.initialized.push(syncResource); this.logService.trace(`Initializing ${getSyncAreaLabel(syncResource)}`); const initializer = this.createSyncResourceInitializer(syncResource, instantiationService); - const userData = await userDataSyncStoreClient.read(syncResource, null); + const userData = await userDataSyncStoreClient.read(syncResource, syncResource === SyncResource.GlobalState ? this.globalStateUserData : null); await initializer.initialize(userData); this.logService.info(`Initialized ${getSyncAreaLabel(syncResource)}`); } catch (error) { diff --git a/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts index af14b952ca6..41540b10fc8 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataAutoSyncEnablementService.ts @@ -29,13 +29,14 @@ export class WebUserDataAutoSyncEnablementService extends UserDataAutoSyncEnable } setEnablement(enabled: boolean) { - if (this.canToggleEnablement()) { - if (this.enabled !== enabled) { - this.enabled = enabled; - super.setEnablement(enabled); - if (this.workbenchEnvironmentService.options?.settingsSyncOptions?.enablementHandler) { - this.workbenchEnvironmentService.options.settingsSyncOptions.enablementHandler(this.enabled); - } + if (enabled && !this.canToggleEnablement()) { + return; + } + if (this.enabled !== enabled) { + this.enabled = enabled; + super.setEnablement(enabled); + if (this.workbenchEnvironmentService.options?.settingsSyncOptions?.enablementHandler) { + this.workbenchEnvironmentService.options.settingsSyncOptions.enablementHandler(this.enabled); } } } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 81afe84f97c..48602a7f767 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -31,6 +31,9 @@ import { URI } from 'vs/base/common/uri'; import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; type UserAccountClassification = { id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' }; @@ -109,6 +112,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); @@ -280,6 +284,11 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } await this.userDataAutoSyncService.turnOn(); + + if (this.userDataSyncStoreManagementService.userDataSyncStore?.canSwitch) { + await this.synchroniseUserDataSyncStoreType(); + } + this.notificationService.info(localize('sync turned on', "{0} is turned on", title)); } @@ -287,6 +296,25 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat return this.userDataAutoSyncService.turnOff(everywhere); } + async synchroniseUserDataSyncStoreType(): Promise { + if (!this.userDataSyncAccountService.account) { + throw new Error('Cannot update because you are signed out from settings sync. Please sign in and try again.'); + } + if (!isWeb || !this.userDataSyncStoreManagementService.userDataSyncStore) { + // Not supported + return; + } + + const userDataSyncStoreUrl = this.userDataSyncStoreManagementService.userDataSyncStore.type === 'insiders' ? this.userDataSyncStoreManagementService.userDataSyncStore.stableUrl : this.userDataSyncStoreManagementService.userDataSyncStore.insidersUrl; + const userDataSyncStoreClient = this.instantiationService.createInstance(UserDataSyncStoreClient, userDataSyncStoreUrl); + userDataSyncStoreClient.setAuthToken(this.userDataSyncAccountService.account.token, this.userDataSyncAccountService.account.authenticationProviderId); + await this.instantiationService.createInstance(UserDataSyncStoreTypeSynchronizer, userDataSyncStoreClient).sync(this.userDataSyncStoreManagementService.userDataSyncStore.type); + } + + syncNow(): Promise { + return this.userDataAutoSyncService.triggerSync(['Sync Now'], false, true); + } + private async onBeforeShutdown(manualSyncTask: IManualSyncTask): Promise { const result = await this.dialogService.confirm({ type: 'warning', diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index e46b1531589..c40f3fa8966 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -63,6 +63,9 @@ export interface IUserDataSyncWorkbenchService { resetSyncedData(): Promise; showSyncActivity(): Promise; + syncNow(): Promise; + + synchroniseUserDataSyncStoreType(): Promise; } export function getSyncAreaLabel(source: SyncResource): string { diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index a851ffe2676..64cd61a6431 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -5,7 +5,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { URI } from 'vs/base/common/uri'; -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER, IEnterWorkspaceResult, hasWorkspaceFileExtension, WORKSPACE_EXTENSION, isUntitledWorkspace, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; @@ -51,8 +51,8 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi async pickNewWorkspacePath(): Promise { let workspacePath = await this.fileDialogService.showSaveDialog({ - saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")), - title: nls.localize('saveWorkspace', "Save Workspace"), + saveLabel: mnemonicButtonLabel(localize('save', "Save")), + title: localize('saveWorkspace', "Save Workspace"), filters: WORKSPACE_FILTER, defaultUri: await this.fileDialogService.defaultWorkspacePath(undefined, UNTITLED_WORKSPACE_FILENAME) }); @@ -292,19 +292,19 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi } private onInvalidWorkspaceConfigurationFileError(): void { - const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again."); + const message = localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again."); this.askToOpenWorkspaceConfigurationFile(message); } private onWorkspaceConfigurationFileDirtyError(): void { - const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again."); + const message = localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again."); this.askToOpenWorkspaceConfigurationFile(message); } private askToOpenWorkspaceConfigurationFile(message: string): void { this.notificationService.prompt(Severity.Error, message, [{ - label: nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"), + label: localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"), run: () => this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile') }] ); diff --git a/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts b/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts new file mode 100644 index 00000000000..78181c577ee --- /dev/null +++ b/src/vs/workbench/services/workspaces/browser/workspaceTrustEditorInput.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { IWorkspaceTrustService } from 'vs/platform/workspace/common/workspaceTrust'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { WorkspaceTrustEditorModel, WorkspaceTrustService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; + +export class WorkspaceTrustEditorInput extends EditorInput { + static readonly ID: string = 'workbench.input.workspaceTrust'; + + readonly resource: URI = URI.from({ + scheme: Schemas.vscodeWorkspaceTrust, + path: `workspaceTrustEditor` + }); + + constructor( + @IWorkspaceTrustService private readonly workspaceTrustService: WorkspaceTrustService + ) { + super(); + } + + getTypeId(): string { + return WorkspaceTrustEditorInput.ID; + } + + matches(otherInput: unknown): boolean { + return otherInput instanceof WorkspaceTrustEditorInput; + } + + getName(): string { + return localize('workspaceTrustEditorInputName', "Workspace Trust"); + } + + async resolve(): Promise { + return this.workspaceTrustService.workspaceTrustEditorModel; + } +} diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index f8ea335eae8..f048e4d411d 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -13,16 +13,28 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustModel, IWorkspaceTrustRequest, IWorkspaceTrustRequestModel, IWorkspaceTrustService, IWorkspaceTrustStateInfo, WorkspaceTrustState, WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust'; import { isEqual, isEqualOrParent } from 'vs/base/common/extpath'; +import { EditorModel } from 'vs/workbench/common/editor'; export const WORKSPACE_TRUST_ENABLED = 'workspace.trustEnabled'; export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key'; -export const WORKSPACE_TRUST_URI = URI.parse('workspaceTrust:/Trusted Workspaces'); export const WorkspaceTrustContext = { PendingRequest: new RawContextKey('workspaceTrustPendingRequest', false), TrustState: new RawContextKey('workspaceTrustState', WorkspaceTrustState.Unknown) }; +export class WorkspaceTrustEditorModel extends EditorModel { + constructor( + readonly dataModel: IWorkspaceTrustModel, + private readonly workspaceTrustService: WorkspaceTrustService + ) { + super(); + } + + get currentWorkspaceTrustState(): WorkspaceTrustState { + return this.workspaceTrustService.getWorkspaceTrustState(); + } +} export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustModel { private storageKey = WORKSPACE_TRUST_STORAGE_KEY; @@ -82,6 +94,30 @@ export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustMo this._onDidChangeTrustState.fire(); } + setTrustedFolders(folders: URI[]): void { + this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(folder => folder.trustState !== WorkspaceTrustState.Trusted); + for (const folder of folders) { + this.trustStateInfo.localFolders.push({ + trustState: WorkspaceTrustState.Trusted, + uri: folder.fsPath + }); + } + + this.saveTrustInfo(); + } + + setUntrustedFolders(folders: URI[]): void { + this.trustStateInfo.localFolders = this.trustStateInfo.localFolders.filter(folder => folder.trustState !== WorkspaceTrustState.Untrusted); + for (const folder of folders) { + this.trustStateInfo.localFolders.push({ + trustState: WorkspaceTrustState.Untrusted, + uri: folder.fsPath + }); + } + + this.saveTrustInfo(); + } + setFolderTrustState(folder: URI, trustState: WorkspaceTrustState): void { let changed = false; @@ -135,6 +171,10 @@ export class WorkspaceTrustModel extends Disposable implements IWorkspaceTrustMo return result; } + + getTrustStateInfo(): IWorkspaceTrustStateInfo { + return this.trustStateInfo; + } } export class WorkspaceTrustRequestModel extends Disposable implements IWorkspaceTrustRequestModel { @@ -166,6 +206,7 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust _serviceBrand: undefined; private readonly dataModel: IWorkspaceTrustModel; readonly requestModel: IWorkspaceTrustRequestModel; + private editorModel?: WorkspaceTrustEditorModel; private readonly _onDidChangeTrustState = this._register(new Emitter()); readonly onDidChangeTrustState = this._onDidChangeTrustState.event; @@ -212,6 +253,14 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust this._onDidChangeTrustState.fire({ previousTrustState: previousState, currentTrustState: this._currentTrustState }); } + get workspaceTrustEditorModel(): WorkspaceTrustEditorModel { + if (this.editorModel === undefined) { + this.editorModel = this._register(new WorkspaceTrustEditorModel(this.dataModel, this)); + } + + return this.editorModel; + } + private calculateWorkspaceTrustState(): WorkspaceTrustState { if (!this.isWorkspaceTrustEnabled()) { return WorkspaceTrustState.Trusted; diff --git a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts index 22f2eeef016..134e347dd65 100644 --- a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts @@ -26,7 +26,7 @@ suite('ExtHostTreeView', function () { onRefresh = new Emitter<{ [treeItemHandle: string]: ITreeItem }>(); - $registerTreeViewDataProvider(treeViewId: string): void { + async $registerTreeViewDataProvider(treeViewId: string): Promise { } $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts index 57be38dfd98..1c48801f6c5 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts @@ -46,7 +46,7 @@ suite('BoundModelReferenceCollection', () => { dispose() { disposed.push(0); } - }); + }, 6); col.add( URI.parse('test://boofar'), @@ -55,7 +55,7 @@ suite('BoundModelReferenceCollection', () => { dispose() { disposed.push(1); } - }); + }, 6); col.add( URI.parse('test://xxxxxxx'), @@ -64,7 +64,7 @@ suite('BoundModelReferenceCollection', () => { dispose() { disposed.push(2); } - }); + }, 70); assert.deepStrictEqual(disposed, [0, 1]); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 1935dbb5adc..e5c69a38e4e 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -713,10 +713,10 @@ suite('ExtHostSearch', () => { match: null // Don't care about this right now } } : { - uri: r.uri.toString(), - text: r.text, - lineNumber: r.lineNumber - }); + uri: r.uri.toString(), + text: r.text, + lineNumber: r.lineNumber + }); return assert.deepEqual( makeComparable(actualTextSearchResults), diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index aa57432c433..2fd80a3c538 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -232,7 +232,6 @@ import 'vs/workbench/contrib/terminal/common/environmentVariable.contribution'; import 'vs/workbench/contrib/terminal/common/terminalExtensionPoints.contribution'; import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; import 'vs/workbench/contrib/terminal/browser/terminalView'; -import 'vs/workbench/contrib/terminal/browser/remoteTerminalService'; // Relauncher import 'vs/workbench/contrib/relauncher/browser/relauncher.contribution'; diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 2cd1d1d6916..5ff656776f3 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -113,6 +113,9 @@ import 'vs/workbench/contrib/remote/electron-sandbox/remote.contribution'; // Configuration Exporter import 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExportHelper.contribution'; +// Terminal +import 'vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution'; + // Themes Support import 'vs/workbench/contrib/themes/browser/themes.test.contribution'; diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 066b8f63fde..35ee19cc0c5 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -73,7 +73,7 @@ export class Application { await this.code.waitForElement('.explorer-folders-view'); if (expectWalkthroughPart) { - await this.code.waitForActiveElement(`.editor-instance > div > div.welcomePageFocusElement[tabIndex="0"]`); + await this.code.waitForElement(`.editor-instance > div > div.welcomePageFocusElement[tabIndex="0"]`); } } diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 0f04f08fa26..719598b188e 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -138,6 +138,7 @@ export async function spawn(options: SpawnOptions): Promise { '--disable-telemetry', '--no-cached-data', '--disable-updates', + '--disable-keytar', '--disable-crash-reporter', `--extensions-dir=${options.extensionsPath}`, `--user-data-dir=${options.userDataDir}`, diff --git a/yarn.lock b/yarn.lock index 8695942fcec..4fbbc3752b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9687,11 +9687,6 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@4.2.0-dev.20201207: - version "4.2.0-dev.20201207" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.0-dev.20201207.tgz#19a34bc7d2d42a7467c512c63f135587ac848807" - integrity sha512-fPHBDi/fgdX4WiRC7cFVv/aL069PgUaDWuLYUSHatWZujz/Lkc9bkf/zL3rKdNSCxlNKAMs3fhJv/yompOphZA== - typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" @@ -9702,6 +9697,11 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +typescript@^4.3.0-dev.20210216: + version "4.3.0-dev.20210216" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.0-dev.20210216.tgz#233327e6094008c02265ba140f8d9ece9133421e" + integrity sha512-pJLcC/kqnE+0rftTRc2/gYBkz9nl+kJfaU8sSOLYnzUvD8p+LOZMzXfaLoKPdGFJ6U9+Ox/sYV9HBTJVEjSTYg== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"